package compiler

import (
	"c65gm/internal/utils"
	"fmt"

	"c65gm/internal/preproc"
)

// CmdClass categorizes commands for the peephole optimizer
type CmdClass int

const (
	CmdLinear   CmdClass = iota // straight-line code, no visible labels
	CmdFlowCtrl                 // IF/WHILE/FOR/SWITCH: internal labels, bodies still optimizable
	CmdBoundary                 // FUNC/FEND/LABEL/MACRO: reset all optimizer state
)

// Command represents a script command that can interpret source lines and generate assembly
type Command interface {
	// WillHandle checks if this command can handle the given line
	// Should clear any internal state and return true if it handles this line
	WillHandle(line preproc.Line) bool

	// Interpret parses and validates the line, storing state in the command
	// Returns error if line is malformed or invalid
	Interpret(line preproc.Line, ctx *CompilerContext) error

	// Generate produces assembly output based on previously interpreted line
	// Returns assembly lines and error if generation fails
	Generate(ctx *CompilerContext) ([]string, error)

	// GetClass returns the command's optimizer class
	GetClass() CmdClass

	// GetName returns a short command name for @@OPT annotations (e.g. "LET", "ADD", "POKE")
	GetName() string
}

// CommandRegistry manages registered commands and dispatches lines to appropriate handlers
type CommandRegistry struct {
	commands []Command
}

// NewCommandRegistry creates a new command registry
func NewCommandRegistry() *CommandRegistry {
	return &CommandRegistry{
		commands: make([]Command, 0),
	}
}

// Register adds a command to the registry
func (r *CommandRegistry) Register(cmd Command) {
	r.commands = append(r.commands, cmd)
}

// FindHandler finds the first command that will handle the given line
// Returns the command and true if found, nil and false otherwise
func (r *CommandRegistry) FindHandler(line preproc.Line) (Command, bool) {
	for _, cmd := range r.commands {
		if cmd.WillHandle(line) {
			return cmd, true
		}
	}
	return nil, false
}

// UnhandledLineError represents an error when no command handles a source line
type UnhandledLineError struct {
	Line preproc.Line
}

func (e *UnhandledLineError) Error() string {
	return fmt.Sprintf("%s:%d: unhandled line: %s", e.Line.Filename, e.Line.LineNo, e.Line.Text)
}

// ParseOperandParam parses a command parameter that can be a variable or expression
// Returns: varName, varKind, value, isVar, error
func ParseOperandParam(
	param string,
	symTable *SymbolTable,
	scope []string,
	constLookup utils.ConstantLookup,
) (varName string, varKind VarKind, value uint16, isVar bool, err error) {
	// Try variable lookup first
	sym := symTable.Lookup(param, scope)
	if sym != nil {
		varKind = sym.GetVarKind()
		value = sym.Value

		// Constants are treated as literals (inlined), not variables
		if sym.IsConst() {
			varName = sym.FullName() // Preserve name for documentation
			isVar = false
			return
		}

		// It's a variable
		varName = sym.FullName()
		isVar = true
		return
	}

	// Not a variable, must be an expression
	val, evalErr := utils.EvaluateExpression(param, constLookup)
	if evalErr != nil {
		err = fmt.Errorf("not a valid variable or expression: %w", evalErr)
		return
	}

	if val < 0 || val > 65535 {
		err = fmt.Errorf("value %d out of range (0-65535)", val)
		return
	}

	value = uint16(val)
	isVar = false
	return
}
