package commands

import (
	"fmt"
	"strings"

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

// PokeWCommand handles POKEW statements (writes a word/2 bytes to memory)
// Syntax: POKEW <address>[offset] WITH|, <value>
// Where address is:
//   - Byte variable (zero-page indexed addressing)
//   - Word variable (self-modifying code or ZP pointer with offset)
//   - Expression (direct addressing)
//
// Value must be a word variable or word literal (0-65535)
type PokeWCommand struct {
	addrVarName string
	addrVarKind compiler.VarKind
	addrValue   uint16
	isAddrVar   bool
	isZPPointer bool

	offsetVarName string
	offsetValue   uint8
	hasOffset     bool
	isOffsetVar   bool

	valueVarName string
	valueLiteral uint16
	isValueVar   bool

	pragmaSet preproc.PragmaSet
}

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

func (c *PokeWCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error {
	// Clear state
	c.addrVarName = ""
	c.addrValue = 0
	c.isAddrVar = false
	c.isZPPointer = false
	c.offsetVarName = ""
	c.offsetValue = 0
	c.hasOffset = false
	c.isOffsetVar = false
	c.valueVarName = ""
	c.valueLiteral = 0
	c.isValueVar = false

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

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

	if len(params) != 4 {
		return fmt.Errorf("POKEW: expected 4 parameters, got %d", len(params))
	}

	scope := ctx.CurrentScope()

	// Validate separator (param 3)
	sep := strings.ToUpper(params[2])
	if sep != "WITH" && sep != "," {
		return fmt.Errorf("POKEW: parameter #3 must be 'WITH' or ',', got %q", params[2])
	}

	// Parse address (param 2) - may have [offset]
	addrParam := params[1]
	baseAddr, offsetParam := parsePOKEWOffset(addrParam)

	constLookup := ctx.SymbolTable.ConstantLookupFunc(scope)

	// Try to parse base address
	varName, varKind, value, isVar, err := compiler.ParseOperandParam(
		baseAddr, ctx.SymbolTable, scope, constLookup)

	if err != nil {
		return fmt.Errorf("POKEW: invalid address: %w", err)
	}

	if isVar {
		// It's a variable
		c.addrVarName = varName
		c.addrVarKind = varKind
		c.isAddrVar = true

		// Check if it's a ZP pointer
		addrSym := ctx.SymbolTable.Lookup(baseAddr, scope)
		if addrSym != nil {
			c.isZPPointer = addrSym.IsZeroPagePointer()
		}
	} else {
		// It's an expression/constant
		c.addrValue = value
		c.isAddrVar = false
	}

	// Parse offset if present
	if offsetParam != "" {
		// Offset only allowed with ZP pointers
		if !c.isZPPointer {
			return fmt.Errorf("POKEW: offset [%s] only allowed with zero-page word pointer", offsetParam)
		}

		c.hasOffset = true

		// Try to parse offset as variable or expression
		offsetVarName, _, offsetValue, isOffsetVar, err := compiler.ParseOperandParam(
			offsetParam, ctx.SymbolTable, scope, constLookup)

		if err != nil {
			return fmt.Errorf("POKEW: invalid offset: %w", err)
		}

		if isOffsetVar {
			// Check it's a byte variable
			offsetSym := ctx.SymbolTable.Lookup(offsetParam, scope)
			if offsetSym != nil && !offsetSym.IsByte() {
				return fmt.Errorf("POKEW: offset variable %q must be byte type", offsetParam)
			}
			c.offsetVarName = offsetVarName
			c.isOffsetVar = true
		} else {
			// Numeric offset - must fit in byte
			if offsetValue > 255 {
				return fmt.Errorf("POKEW: offset value %d out of byte range", offsetValue)
			}
			c.offsetValue = uint8(offsetValue)
			c.isOffsetVar = false
		}
	}

	// Parse value (param 4)
	valueParam := params[3]

	// Try to parse as variable or expression
	valVarName, valVarKind, valLiteral, isValVar, err := compiler.ParseOperandParam(
		valueParam, ctx.SymbolTable, scope, constLookup)

	if err != nil {
		return fmt.Errorf("POKEW: invalid value: %w", err)
	}

	if isValVar {
		// It's a variable - must be word
		if valVarKind == compiler.KindByte {
			return fmt.Errorf("POKEW: cannot use byte variable %q (use word variable or literal)", valueParam)
		}
		c.valueVarName = valVarName
		c.isValueVar = true
	} else {
		// It's a literal word value
		c.valueLiteral = valLiteral
		c.isValueVar = false
	}

	// Check for self-referential POKEW (writing pointer through itself)
	if c.isZPPointer && c.isValueVar && c.addrVarName == c.valueVarName {
		return fmt.Errorf("POKEW: writing pointer %q through itself - pointer is both address and value", c.valueVarName)
	}
	return nil
}

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

	// Check for immutable code pragma when using self-modifying code
	if c.isAddrVar && c.addrVarKind == compiler.KindWord && !c.isZPPointer {
		if c.pragmaSet.GetPragma("_P_USE_IMMUTABLE_CODE") != "" && c.pragmaSet.GetPragma("_P_USE_IMMUTABLE_CODE") != "0" {
			return nil, fmt.Errorf("POKEW: USE_IMMUTABLE_CODE pragma set but construct requires self-modifying code (consider using zero page word for address)")
		}
	}

	// Case 1: ZP pointer with offset
	if c.isZPPointer {
		// Load offset into Y
		if c.hasOffset {
			if c.isOffsetVar {
				asm = append(asm, fmt.Sprintf("\tldy %s", c.offsetVarName))
			} else {
				asm = append(asm, fmt.Sprintf("\tldy #%d", c.offsetValue))
			}
		} else {
			asm = append(asm, "\tldy #0")
		}

		// Store low byte
		if c.isValueVar {
			asm = append(asm, fmt.Sprintf("\tlda %s", c.valueVarName))
		} else {
			asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.valueLiteral&0xFF)))
		}
		asm = append(asm, fmt.Sprintf("\tsta (%s),y", c.addrVarName))

		// Increment Y and store high byte
		asm = append(asm, "\tiny")
		if c.isValueVar {
			asm = append(asm, fmt.Sprintf("\tlda %s+1", c.valueVarName))
		} else {
			asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8((c.valueLiteral>>8)&0xFF)))
		}
		asm = append(asm, fmt.Sprintf("\tsta (%s),y", c.addrVarName))
		return asm, nil
	}

	// Case 2: Byte variable (zero-page indexed addressing)
	if c.isAddrVar && c.addrVarKind == compiler.KindByte {
		asm = append(asm, fmt.Sprintf("\tldx %s", c.addrVarName))

		// Store low byte
		if c.isValueVar {
			asm = append(asm, fmt.Sprintf("\tlda %s", c.valueVarName))
		} else {
			asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.valueLiteral&0xFF)))
		}
		asm = append(asm, "\tsta $00,x")

		// Increment X and store high byte
		asm = append(asm, "\tinx")
		if c.isValueVar {
			asm = append(asm, fmt.Sprintf("\tlda %s+1", c.valueVarName))
		} else {
			asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8((c.valueLiteral>>8)&0xFF)))
		}
		asm = append(asm, "\tsta $00,x")
		return asm, nil
	}

	// Case 3: Word variable (self-modifying code)
	if c.isAddrVar && c.addrVarKind == compiler.KindWord {
		label1 := ctx.GeneralStack.Push()
		label2 := ctx.GeneralStack.Push()

		// Setup both labels with base address
		asm = append(asm, fmt.Sprintf("\tlda %s", c.addrVarName))
		asm = append(asm, fmt.Sprintf("\tsta %s+1", label1))
		asm = append(asm, fmt.Sprintf("\tsta %s+1", label2))
		asm = append(asm, fmt.Sprintf("\tlda %s+1", c.addrVarName))
		asm = append(asm, fmt.Sprintf("\tsta %s+2", label1))
		asm = append(asm, fmt.Sprintf("\tsta %s+2", label2))

		// Store low byte
		if c.isValueVar {
			asm = append(asm, fmt.Sprintf("\tlda %s", c.valueVarName))
		} else {
			asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.valueLiteral&0xFF)))
		}
		asm = append(asm, label1)
		asm = append(asm, "\tsta $ffff")

		// Increment address in label2
		asm = append(asm, fmt.Sprintf("\tinc %s+1", label2))
		asm = append(asm, fmt.Sprintf("\tbne %s", label2))
		asm = append(asm, fmt.Sprintf("\tinc %s+2", label2))

		// Store high byte
		asm = append(asm, label2)
		if c.isValueVar {
			asm = append(asm, fmt.Sprintf("\tlda %s+1", c.valueVarName))
		} else {
			asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8((c.valueLiteral>>8)&0xFF)))
		}
		asm = append(asm, "\tsta $ffff")
		return asm, nil
	}

	// Case 4: Expression/constant address (direct addressing)
	if c.isValueVar {
		asm = append(asm, fmt.Sprintf("\tlda %s", c.valueVarName))
		asm = append(asm, fmt.Sprintf("\tsta %d", c.addrValue))
		asm = append(asm, fmt.Sprintf("\tlda %s+1", c.valueVarName))
		asm = append(asm, fmt.Sprintf("\tsta %d", c.addrValue+1))
	} else {
		asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.valueLiteral&0xFF)))
		asm = append(asm, fmt.Sprintf("\tsta %d", c.addrValue))
		asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8((c.valueLiteral>>8)&0xFF)))
		asm = append(asm, fmt.Sprintf("\tsta %d", c.addrValue+1))
	}

	return asm, nil
}

func (c *PokeWCommand) GetClass() compiler.CmdClass { return compiler.CmdLinear }
func (c *PokeWCommand) GetName() string {
	if c.isZPPointer {
		return "POKEW:ZP"
	}
	if c.isAddrVar {
		return "POKEW:SM"
	}
	return "POKEW"
}

// parsePOKEWOffset extracts offset from syntax like "var[offset]"
// Returns (base, offset) where offset is empty if no brackets found
func parsePOKEWOffset(param string) (base string, offset string) {
	openBracket := strings.IndexByte(param, '[')
	closeBracket := strings.IndexByte(param, ']')

	if openBracket == -1 || closeBracket == -1 {
		return param, ""
	}

	if closeBracket <= openBracket || closeBracket != len(param)-1 {
		return param, ""
	}

	base = param[:openBracket]
	offset = param[openBracket+1 : closeBracket]
	return base, offset
}
