package typescript

import (
	"io"
	"strings"

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

var EXPECTATIONS_TO_FUNCTION = map[string]string{
	ValuesToBeInSet:         "valuesToBeInSet",
	ValuesToBeBetween:       "valuesToBeBetween",
	ValuesToNotBeNull:       "valuesToNotBeNull",
	ValuesToMatchRegexList:  "valuesToMatchRegexList",
	ValueLengthsToBeBetween: "valueLengthsToBeBetween",
}

func GenerateExpectationsDefinitions(o io.Writer, schema Schema) {
	if schema.HasFieldWithExpectation(ValuesToBeInSet) {
		GenerateValuesToBeInSetDefinition(o)
	}
	if schema.HasFieldWithExpectation(ValuesToBeBetween) {
		GenerateValuesToBeBetweenDefinition(o)
	}
	if schema.HasFieldWithExpectation(ValuesToNotBeNull) {
		GenerateValuesToNotBeNullDefinition(o)
	}
	if schema.HasFieldWithExpectation(ValuesToMatchRegexList) {
		GenerateValuesToMatchRegexListDefinition(o)
	}
	if schema.HasFieldWithExpectation(ValueLengthsToBeBetween) {
		GenerateValueLengthsToBeBetweenDefinition(o)
	}
}

func GenerateValuesToNotBeNullCall(v string) string {
	return strings.Join([]string{EXPECTATIONS_TO_FUNCTION["values_to_not_be_null"], "(", v, ")"}, "")
}

func GenerateValuesToNotBeNullDefinition(o io.Writer) {
	P(o, "")
	P(o, "function ", EXPECTATIONS_TO_FUNCTION["values_to_not_be_null"], "(value: any): Error | undefined {")
	P(o, "  if (value === null || value === undefined) {")
	P(o, "    return new Error(`expected to be defined`)")
	P(o, "  }")
	P(o, "")
	P(o, "  if (typeof value === 'string' || value instanceof String) {")
	P(o, "    if (value === \"\") {")
	P(o, "      return new Error(`expected to not be empty string`)")
	P(o, "    }")
	P(o, "  }")
	P(o, "")
	P(o, "  return")
	P(o, "}")
}

func GenerateValuesToMatchRegexListCall(v string, regexes string) string {
	return strings.Join([]string{EXPECTATIONS_TO_FUNCTION["values_to_match_regex_list"], "(", v, ", ", regexes, ")"}, "")
}

func GenerateValuesToMatchRegexListDefinition(o io.Writer) {
	P(o, "")
	P(o, "function ", EXPECTATIONS_TO_FUNCTION["values_to_match_regex_list"], "(value: any, regexes: RegExp[]): Error | undefined {")
	P(o, "  if (value === null || value === undefined) {")
	P(o, "    return")
	P(o, "  }")
	P(o, "")
	P(o, "  if (!regexes.find(r => r.test(value))) {")
	P(o, "    return Error(`expected to match one of ${regexes} got ${value}`)")
	P(o, "  }")
	P(o, "")
	P(o, "  return")
	P(o, "}")
}

func GenerateValueLengthsToBeBetweenCall(v string, min string, max string) string {
	return strings.Join([]string{EXPECTATIONS_TO_FUNCTION["value_lengths_to_be_between"], "(", v, ", ", min, ", ", max, ")"}, "")
}

// https://git.xarth.tv/spade/events/blob/mainline/expectations/value_lengths_to_be_between.md
func GenerateValueLengthsToBeBetweenDefinition(o io.Writer) {
	P(o, "")
	P(o, "function ", EXPECTATIONS_TO_FUNCTION["value_lengths_to_be_between"], "(value: string | undefined, min: number, max: number): Error | undefined {")
	P(o, "  if (value === null || value === undefined) {")
	P(o, "    return")
	P(o, "  }")
	P(o, "")
	P(o, "	if (value.length < min || value.length > max) {")
	P(o, "		return new Error(`expected length to be between between ${min} and ${max} got ${value.length}`)")
	P(o, "	}")
	P(o, "")
	P(o, "	return")
	P(o, "}")
}

func GenerateValuesToBeInSetCall(v string, set string) string {
	return strings.Join([]string{EXPECTATIONS_TO_FUNCTION["values_to_be_in_set"], "(", v, ", ", set, ")"}, "")
}

// https://git.xarth.tv/spade/events/blob/mainline/expectations/values_to_be_in_set.md
func GenerateValuesToBeInSetDefinition(o io.Writer) {
	P(o, "")
	P(o, "function ", EXPECTATIONS_TO_FUNCTION["values_to_be_in_set"], "(value: any, set: Set<any>): Error | undefined {")
	P(o, "  if (value === null || value === undefined) {")
	P(o, "    return")
	P(o, "  }")
	P(o, "")
	P(o, "  if (!set.has(value)) {")
	P(o, "    return new Error(`expected to be in ${Array.from(set.values())} got ${value}`)")
	P(o, "  }")
	P(o, "")
	P(o, "  return")
	P(o, "}")
}

func GenerateValuesToBeBetweenCall(v string, min string, max string) string {
	return strings.Join([]string{EXPECTATIONS_TO_FUNCTION["values_to_be_between"], "(", v, ", ", min, ", ", max, ")"}, "")
}

// TODO: handle timestamps, stop using any (?)
// https://git.xarth.tv/spade/events/blob/mainline/expectations/values_to_be_between.md
func GenerateValuesToBeBetweenDefinition(o io.Writer) {
	P(o, "")
	P(o, "function ", EXPECTATIONS_TO_FUNCTION["values_to_be_between"], "(value: any, min: any, max: any): Error | undefined {")
	P(o, "  if (value === null || value === undefined) {")
	P(o, "    return")
	P(o, "  }")
	P(o, "")
	P(o, "  if (typeof value === 'string' || value instanceof String) {")
	P(o, "    if (value.localeCompare(min) === -1 || value.localeCompare(max) === 1) {")
	P(o, "      return new Error(`expected to be between ${min} and ${max} got ${value}`)")
	P(o, "    }")
	P(o, "  }")
	P(o, "")
	P(o, "  if (typeof value === 'number' || value instanceof Number) {")
	P(o, "    if (value < min || value > max) {")
	P(o, "        return new Error(`expected to be between ${min} and ${max} got ${value}`)")
	P(o, "    }")
	P(o, "  }")
	P(o, "")
	P(o, "  return")
	P(o, "}")
}
