package commands

import (
	"fmt"
	"strings"

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

// AddCommand handles ADD operations
// Syntax:
//
//	ADD <param1> TO <param2> GIVING <dest>      # old syntax with TO/GIVING
//	ADD <param1> + <param2> -> <dest>           # old syntax with +/->
//	<dest> = <param1> + <param2>                # new syntax
type AddCommand struct {
	param1VarName string
	param1VarKind compiler.VarKind
	param1Value   uint16
	param1IsVar   bool

	param2VarName string
	param2VarKind compiler.VarKind
	param2Value   uint16
	param2IsVar   bool

	destVarName string
	destVarKind compiler.VarKind
}

func (c *AddCommand) WillHandle(line preproc.Line) bool {
	params, err := utils.ParseParams(line.Text)
	if err != nil || len(params) == 0 {
		return false
	}

	// Old syntax: ADD ... (must have exactly 6 params)
	if strings.ToUpper(params[0]) == "ADD" && len(params) == 6 {
		return true
	}

	// New syntax: <dest> = <param1> + <param2>
	if len(params) == 5 && params[1] == "=" && params[3] == "+" {
		return true
	}

	return false
}

func (c *AddCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error {
	// Clear state
	c.param1VarName = ""
	c.param1IsVar = false
	c.param1Value = 0
	c.param2VarName = ""
	c.param2IsVar = false
	c.param2Value = 0
	c.destVarName = ""

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

	paramCount := len(params)
	scope := ctx.CurrentScope()

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

	// Determine syntax and parse accordingly
	if strings.ToUpper(params[0]) == "ADD" {
		// Old syntax: ADD <param1> TO/+ <param2> GIVING/-> <dest>
		if paramCount != 6 {
			return fmt.Errorf("ADD: wrong number of parameters (%d), expected 6", paramCount)
		}

		separator1 := strings.ToUpper(params[2])
		if separator1 != "TO" && separator1 != "+" {
			return fmt.Errorf("ADD: parameter #3 must be 'TO' or '+', got %q", params[2])
		}

		separator2 := strings.ToUpper(params[4])
		if separator2 != "GIVING" && separator2 != "->" {
			return fmt.Errorf("ADD: parameter #5 must be 'GIVING' or '->', got %q", params[4])
		}

		// Parse destination
		destName := params[5]
		destSym := ctx.SymbolTable.Lookup(destName, scope)
		if destSym == nil {
			return fmt.Errorf("ADD: unknown variable %q", destName)
		}
		if destSym.IsConst() {
			return fmt.Errorf("ADD: cannot assign to constant %q", destName)
		}
		c.destVarName = destSym.FullName()
		c.destVarKind = destSym.GetVarKind()

		// Parse param1
		var err error
		c.param1VarName, c.param1VarKind, c.param1Value, c.param1IsVar, err = compiler.ParseOperandParam(
			params[1], ctx.SymbolTable, scope, constLookup)
		if err != nil {
			return fmt.Errorf("ADD: param1: %w", err)
		}

		// Parse param2
		c.param2VarName, c.param2VarKind, c.param2Value, c.param2IsVar, err = compiler.ParseOperandParam(
			params[3], ctx.SymbolTable, scope, constLookup)
		if err != nil {
			return fmt.Errorf("ADD: param2: %w", err)
		}

	} else {
		// New syntax: <dest> = <param1> + <param2>
		if paramCount != 5 {
			return fmt.Errorf("ADD: wrong number of parameters (%d), expected 5", paramCount)
		}

		if params[1] != "=" {
			return fmt.Errorf("ADD: expected '=' at position 2, got %q", params[1])
		}

		if params[3] != "+" {
			return fmt.Errorf("ADD: expected '+' at position 4, got %q", params[3])
		}

		// Parse destination
		destName := params[0]
		destSym := ctx.SymbolTable.Lookup(destName, scope)
		if destSym == nil {
			return fmt.Errorf("ADD: unknown variable %q", destName)
		}
		if destSym.IsConst() {
			return fmt.Errorf("ADD: cannot assign to constant %q", destName)
		}
		c.destVarName = destSym.FullName()
		c.destVarKind = destSym.GetVarKind()

		// Parse param1
		var err error
		c.param1VarName, c.param1VarKind, c.param1Value, c.param1IsVar, err = compiler.ParseOperandParam(
			params[2], ctx.SymbolTable, scope, constLookup)
		if err != nil {
			return fmt.Errorf("ADD: param1: %w", err)
		}

		// Parse param2
		c.param2VarName, c.param2VarKind, c.param2Value, c.param2IsVar, err = compiler.ParseOperandParam(
			params[4], ctx.SymbolTable, scope, constLookup)
		if err != nil {
			return fmt.Errorf("ADD: param2: %w", err)
		}
	}

	return nil
}

func (c *AddCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
	var asm []string

	// If both params are literals, fold at compile time
	if !c.param1IsVar && !c.param2IsVar {
		sum := uint32(c.param1Value) + uint32(c.param2Value)
		lo := uint8(sum & 0xFF)
		hi := uint8((sum >> 8) & 0xFF)

		asm = append(asm, fmt.Sprintf("\tlda #$%02x", lo))
		asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))

		if c.destVarKind == compiler.KindWord {
			asm = append(asm, fmt.Sprintf("\tlda #$%02x", hi))
			asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
		}

		return asm, nil
	}

	// At least one param is a variable - generate add code
	asm = append(asm, "\tclc")

	// Load param1
	if c.param1IsVar {
		asm = append(asm, fmt.Sprintf("\tlda %s", c.param1VarName))
	} else {
		asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.param1Value&0xFF)))
	}

	// Add param2
	if c.param2IsVar {
		asm = append(asm, fmt.Sprintf("\tadc %s", c.param2VarName))
	} else {
		asm = append(asm, fmt.Sprintf("\tadc #$%02x", uint8(c.param2Value&0xFF)))
	}

	// Store low byte
	asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))

	// If destination is word, handle high byte
	if c.destVarKind == compiler.KindWord {
		// Load high byte of param1
		if c.param1IsVar {
			if c.param1VarKind == compiler.KindWord {
				asm = append(asm, fmt.Sprintf("\tlda %s+1", c.param1VarName))
			} else {
				asm = append(asm, "\tlda #0")
			}
		} else {
			hi := uint8((c.param1Value >> 8) & 0xFF)
			asm = append(asm, fmt.Sprintf("\tlda #$%02x", hi))
		}

		// Add high byte of param2
		if c.param2IsVar {
			if c.param2VarKind == compiler.KindWord {
				asm = append(asm, fmt.Sprintf("\tadc %s+1", c.param2VarName))
			} else {
				asm = append(asm, "\tadc #0")
			}
		} else {
			hi := uint8((c.param2Value >> 8) & 0xFF)
			asm = append(asm, fmt.Sprintf("\tadc #$%02x", hi))
		}

		// Store high byte
		asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
	}

	return asm, nil
}

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