package commands

import (
	"fmt"
	"strings"

	"c65gm/internal/compiler"
	"c65gm/internal/preproc"
	"c65gm/internal/utils"
)

// WordCommand handles WORD variable declarations
// Syntax:
//
//	WORD varname                    # word with init = 0
//	WORD varname = value            # word with init value
//	WORD varname = "string"         # word pointer to constant string
//	WORD varname = @label           # word initialized to label address
//	WORD varname @ address          # word at absolute address
//	WORD CONST varname = value      # constant word
type WordCommand struct {
	varName string
	value   uint16
	isConst bool
	isAbs   bool
}

func (c *WordCommand) WillHandle(line preproc.Line) bool {
	params, err := utils.ParseParams(line.Text)
	if err != nil || len(params) == 0 {
		return false
	}
	return strings.ToUpper(params[0]) == "WORD"
}

func (c *WordCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error {
	// Clear state
	c.varName = ""
	c.value = 0
	c.isConst = false
	c.isAbs = false

	params, err := utils.ParseParams(line.Text)
	if err != nil {
		return err
	}

	paramCount := len(params)

	// Reject WORD REGISTER (6502 has no 16-bit ALU register)
	if paramCount >= 2 && strings.ToUpper(params[1]) == "REGISTER" {
		return fmt.Errorf("WORD REGISTER is not supported; only BYTE variables may use the REGISTER hint (the 6502 has no 16-bit ALU register). Use a @-mapped zero-page WORD instead")
	}

	// Validate parameter count
	if paramCount != 2 && paramCount != 4 && paramCount != 5 {
		return fmt.Errorf("WORD: wrong number of parameters (%d)", paramCount)
	}

	var varName string
	var value int64
	scope := ctx.FunctionHandler.CurrentFunction()

	// Create constant lookup function
	constLookup := ctx.SymbolTable.ConstantLookupFunc(ctx.CurrentScope())

	switch paramCount {
	case 2:
		// WORD varname
		varName = params[1]
		value = 0

		if !utils.ValidateIdentifier(varName) {
			return fmt.Errorf("WORD: invalid identifier %q", varName)
		}

			err = ctx.SymbolTable.AddVar(varName, scope, compiler.KindWord, uint16(value), line)

	case 4:
		// WORD varname = value  OR  WORD varname @ address  OR  WORD varname = "string"
		varName = params[1]
		operator := params[2]
		valueStr := params[3]

		if !utils.ValidateIdentifier(varName) {
			return fmt.Errorf("WORD: invalid identifier %q", varName)
		}

		// Check for string literal
		if utils.IsStringLiteral(valueStr) {
			if operator != "=" {
				return fmt.Errorf("WORD: expected '=' when assigning string pointer, got %q", operator)
			}

			// Generate label for the string
			label := ctx.GeneralStack.Push()

			// Get pragma set for this line
			pragmaSet := ctx.Pragma.GetPragmaSetByIndex(line.PragmaSetIndex)

			// Add string to constant string handler
			ctx.ConstStrHandler.AddConstStr(label, valueStr, false, pragmaSet)

			err = ctx.SymbolTable.AddLabel(varName, scope, label, line)
			if err != nil {
				return fmt.Errorf("WORD: %w", err)
			}

			c.varName = varName
			c.value = 0 // label refs don't have numeric values
			return nil
		}

		// Check for label reference: @label
		if strings.HasPrefix(valueStr, "@") {
			if operator != "=" {
				return fmt.Errorf("WORD: expected '=' when assigning label reference, got %q", operator)
			}

			labelName := valueStr[1:] // strip @
			if !utils.ValidateIdentifier(labelName) {
				return fmt.Errorf("WORD: invalid label identifier %q", labelName)
			}

			// Look up to expand local variable names, otherwise use as-is (forward reference)
			labelRef := labelName
			if sym := ctx.SymbolTable.Lookup(labelName, ctx.CurrentScope()); sym != nil {
				labelRef = sym.FullName()
			}

			err = ctx.SymbolTable.AddLabel(varName, scope, labelRef, line)
			if err != nil {
				return fmt.Errorf("WORD: %w", err)
			}

			c.varName = varName
			c.value = 0 // label refs don't have numeric values
			return nil
		}

		// Not a string or label, evaluate as expression
		value, err = utils.EvaluateExpression(valueStr, constLookup)
		if err != nil {
			return fmt.Errorf("WORD: invalid value %q: %w", valueStr, err)
		}

		if operator == "=" {
			// WORD varname = value
			if value < 0 || value > 65535 {
				return fmt.Errorf("WORD: init value %d out of range (0-65535)", value)
			}
		err = ctx.SymbolTable.AddVar(varName, scope, compiler.KindWord, uint16(value), line)

		} else if operator == "@" {
			// WORD varname @ address
			if value < 0 || value > 0xFFFF {
				return fmt.Errorf("WORD: absolute address $%X out of range", value)
			}
			c.isAbs = true
			err = ctx.SymbolTable.AddAbsolute(varName, scope, compiler.KindWord, uint16(value), line)

		} else {
			return fmt.Errorf("WORD: expected '=' or '@', got %q", operator)
		}

	case 5:
		// WORD CONST varname = value
		constKeyword := strings.ToUpper(params[1])
		varName = params[2]
		operator := params[3]
		valueStr := params[4]

		if constKeyword != "CONST" {
			return fmt.Errorf("WORD: expected CONST keyword, got %q", params[1])
		}

		if operator != "=" {
			return fmt.Errorf("WORD: expected '=', got %q", operator)
		}

		if !utils.ValidateIdentifier(varName) {
			return fmt.Errorf("WORD: invalid identifier %q", varName)
		}

		value, err = utils.EvaluateExpression(valueStr, constLookup)
		if err != nil {
			return fmt.Errorf("WORD: invalid value %q: %w", valueStr, err)
		}

		if value < 0 || value > 65535 {
			return fmt.Errorf("WORD: const value %d out of range (0-65535)", value)
		}

		c.isConst = true
		err = ctx.SymbolTable.AddConst(varName, scope, compiler.KindWord, uint16(value), line)
	}

	if err != nil {
		return fmt.Errorf("WORD: %w", err)
	}

	c.varName = varName
	c.value = uint16(value)

	return nil
}

func (c *WordCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
	// Variables are rendered by assembleOutput, not by individual commands
	return nil, nil
}

func (c *WordCommand) GetClass() compiler.CmdClass { return compiler.CmdLinear }
func (c *WordCommand) GetName() string              { return "WORD" }
