package commands

import (
	"fmt"
	"strings"

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

// GosubCommand handles GOSUB statements (subroutine call with return)
// Syntax:
//
//	GOSUB <target>
//	GOSUB <target> PASSING <var1> AS ACC|XREG|YREG [<var2> AS XREG|YREG [<var3> AS YREG]]
//
// Where target is:
//   - Label name
//   - Variable (byte or word) - uses self-modifying code
//   - Expression/address
type GosubCommand struct {
	targetName    string
	targetKind    compiler.VarKind
	targetAddress uint16
	isVar         bool
	isLabel       bool

	// Register passing
	accVarName  string
	accVarKind  compiler.VarKind
	xregVarName string
	xregVarKind compiler.VarKind
	yregVarName string
	yregVarKind compiler.VarKind

	pragmaSet preproc.PragmaSet
}

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

func (c *GosubCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error {
	// Clear state
	c.targetName = ""
	c.targetAddress = 0
	c.isVar = false
	c.isLabel = false
	c.accVarName = ""
	c.xregVarName = ""
	c.yregVarName = ""

	// Store pragma set
	c.pragmaSet = ctx.Pragma.GetPragmaSetByIndex(line.PragmaSetIndex)

	params, err := utils.ParseParams(line.Text)
	if err != nil {
		return err
	}

	paramCount := len(params)
	if paramCount != 2 && paramCount != 6 && paramCount != 9 && paramCount != 12 {
		return fmt.Errorf("GOSUB: expected 2, 6, 9, or 12 parameters, got %d", paramCount)
	}

	scope := ctx.CurrentScope()

	constLookup := ctx.SymbolTable.ConstantLookupFunc(scope)

	// Parse target (param 2)
	targetParam := params[1]

	// Try ParseOperandParam - handles variables, constants, expressions
	varName, varKind, value, isVar, err := compiler.ParseOperandParam(
		targetParam, ctx.SymbolTable, scope, constLookup)

	if err != nil {
		// Not a variable or expression -> treat as label
		c.targetName = targetParam
		c.isLabel = true
	} else if isVar {
		// It's a variable
		c.targetName = varName
		c.targetKind = varKind
		c.isVar = true
	} else {
		// It's an expression/constant
		c.targetAddress = value
		c.isVar = false
		c.isLabel = false
	}

	// Parse PASSING clause if present
	if paramCount > 2 {
		if strings.ToUpper(params[2]) != "PASSING" {
			return fmt.Errorf("GOSUB: parameter #3 must be 'PASSING', got %q", params[2])
		}

		// First register (params 4-6)
		if err := c.parseRegisterParam(params[3], params[4], params[5], ctx, scope); err != nil {
			return err
		}

		// Second register (params 7-9)
		if paramCount >= 9 {
			if err := c.parseRegisterParam(params[6], params[7], params[8], ctx, scope); err != nil {
				return err
			}
		}

		// Third register (params 10-12)
		if paramCount == 12 {
			if err := c.parseRegisterParam(params[9], params[10], params[11], ctx, scope); err != nil {
				return err
			}
		}
	}

	return nil
}

func (c *GosubCommand) parseRegisterParam(varName, asWord, regName string, ctx *compiler.CompilerContext, scope []string) error {
	if strings.ToUpper(asWord) != "AS" {
		return fmt.Errorf("GOSUB: expected 'AS', got %q", asWord)
	}

	reg := strings.ToUpper(regName)
	if reg != "ACC" && reg != "XREG" && reg != "YREG" {
		return fmt.Errorf("GOSUB: register must be ACC, XREG, or YREG, got %q", regName)
	}

	// Look up variable
	sym := ctx.SymbolTable.Lookup(varName, scope)
	if sym == nil {
		return fmt.Errorf("GOSUB: unknown variable %q for register %s", varName, reg)
	}

	fullName := sym.FullName()
	kind := sym.GetVarKind()

	// Assign to appropriate register
	switch reg {
	case "ACC":
		if c.accVarName != "" {
			return fmt.Errorf("GOSUB: ACC already assigned")
		}
		c.accVarName = fullName
		c.accVarKind = kind
	case "XREG":
		if c.xregVarName != "" {
			return fmt.Errorf("GOSUB: XREG already assigned")
		}
		c.xregVarName = fullName
		c.xregVarKind = kind
	case "YREG":
		if c.yregVarName != "" {
			return fmt.Errorf("GOSUB: YREG already assigned")
		}
		c.yregVarName = fullName
		c.yregVarKind = kind
	}

	return nil
}

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

	// Check for immutable code pragma when using variable target
	if c.isVar {
		if c.pragmaSet.GetPragma("_P_USE_IMMUTABLE_CODE") != "" && c.pragmaSet.GetPragma("_P_USE_IMMUTABLE_CODE") != "0" {
			return nil, fmt.Errorf("GOSUB: USE_IMMUTABLE_CODE pragma set but variable target requires self-modifying code")
		}
	}

	// Generate based on target type
	if c.isVar {
		// Variable target - self-modifying code
		label := ctx.GeneralStack.Push()

		if c.targetKind == compiler.KindByte {
			// Byte variable
			asm = append(asm, fmt.Sprintf("\tlda %s", c.targetName))
			asm = append(asm, fmt.Sprintf("\tsta %s+1", label))
			asm = append(asm, label)
		} else {
			// Word variable
			asm = append(asm, fmt.Sprintf("\tlda %s", c.targetName))
			asm = append(asm, fmt.Sprintf("\tsta %s+1", label))
			asm = append(asm, fmt.Sprintf("\tlda %s+1", c.targetName))
			asm = append(asm, fmt.Sprintf("\tsta %s+2", label))
			asm = append(asm, label)
		}

		// Pre-passing: load variables into registers
		asm = append(asm, c.generatePrePassing()...)

		// JSR with placeholder address
		asm = append(asm, "\tjsr $0000")

		// Post-passing: store registers back to variables
		asm = append(asm, c.generatePostPassing()...)

	} else if c.isLabel {
		// Label target
		asm = append(asm, c.generatePrePassing()...)
		asm = append(asm, fmt.Sprintf("\tjsr %s", c.targetName))
		asm = append(asm, c.generatePostPassing()...)

	} else {
		// Address target
		asm = append(asm, c.generatePrePassing()...)
		asm = append(asm, fmt.Sprintf("\tjsr %d", c.targetAddress))
		asm = append(asm, c.generatePostPassing()...)
	}

	return asm, nil
}

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

func (c *GosubCommand) generatePrePassing() []string {
	var asm []string

	if c.accVarName != "" {
		asm = append(asm, fmt.Sprintf("\tlda %s", c.accVarName))
	}
	if c.xregVarName != "" {
		asm = append(asm, fmt.Sprintf("\tldx %s", c.xregVarName))
	}
	if c.yregVarName != "" {
		asm = append(asm, fmt.Sprintf("\tldy %s", c.yregVarName))
	}

	return asm
}

func (c *GosubCommand) generatePostPassing() []string {
	var asm []string

	if c.accVarName != "" {
		asm = append(asm, fmt.Sprintf("\tsta %s", c.accVarName))
		// Clear high byte for word destination
		if c.accVarKind == compiler.KindWord {
			asm = append(asm, "\tlda #0")
			asm = append(asm, fmt.Sprintf("\tsta %s+1", c.accVarName))
		}
	}
	if c.xregVarName != "" {
		asm = append(asm, fmt.Sprintf("\tstx %s", c.xregVarName))
		// Clear high byte for word destination
		if c.xregVarKind == compiler.KindWord {
			asm = append(asm, "\tldx #0")
			asm = append(asm, fmt.Sprintf("\tstx %s+1", c.xregVarName))
		}
	}
	if c.yregVarName != "" {
		asm = append(asm, fmt.Sprintf("\tsty %s", c.yregVarName))
		// Clear high byte for word destination
		if c.yregVarKind == compiler.KindWord {
			asm = append(asm, "\tldy #0")
			asm = append(asm, fmt.Sprintf("\tsty %s+1", c.yregVarName))
		}
	}

	return asm
}
