package typescript

import (
	"fmt"
	"io"

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

func GenerateEventMap(o io.Writer, schema Schema) {
	P(o, "// __EventMap is a utility type that maps the event name to the type for the event properties.")
	P(o, "interface __EventMap {")
	for _, event := range schema.Events {
		P(o, "  ", *event.Name, ": ", ToCamelCase(*event.Name), ";")
	}
	P(o, "}")
}

func GenerateEventsDefinitions(o io.Writer, schema Schema, options GenerationOptions) {
	if len(schema.Events) == 0 {
		return
	}

	// generate each event interface
	for _, event := range schema.Events {
		P(o, "")
		generateEventInterfaceDefinition(o, event)
	}

	// generate a factory that returns constructors for each event with CommonProps omitted
	P(o, "")
	P(o, "export class TailoredSpadeEventFactory<CommonProps = {}> {")
	for _, event := range schema.Events {
		if event.Description != nil {
			WriteComment(o, 2, *event.Description)
		}
		P(o, "  ", ToLowerCamelCase(*event.Name), "(")
		P(o, "    attributes: Omit<", ToCamelCase(*event.Name), ", keyof CommonProps>,")
		P(o, "  ): TailoredSpadeEvent<CommonProps> {")
		P(o, "    const event: TailoredSpadeEvent<CommonProps> = {")
		P(o, "      name: \"", *event.Name, "\",")
		P(o, "      attributes,")
		P(o, "    };")

		// generate runtime expectations
		if event.ExpectationsCount() > 0 {
			P(o, "")
			P(o, "    if (", options.GuardExpectationsWithExpression, ") {")
			P(o, "      event.validate = function (): Error | undefined {")
			P(o, "        let _event = this as __MappedEventUnion<\"", *event.Name, "\">;")
			P(o, "        return concat(")
			P(o, "          \"", *event.Name, "\",")
			for _, field := range event.Fields {
				generateFieldExpectationsCall(o, field)
			}
			P(o, "        );")

			P(o, "      };")
			P(o, "    }")
			P(o, "")
		}

		P(o, "    return event;")
		P(o, "  ", "}")
		P(o, "")
	}
	P(o, "}")
}

// GenerateEventInterfaceDefinition writes the following to the io.Writer
//
//  type {EventName} = {
// 	  // {attribute_a_comment}
// 	  {attribute_a_name}: {attribute_a_type}
//
// 	  // {attribute_b_comment}
// 	  {attribute_b_name}: {attribute_b_type}
//  }
//
func generateEventInterfaceDefinition(o io.Writer, event Event) {
	P(o, "// The input of ", ToLowerCamelCase(*event.Name), " event constructor")
	P(o, "type ", ToCamelCase(*event.Name), " = {")

	for _, field := range event.Fields {
		if field.Description != nil {
			WriteComment(o, 2, *field.Description)
		}

		d := SchemaFieldToProperty(field)
		P(o, "  ", d.Identifier, ": ", d.Type, ";")
	}

	P(o, "};")
}

// generateFieldExpectationFunctionExpression write the field expectations function expresions.
//
// withPrefix("{field_name}", {field_expectation_1_call}),
// withPrefix("{field_name}", {field_expectation_2_call}),
//
func generateFieldExpectationsCall(o io.Writer, field Field) {
	attribute := fmt.Sprintf("_event.attributes.%s", *field.Name)

	for _, expectation := range field.Expectations {
		var call string

		switch expectation.Name {
		case ValueLengthsToBeBetween:
			call = GenerateValueLengthsToBeBetweenCall(attribute, expectation.Min, expectation.Max)
		case ValuesToBeBetween:
			call = GenerateValuesToBeBetweenCall(attribute, ToNumberOrString(expectation.Min), ToNumberOrString(expectation.Max))
		case ValuesToNotBeNull:
			call = GenerateValuesToNotBeNullCall(attribute)
		case ValuesToMatchRegexList:
			call = GenerateValuesToMatchRegexListCall(attribute, StringsToRegexes(expectation.Patterns))
		default:
			err := fmt.Sprintf("fatal: unknown expectation %s for field %s\n", expectation.Name, *field.Name)
			err = err + fmt.Sprintf("please contact the data platform team by making a support desk ticket support.di.xarth.tv\n")
			Fatal(err)
		}

		if call != "" {
			P(o, "          withPrefix(")
			P(o, "            \"", *field.Name, "\",")
			P(o, "            ", call, ",")
			P(o, "          ),")
		}
	}
}
