package commands

import (
	"fmt"
	"strings"

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

// XorCommand handles bitwise XOR operations
// Syntax:
//
//	XOR <param1> WITH <param2> GIVING <dest>    # old syntax with WITH/GIVING
//	XOR <param1> WITH <param2> -> <dest>        # old syntax with WITH/->
//	<dest> = <param1> ^ <param2>                # new syntax
type XorCommand 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 *XorCommand) WillHandle(line preproc.Line) bool {
	params, err := utils.ParseParams(line.Text)
	if err != nil || len(params) == 0 {
		return false
	}

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

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

	return false
}

func (c *XorCommand) 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]) == "XOR" {
		// Old syntax: XOR <param1> WITH <param2> GIVING/-> <dest>
		if paramCount != 6 {
			return fmt.Errorf("XOR: wrong number of parameters (%d), expected 6", paramCount)
		}

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

		separator2 := strings.ToUpper(params[4])
		if separator2 != "GIVING" && separator2 != "->" {
			return fmt.Errorf("XOR: 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("XOR: unknown variable %q", destName)
		}
		if destSym.IsConst() {
			return fmt.Errorf("XOR: 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("XOR: 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("XOR: param2: %w", err)
		}

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

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

		if params[3] != "^" {
			return fmt.Errorf("XOR: 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("XOR: unknown variable %q", destName)
		}
		if destSym.IsConst() {
			return fmt.Errorf("XOR: 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("XOR: 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("XOR: param2: %w", err)
		}
	}

	// Normalize operands: leverage XOR commutativity to swap if needed
	// This ensures word operands are in param1 when mixed with bytes
	// Makes code generation simpler and more consistent
	param1IsByteSized := false
	if c.param1IsVar {
		param1IsByteSized = (c.param1VarKind == compiler.KindByte)
	} else {
		param1IsByteSized = ((c.param1Value >> 8) & 0xFF) == 0
	}

	param2IsWord := c.param2IsVar && c.param2VarKind == compiler.KindWord

	if param1IsByteSized && param2IsWord {
		// Swap param1 and param2
		c.param1VarName, c.param2VarName = c.param2VarName, c.param1VarName
		c.param1VarKind, c.param2VarKind = c.param2VarKind, c.param1VarKind
		c.param1Value, c.param2Value = c.param2Value, c.param1Value
		c.param1IsVar, c.param2IsVar = c.param2IsVar, c.param1IsVar
	}

	return nil
}

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

	// If both params are literals, fold at compile time
	if !c.param1IsVar && !c.param2IsVar {
		result := c.param1Value ^ c.param2Value
		lo := uint8(result & 0xFF)
		hi := uint8((result >> 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 XOR code

	// Same variable on both sides: a ^ a = 0
	if c.param1IsVar && c.param2IsVar && c.param1VarName == c.param2VarName {
		asm = append(asm, "\tlda #0")
		asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
		if c.destVarKind == compiler.KindWord {
			asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
		}
		return asm, nil
	}

	// If param1 is literal 0, just load param2 directly (0 XOR a = a)
	if !c.param1IsVar && uint8(c.param1Value&0xFF) == 0 && c.param2IsVar {
		asm = append(asm, fmt.Sprintf("\tlda %s", c.param2VarName))
	} else {
		// 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)))
		}

		// XOR with param2 (skip if literal 0, as eor #0 is a no-op)
		if c.param2IsVar {
			asm = append(asm, fmt.Sprintf("\teor %s", c.param2VarName))
		} else if uint8(c.param2Value&0xFF) != 0 {
			asm = append(asm, fmt.Sprintf("\teor #$%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 {
		// Optimization: skip high byte for self-assignment when param2 is byte-sized
		// e.g., word_var = word_var ^ byte_var would just copy high byte to itself
		if c.destVarName == c.param1VarName {
			param2IsByteSized := false
			if c.param2IsVar {
				param2IsByteSized = (c.param2VarKind == compiler.KindByte)
			} else {
				param2IsByteSized = ((c.param2Value >> 8) & 0xFF) == 0
			}

			if param2IsByteSized {
				// High byte would be: dest+1 = dest+1 ^ 0 (complete no-op)
				return asm, nil
			}
		}

		// 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))
		}

		// XOR with high byte of param2 (skip if param2 high byte is 0, as eor #0 is a no-op)
		if c.param2IsVar {
			if c.param2VarKind == compiler.KindWord {
				asm = append(asm, fmt.Sprintf("\teor %s+1", c.param2VarName))
			}
			// Skip eor when param2 is byte var (high byte is 0)
		} else {
			hi := uint8((c.param2Value >> 8) & 0xFF)
			if hi != 0 {
				asm = append(asm, fmt.Sprintf("\teor #$%02x", hi))
			}
			// Skip eor when param2 const high byte is 0
		}

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

	return asm, nil
}

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