package inliners

import (
	"strings"

	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/debug"
	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/jsnum"
	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/printer"
	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/scanner"
	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/transformers"
)

type ConstEnumInliningTransformer struct {
	transformers.Transformer
	compilerOptions   *core.CompilerOptions
	currentSourceFile *ast.SourceFile
	emitResolver      printer.EmitResolver
}

func NewConstEnumInliningTransformer(opt *transformers.TransformOptions) *transformers.Transformer {
	compilerOptions := opt.CompilerOptions
	emitContext := opt.Context
	if compilerOptions.GetIsolatedModules() {
		debug.Fail("const enums are not inlined under isolated modules")
	}
	tx := &ConstEnumInliningTransformer{compilerOptions: compilerOptions, emitResolver: opt.EmitResolver}
	return tx.NewTransformer(tx.visit, emitContext)
}

func (tx *ConstEnumInliningTransformer) visit(node *ast.Node) *ast.Node {
	switch node.Kind {
	case ast.KindPropertyAccessExpression, ast.KindElementAccessExpression:
		{
			parse := tx.EmitContext().ParseNode(node)
			if parse == nil {
				return tx.Visitor().VisitEachChild(node)
			}
			value := tx.emitResolver.GetConstantValue(parse)
			if value != nil {
				var replacement *ast.Node
				switch v := value.(type) {
				case jsnum.Number:
					if v.IsInf() {
						if v.Abs() == v {
							replacement = tx.Factory().NewIdentifier("Infinity")
						} else {
							replacement = tx.Factory().NewPrefixUnaryExpression(ast.KindMinusToken, tx.Factory().NewIdentifier("Infinity"))
						}
					} else if v.IsNaN() {
						replacement = tx.Factory().NewIdentifier("NaN")
					} else if v.Abs() == v {
						replacement = tx.Factory().NewNumericLiteral(v.String())
					} else {
						replacement = tx.Factory().NewPrefixUnaryExpression(ast.KindMinusToken, tx.Factory().NewNumericLiteral(v.Abs().String()))
					}
				case string:
					replacement = tx.Factory().NewStringLiteral(v)
				case jsnum.PseudoBigInt: // technically not supported by strada, and issues a checker error, handled here for completeness
					if v == (jsnum.PseudoBigInt{}) {
						replacement = tx.Factory().NewBigIntLiteral("0")
					} else if !v.Negative {
						replacement = tx.Factory().NewBigIntLiteral(v.Base10Value)
					} else {
						replacement = tx.Factory().NewPrefixUnaryExpression(ast.KindMinusToken, tx.Factory().NewBigIntLiteral(v.Base10Value))
					}
				}

				if tx.compilerOptions.RemoveComments.IsFalseOrUnknown() {
					original := tx.EmitContext().MostOriginal(node)
					if original != nil && !ast.NodeIsSynthesized(original) {
						originalText := scanner.GetTextOfNode(original)
						escapedText := " " + safeMultiLineComment(originalText) + " "
						tx.EmitContext().AddSyntheticTrailingComment(replacement, ast.KindMultiLineCommentTrivia, escapedText, false)
					}
				}
				return replacement
			}
			return tx.Visitor().VisitEachChild(node)
		}
	}
	return tx.Visitor().VisitEachChild(node)
}

func safeMultiLineComment(text string) string {
	return strings.ReplaceAll(text, "*/", "*_/")
}
