package commands

import (
	"fmt"
	"strings"

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

// LetCommand handles LET/assignment operations
// Syntax:
//
//	LET <dest> GET <source>     # old syntax with GET
//	LET <dest> = <source>       # old syntax with =
//	<dest> = <source>           # new syntax
//
// Note: Differs from arithmetic ops by param count (3 vs 5)
type LetCommand struct {
	sourceVarName string
	sourceVarKind compiler.VarKind
	sourceValue   uint16
	sourceIsVar   bool

	destVarName string
	destVarKind compiler.VarKind
}

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

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

	// New syntax: <dest> = <source> (exactly 3 params)
	if len(params) == 3 && params[1] == "=" {
		return true
	}

	return false
}

func (c *LetCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error {
	// Clear state
	c.sourceVarName = ""
	c.sourceIsVar = false
	c.sourceValue = 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]) == "LET" {
		// Old syntax: LET <dest> GET/= <source>
		if paramCount != 4 {
			return fmt.Errorf("LET: wrong number of parameters (%d), expected 4", paramCount)
		}

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

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

		// Parse source
		c.sourceVarName, c.sourceVarKind, c.sourceValue, c.sourceIsVar, err = compiler.ParseOperandParam(
			params[3], ctx.SymbolTable, scope, constLookup)
		if err != nil {
			return fmt.Errorf("LET: source: %w", err)
		}

	} else {
		// New syntax: <dest> = <source>
		if paramCount != 3 {
			return fmt.Errorf("LET: wrong number of parameters (%d), expected 3", paramCount)
		}

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

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

		// Parse source
		c.sourceVarName, c.sourceVarKind, c.sourceValue, c.sourceIsVar, err = compiler.ParseOperandParam(
			params[2], ctx.SymbolTable, scope, constLookup)
		if err != nil {
			return fmt.Errorf("LET: source: %w", err)
		}
	}

	return nil
}

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

	// Variable assignment
	if c.sourceIsVar {
		// Destination: byte
		if c.destVarKind == compiler.KindByte {
			// byte → byte or word → byte (take low byte)
			asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName))
			asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
			return asm, nil
		}

		// Destination: word
		// byte → word (zero-extend)
		if c.sourceVarKind == compiler.KindByte {
			asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName))
			asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
			asm = append(asm, "\tlda #0")
			asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
			return asm, nil
		}

		// word → word (copy both bytes)
		asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName))
		asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
		asm = append(asm, fmt.Sprintf("\tlda %s+1", c.sourceVarName))
		asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
		return asm, nil
	}

	// Literal assignment
	lo := uint8(c.sourceValue & 0xFF)
	hi := uint8((c.sourceValue >> 8) & 0xFF)

	// Destination: byte
	if c.destVarKind == compiler.KindByte {
		asm = append(asm, fmt.Sprintf("\tlda #$%02x", lo))
		asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
		return asm, nil
	}

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

	// Optimization: don't reload if lo == hi (common for $0000, $FFFF)
	if lo != hi {
		asm = append(asm, fmt.Sprintf("\tlda #$%02x", hi))
	}
	asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))

	return asm, nil
}

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