package commands

import (
	"fmt"
	"os"
	"strings"

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

// ForCommand handles FOR loop statements
// Syntax: FOR <var> = <start> TO <end>
type ForCommand struct {
	varName     string
	varKind     compiler.VarKind
	startOp     *compiler.OperandInfo
	endOp       *compiler.OperandInfo
	useLongJump bool
	loopLabel   string
	skipLabel   string
}

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

func (c *ForCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error {
	params, err := utils.ParseParams(line.Text)
	if err != nil {
		return err
	}

	// FOR <var> = <start> TO <end>
	// Exactly 6 params (FOR var = start TO end)
	if len(params) != 6 {
		return fmt.Errorf("FOR: expected 5 parameters (FOR var = start TO end), got %d", len(params)-1)
	}

	// Check '=' separator
	if params[2] != "=" {
		return fmt.Errorf("FOR: expected '=' at position 3, got %q", params[2])
	}

	scope := ctx.CurrentScope()
	constLookup := ctx.SymbolTable.ConstantLookupFunc(scope)

	// Parse variable
	varName := params[1]
	varSym := ctx.SymbolTable.Lookup(varName, scope)
	if varSym == nil {
		return fmt.Errorf("FOR: unknown variable %q", varName)
	}
	if varSym.IsConst() {
		return fmt.Errorf("FOR: cannot use constant %q as loop variable", varName)
	}
	c.varName = varSym.FullName()
	c.varKind = varSym.GetVarKind()

	// Parse start value
	var parseErr error
	startVarName, startVarKind, startValue, startIsVar, parseErr := compiler.ParseOperandParam(
		params[3], ctx.SymbolTable, scope, constLookup)
	if parseErr != nil {
		return fmt.Errorf("FOR: start value: %w", parseErr)
	}
	c.startOp = &compiler.OperandInfo{
		VarName: startVarName,
		VarKind: startVarKind,
		Value:   startValue,
		IsVar:   startIsVar,
	}

	// Parse direction (TO only)
	direction := strings.ToUpper(params[4])
	if direction != "TO" {
		return fmt.Errorf("FOR: expected 'TO' at position 5, got %q (DOWNTO is not supported)", params[4])
	}

	// Parse end value
	endVarName, endVarKind, endValue, endIsVar, parseErr := compiler.ParseOperandParam(
		params[5], ctx.SymbolTable, scope, constLookup)
	if parseErr != nil {
		return fmt.Errorf("FOR: end value: %w", parseErr)
	}
	c.endOp = &compiler.OperandInfo{
		VarName: endVarName,
		VarKind: endVarKind,
		Value:   endValue,
		IsVar:   endIsVar,
	}

	if c.varKind == compiler.KindByte {
		// Error on literal out of range
		if !c.startOp.IsVar && c.startOp.Value > 255 {
			return fmt.Errorf("FOR: BYTE variable cannot start at literal %d (max 255)", c.startOp.Value)
		}
		if !c.endOp.IsVar && c.endOp.Value > 255 {
			return fmt.Errorf("FOR: BYTE variable cannot loop to literal %d (max 255)", c.endOp.Value)
		}

		// Warn on variable type mismatch
		if c.startOp.IsVar && c.startOp.VarKind == compiler.KindWord {
			_, _ = fmt.Fprintf(os.Stderr, "%s:%d: warning: BYTE loop variable with WORD start value truncates to low byte\n",
				line.Filename, line.LineNo)
		}
		if c.endOp.IsVar && c.endOp.VarKind == compiler.KindWord {
			_, _ = fmt.Fprintf(os.Stderr, "%s:%d: warning: BYTE loop variable with WORD end value may cause infinite loop\n",
				line.Filename, line.LineNo)
		}
	}

	// Check pragma
	ps := ctx.Pragma.GetPragmaSetByIndex(line.PragmaSetIndex)
	longJumpPragma := ps.GetPragma("_P_USE_LONG_JUMP")
	c.useLongJump = longJumpPragma != "" && longJumpPragma != "0"

	// Create labels
	c.loopLabel = ctx.LoopStartStack.Push()
	c.skipLabel = ctx.LoopEndStack.Push()

	// Push FOR info to ForStack
	ctx.ForStack.Push(&compiler.ForLoopInfo{
		VarName:     c.varName,
		VarKind:     c.varKind,
		EndOperand:  c.endOp,
		LoopLabel:   c.loopLabel,
		SkipLabel:   c.skipLabel,
		UseLongJump: c.useLongJump,
	})

	return nil
}

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

	// Initial assignment: var = start
	assignAsm := c.generateAssignment()
	asm = append(asm, assignAsm...)

	// Initial guard: skip loop if start > end (using start <= end, jump on FALSE)
	startOp := &operandInfo{
		varName: c.startOp.VarName,
		varKind: c.startOp.VarKind,
		value:   c.startOp.Value,
		isVar:   c.startOp.IsVar,
	}

	endOp := &operandInfo{
		varName: c.endOp.VarName,
		varKind: c.endOp.VarKind,
		value:   c.endOp.Value,
		isVar:   c.endOp.IsVar,
	}

	gen, err := newComparisonGenerator(
		opLessEqual,
		startOp,
		endOp,
		c.useLongJump,
		ctx.LoopEndStack,
		ctx.GeneralStack,
	)
	if err != nil {
		return nil, fmt.Errorf("FOR: %w", err)
	}

	guardAsm, err := gen.generate()
	if err != nil {
		return nil, fmt.Errorf("FOR: %w", err)
	}

	asm = append(asm, guardAsm...)

	// Emit loop label (body starts here)
	asm = append(asm, c.loopLabel)

	return asm, nil
}

func (c *ForCommand) generateAssignment() []string {
	var asm []string

	// If startOp is also the iterator (varName) no need to load it.
	if c.startOp.IsVar && c.startOp.VarName == c.varName {
		return asm
	}

	// Variable assignment from startOp
	if c.startOp.IsVar {
		// Destination: byte
		if c.varKind == compiler.KindByte {
			// byte â†’ byte or word â†’ byte (take low byte)
			asm = append(asm, fmt.Sprintf("\tlda %s", c.startOp.VarName))
			asm = append(asm, fmt.Sprintf("\tsta %s", c.varName))
			return asm
		}

		// Destination: word
		// byte â†’ word (zero-extend)
		if c.startOp.VarKind == compiler.KindByte {
			asm = append(asm, fmt.Sprintf("\tlda %s", c.startOp.VarName))
			asm = append(asm, fmt.Sprintf("\tsta %s", c.varName))
			asm = append(asm, "\tlda #0")
			asm = append(asm, fmt.Sprintf("\tsta %s+1", c.varName))
			return asm
		}

		// word â†’ word (copy both bytes)
		asm = append(asm, fmt.Sprintf("\tlda %s", c.startOp.VarName))
		asm = append(asm, fmt.Sprintf("\tsta %s", c.varName))
		asm = append(asm, fmt.Sprintf("\tlda %s+1", c.startOp.VarName))
		asm = append(asm, fmt.Sprintf("\tsta %s+1", c.varName))
		return asm
	}

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

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

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

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

	return asm
}

func (c *ForCommand) GetClass() compiler.CmdClass { return compiler.CmdFlowCtrl }
func (c *ForCommand) GetName() string              { return "FOR" }
