package commands

import (
	"fmt"
	"strings"

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

// NextCommand handles NEXT statements
// Syntax: NEXT
// Increments loop variable and jumps back to loop start
type NextCommand struct {
	info *compiler.ForLoopInfo
}

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

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

	if len(params) != 1 {
		return fmt.Errorf("NEXT: expected 1 parameter, got %d", len(params))
	}

	// Pop FOR info
	info, err := ctx.ForStack.Pop()
	if err != nil {
		return fmt.Errorf("NEXT: not inside FOR loop")
	}
	c.info = info

	// Pop and validate labels
	loopLabel, err := ctx.LoopStartStack.Pop()
	if err != nil || loopLabel != info.LoopLabel {
		return fmt.Errorf("NEXT: loop stack mismatch")
	}

	skipLabel, err := ctx.LoopEndStack.Pop()
	if err != nil || skipLabel != info.SkipLabel {
		return fmt.Errorf("NEXT: loop stack mismatch")
	}

	return nil
}

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

	// Do-while style: check if var == end BEFORE incrementing.
	// Exit loop if var == end, otherwise continue to increment.
	asm = append(asm, c.generateEndCheck(ctx)...)

	// Generate increment
	asm = append(asm, c.generateIncrement(ctx)...)

	// Jump back to loop start
	asm = append(asm, fmt.Sprintf("\tjmp %s", c.info.LoopLabel))

	// Emit skip label
	asm = append(asm, c.info.SkipLabel)

	return asm, nil
}

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

// generateEndCheck generates code to exit if var == end.
func (c *NextCommand) generateEndCheck(ctx *compiler.CompilerContext) []string {
	var asm []string
	endOp := c.info.EndOperand

	if c.info.VarKind == compiler.KindByte {
		return c.generateByteEndCheck(ctx)
	}

	// WORD comparison
	if !endOp.IsVar {
		// Literal end value
		lo := uint8(endOp.Value & 0xFF)
		hi := uint8((endOp.Value >> 8) & 0xFF)

		if !c.info.UseLongJump {
			asm = append(asm, fmt.Sprintf("\tlda %s", c.info.VarName))
			asm = append(asm, fmt.Sprintf("\tcmp #$%02x", lo))
			asm = append(asm, fmt.Sprintf("\tbne +"))
			asm = append(asm, fmt.Sprintf("\tlda %s+1", c.info.VarName))
			asm = append(asm, fmt.Sprintf("\tcmp #$%02x", hi))
			asm = append(asm, fmt.Sprintf("\tbeq %s", c.info.SkipLabel))
			asm = append(asm, "+")
		} else {
			continueLabel := ctx.GeneralStack.Push()
			ctx.GeneralStack.Pop()
			asm = append(asm, fmt.Sprintf("\tlda %s", c.info.VarName))
			asm = append(asm, fmt.Sprintf("\tcmp #$%02x", lo))
			asm = append(asm, fmt.Sprintf("\tbne %s", continueLabel))
			asm = append(asm, fmt.Sprintf("\tlda %s+1", c.info.VarName))
			asm = append(asm, fmt.Sprintf("\tcmp #$%02x", hi))
			asm = append(asm, fmt.Sprintf("\tbne %s", continueLabel))
			asm = append(asm, fmt.Sprintf("\tjmp %s", c.info.SkipLabel))
			asm = append(asm, continueLabel)
		}
	} else {
		// Variable end value
		if !c.info.UseLongJump {
			asm = append(asm, fmt.Sprintf("\tlda %s", c.info.VarName))
			asm = append(asm, fmt.Sprintf("\tcmp %s", endOp.VarName))
			asm = append(asm, fmt.Sprintf("\tbne +"))
			if endOp.VarKind == compiler.KindWord {
				asm = append(asm, fmt.Sprintf("\tlda %s+1", c.info.VarName))
				asm = append(asm, fmt.Sprintf("\tcmp %s+1", endOp.VarName))
			} else {
				// BYTE end var compared to WORD loop var
				asm = append(asm, fmt.Sprintf("\tlda %s+1", c.info.VarName))
				asm = append(asm, "\tcmp #$00")
			}
			asm = append(asm, fmt.Sprintf("\tbeq %s", c.info.SkipLabel))
			asm = append(asm, "+")
		} else {
			continueLabel := ctx.GeneralStack.Push()
			ctx.GeneralStack.Pop()
			asm = append(asm, fmt.Sprintf("\tlda %s", c.info.VarName))
			asm = append(asm, fmt.Sprintf("\tcmp %s", endOp.VarName))
			asm = append(asm, fmt.Sprintf("\tbne %s", continueLabel))
			if endOp.VarKind == compiler.KindWord {
				asm = append(asm, fmt.Sprintf("\tlda %s+1", c.info.VarName))
				asm = append(asm, fmt.Sprintf("\tcmp %s+1", endOp.VarName))
			} else {
				asm = append(asm, fmt.Sprintf("\tlda %s+1", c.info.VarName))
				asm = append(asm, "\tcmp #$00")
			}
			asm = append(asm, fmt.Sprintf("\tbne %s", continueLabel))
			asm = append(asm, fmt.Sprintf("\tjmp %s", c.info.SkipLabel))
			asm = append(asm, continueLabel)
		}
	}

	return asm
}

// generateByteEndCheck generates end check for BYTE loop variable.
func (c *NextCommand) generateByteEndCheck(ctx *compiler.CompilerContext) []string {
	var asm []string
	endOp := c.info.EndOperand

	asm = append(asm, fmt.Sprintf("\tlda %s", c.info.VarName))

	if !endOp.IsVar {
		asm = append(asm, fmt.Sprintf("\tcmp #$%02x", uint8(endOp.Value)))
	} else {
		asm = append(asm, fmt.Sprintf("\tcmp %s", endOp.VarName))
	}

	if !c.info.UseLongJump {
		asm = append(asm, fmt.Sprintf("\tbeq %s", c.info.SkipLabel))
	} else {
		continueLabel := ctx.GeneralStack.Push()
		ctx.GeneralStack.Pop()
		asm = append(asm, fmt.Sprintf("\tbne %s", continueLabel))
		asm = append(asm, fmt.Sprintf("\tjmp %s", c.info.SkipLabel))
		asm = append(asm, continueLabel)
	}

	return asm
}

func (c *NextCommand) generateIncrement(ctx *compiler.CompilerContext) []string {
	var asm []string

	if c.info.VarKind == compiler.KindByte {
		asm = append(asm, fmt.Sprintf("\tinc %s", c.info.VarName))
		return asm
	}

	// Word variable - handle carry to high byte
	label := ctx.GeneralStack.Push()
	asm = append(asm, fmt.Sprintf("\tinc %s", c.info.VarName))
	asm = append(asm, fmt.Sprintf("\tbne %s", label))
	asm = append(asm, fmt.Sprintf("\tinc %s+1", c.info.VarName))
	asm = append(asm, label)

	return asm
}
