package message

import (
	"fmt"
	"math"
	"strconv"
	"strings"

	"code.justin.tv/cplat/twitchling/internal/syntax/ast"
	"code.justin.tv/cplat/twitchling/internal/util"
	"code.justin.tv/cplat/twitchling/localization/language"
	"golang.org/x/text/feature/plural"
)

type visitorContext struct {
	hasEmphasis bool
}

func (c visitorContext) withEmphasis(hasEmphasis bool) visitorContext {
	return visitorContext{
		hasEmphasis,
	}
}

func (m *messageFormatter) visitNode(ctx visitorContext, node ast.Node) error {
	switch n := node.(type) {
	case *ast.TextSpan:
		m.visitTextSpan(ctx, n)
	case *ast.Element:
		return m.visitElement(ctx, n)
	case *ast.Argument:
		return m.visitArgument(ctx, n)
	case *ast.NumberFunction:
		return m.visitNumberFunction(ctx, n)
	case *ast.PluralFunction:
		return m.visitPluralFunction(ctx, n)
	}

	return nil
}

func (m *messageFormatter) visitTextSpan(ctx visitorContext, span *ast.TextSpan) {
	textFragment := TextFragment{value: span.Value(), hasEmphasis: ctx.hasEmphasis}

	m.fragments = append(m.fragments, &textFragment)
}

func (m *messageFormatter) visitElement(ctx visitorContext, element *ast.Element) error {
	ctx = ctx.withEmphasis(element.Kind() == ast.ElementKind_Emphasis)

	for _, node := range element.Nodes() {
		err := m.visitNode(ctx, node)
		if err != nil {
			return err
		}
	}

	return nil
}

func (m *messageFormatter) visitArgument(ctx visitorContext, argument *ast.Argument) error {
	arg := argument.Argument()
	value, ok := m.placeholders[arg]

	if !ok {
		return newPlaceholderError(PlaceHolderError_NotFound, arg)
	}

	if v, ok := value.(*NestedMessage); ok {
		fragments, err := v.Format()
		if err != nil {
			return err
		}

		for _, fragment := range fragments {
			if ctx.hasEmphasis {
				m.fragments = append(m.fragments, fragment.WithEmphasis())
				continue
			}

			m.fragments = append(m.fragments, fragment)
		}

		return nil
	}

	var fragment Fragment
	switch v := value.(type) {
	case *String:
		fragment = &TextFragment{value: string(*v), hasEmphasis: ctx.hasEmphasis}
	case *Number:
		fragment = &NumberFragment{kind: NumberFragmentKind_Integer, value: float64(*v), hasEmphasis: ctx.hasEmphasis}
	case ID:
		fragment = &IDFragment{kind: convertIDKind(v.Kind()), value: v.String(), hasEmphasis: ctx.hasEmphasis}
	default:
		// NOTE: This switch should handle all supported placeholder types. If this point is reached, it's a bug and a panic is produced.
		panic("unreachable")
	}

	m.fragments = append(m.fragments, fragment)

	return nil
}

func (m *messageFormatter) visitNumberFunction(ctx visitorContext, function *ast.NumberFunction) error {
	argument := function.Argument()
	value, ok := m.placeholders[argument]

	if !ok {
		return newPlaceholderError(PlaceHolderError_NotFound, argument)
	}

	number, ok := value.(*Number)

	if !ok {
		return newPlaceholderError(PlaceHolderError_NumberExpected, argument)
	}

	var kind NumberFragmentKind
	switch function.Format() {
	case ast.NumberFormat_Integer:
		kind = NumberFragmentKind_Integer
	case ast.NumberFormat_Currency:
		kind = NumberFragmentKind_Currency
	case ast.NumberFormat_Percent:
		kind = NumberFragmentKind_Percent
	}

	m.fragments = append(m.fragments, &NumberFragment{kind: kind, value: float64(*number), hasEmphasis: ctx.hasEmphasis})

	return nil
}

func (m *messageFormatter) visitPluralFunction(ctx visitorContext, function *ast.PluralFunction) error {
	argument := function.Argument()
	value, ok := m.placeholders[argument]

	if !ok {
		return newPlaceholderError(PlaceHolderError_NotFound, argument)
	}

	number, ok := value.(*Number)

	if !ok {
		return newPlaceholderError(PlaceHolderError_NumberExpected, argument)
	}

	pluralForm := getPluralForm(m.language, float64(*number))
	variant, ok := function.Variants()[pluralForm]

	// Fallback on the other form in the event the correct plural form is missing.
	if !ok {
		variant = function.Variants()[ast.PluralForm_Other]
	}

	for _, node := range variant {
		err := m.visitNode(ctx, node)
		if err != nil {
			return err
		}
	}

	return nil
}

func convertIDKind(kind IDKind) IDFragmentKind {
	switch kind {
	case IDKind_User:
		return IDFragmentKind_User
	case IDKind_Emote:
		return IDFragmentKind_Emote
	}

	// If this is reached, there's a bug in the code.
	panic("unreachable")
}

// NOTE: Not the fastest approach, but it's easy.
func getPluralForm(language language.Tag, number float64) ast.PluralForm {
	// Convert to a string with a precision of 4.
	// Use the absolute value since the plurals function does not support negative numbers.
	numberStr := fmt.Sprintf("%.4f", math.Abs(number))
	var integerPart, fractionPart string
	// Safely unpack (values will default to 0 if array part is missing)
	util.Unpack(strings.Split(numberStr, "."), &integerPart, &fractionPart)
	trimmedFractionPart := strings.TrimRight(fractionPart, "0")

	// Get the integer part of the number.
	i, err := strconv.Atoi(integerPart)
	if err != nil {
		// This is a bug if we get here...
		panic(err)
	}

	// Extract the v, w, t, and f parameters for MatchPlural
	// See https://unicode.org/reports/tr35/tr35-numbers.html#Operands for more info.
	var v, w, t int
	// Only extract values for v, w, and t if there's a non-zero fractional component.
	if len(trimmedFractionPart) > 0 {
		v = len(fractionPart)
		w = len(trimmedFractionPart)
		t, err = strconv.Atoi(trimmedFractionPart)
		if err != nil {
			// This is a bug if we get here...
			panic(err)
		}
	}

	f := t * int(math.Pow10(v-w))

	// Mod 10,000,000 according to the spec.
	i %= 10_000_000
	v %= 10_000_000
	w %= 10_000_000
	f %= 10_000_000
	t %= 10_000_000

	form := plural.Cardinal.MatchPlural(language.Inner(), i, v, w, f, t)

	switch form {
	case plural.Zero:
		return ast.PluralForm_Zero
	case plural.One:
		return ast.PluralForm_One
	case plural.Two:
		return ast.PluralForm_Two
	case plural.Few:
		return ast.PluralForm_Few
	case plural.Many:
		return ast.PluralForm_Many
	default:
		return ast.PluralForm_Other
	}
}
