package typescript

import (
	"io"
	"os"
	"path"

	. "code.justin.tv/spade/code-generator/internal"
)

type GenerationOptions struct {
	GuardExpectationsWithExpression string
}

func GenerateCodeToFile(schema Schema, dir string, options GenerationOptions) error {
	p := path.Join(dir, "spade-events.gen.ts")
	file, err := os.Create(p)
	if err != nil {
		return err
	}

	defer file.Close()
	return GenerateCode(schema, file, options)
}

func GenerateCode(schema Schema, o io.Writer, options GenerationOptions) error {
	P(o, "// Generated from spade-code-generator v", Version)
	P(o, "// Generated using this endpoint: ", schema.Source)
	P(o, "// tslint:disable")
	P(o, "")

	GenerateEventMap(o, schema)

	P(o, "")
	P(o, "// __MappedEventUnion is a type constructor that maps a string union E (that must be a subset of the keys of __EventMap)")
	P(o, "// into a union of object types where the name property is a key of __EventMap and the attributes property is the corresponding type.")
	P(o, "// The OmittedAttrs type parameter allows for filtering each of the attributes types to omit 'common' properties that a client might provide.")
	P(o, "type __MappedEventUnion<")
	P(o, "  E extends keyof __EventMap,")
	P(o, "  OmittedAttrs = {}")
	P(o, "> = E extends any")
	P(o, "  ? {")
	P(o, "      name: E;")
	P(o, "      attributes: Omit<__EventMap[E], keyof OmittedAttrs>;")
	P(o, "      validate?: () => Error | undefined;")
	P(o, "    }")
	P(o, "  : never;")

	P(o, "")
	P(o, "// SpadeEvent is a union of all the valid (registered in SSR) shapes of events that can be submitted to Spade.")
	P(o, "export type SpadeEvent = __MappedEventUnion<keyof __EventMap>;")
	P(o, "")
	P(o, "// TailoredSpadeEvent is a type constructor that produces the same union as SpadeEvent, but with a set of common client-provided properties")
	P(o, "// omitted from each event's `attributes` type as defined by the type passed as P).")
	P(o, "export type TailoredSpadeEvent<P> = __MappedEventUnion<keyof __EventMap, P>;")

	GenerateEventsDefinitions(o, schema, options)

	if schema.HasAnyExpectations() {
		P(o, "")
		P(o, "function concat(")
		P(o, "  name: string,")
		P(o, "  ...errors: (Error | undefined)[]")
		P(o, "): Error | undefined {")
		P(o, "  const errs = errors.filter((e): e is Error => !!e);")
		P(o, "")
		P(o, "  return errs.length > 0")
		P(o, "    ? new Error(")
		P(o, "        `spade event ${name} failed expectations:\\n${errs")
		P(o, "          .map((e) => e.message)")
		P(o, "          .join(`\\n`)}`,")
		P(o, "      )")
		P(o, "    : undefined;")
		P(o, "}")
		P(o, "")
		P(o, "function withPrefix(name: string, error?: Error): Error | undefined {")
		P(o, "  return error ? new Error(`${name}: ${error.message}`) : undefined;")
		P(o, "}")

		GenerateExpectationsDefinitions(o, schema)
	}

	return nil
}
