package commands

import (
	"fmt"
	"os"
	"strings"

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

// ShiftLCommand handles logical shift left operations
// Syntax:
//
//	SHIFTL <source> BY <amount> GIVING <dest>    # old syntax with BY/GIVING
//	SHIFTL <source> << <amount> -> <dest>        # old syntax with <</->
//	<dest> = <source> << <amount>                # new syntax
type ShiftLCommand struct {
	sourceVarName string
	sourceVarKind compiler.VarKind
	sourceValue   uint16
	sourceIsVar   bool

	amountVarName string
	amountVarKind compiler.VarKind
	amountValue   uint16
	amountIsVar   bool

	destVarName string
	destVarKind compiler.VarKind

	line preproc.Line // Store line info for warnings
}

func (c *ShiftLCommand) WillHandle(line preproc.Line) bool {
	params, err := utils.ParseParams(line.Text)
	if err != nil || len(params) == 0 {
		return false
	}

	// Old syntax: SHIFTL ... (must have exactly 6 params)
	if strings.ToUpper(params[0]) == "SHIFTL" && len(params) == 6 {
		return true
	}

	// New syntax: <dest> = <source> << <amount>
	if len(params) == 5 && params[1] == "=" && params[3] == "<<" {
		return true
	}

	return false
}

func (c *ShiftLCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error {
	// Clear state
	c.sourceVarName = ""
	c.sourceIsVar = false
	c.sourceValue = 0
	c.amountVarName = ""
	c.amountIsVar = false
	c.amountValue = 0
	c.destVarName = ""
	c.line = line // Store line for warnings

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

	paramCount := len(params)
	scope := ctx.CurrentScope()

	// Create constant lookup function
	constLookup := ctx.SymbolTable.ConstantLookupFunc(scope)

	// Determine syntax and parse accordingly
	if strings.ToUpper(params[0]) == "SHIFTL" {
		// Old syntax: SHIFTL <source> BY/<< <amount> GIVING/-> <dest>
		if paramCount != 6 {
			return fmt.Errorf("SHIFTL: wrong number of parameters (%d), expected 6", paramCount)
		}

		separator1 := strings.ToUpper(params[2])
		if separator1 != "BY" && separator1 != "<<" {
			return fmt.Errorf("SHIFTL: parameter #3 must be 'BY' or '<<', got %q", params[2])
		}

		separator2 := strings.ToUpper(params[4])
		if separator2 != "GIVING" && separator2 != "->" {
			return fmt.Errorf("SHIFTL: parameter #5 must be 'GIVING' or '->', got %q", params[4])
		}

		// Parse destination
		destName := params[5]
		destSym := ctx.SymbolTable.Lookup(destName, scope)
		if destSym == nil {
			return fmt.Errorf("SHIFTL: unknown variable %q", destName)
		}
		if destSym.IsConst() {
			return fmt.Errorf("SHIFTL: cannot assign to constant %q", destName)
		}
		c.destVarName = destSym.FullName()
		c.destVarKind = destSym.GetVarKind()

		// Parse source
		var err error
		c.sourceVarName, c.sourceVarKind, c.sourceValue, c.sourceIsVar, err = compiler.ParseOperandParam(
			params[1], ctx.SymbolTable, scope, constLookup)
		if err != nil {
			return fmt.Errorf("SHIFTL: source: %w", err)
		}

		// Parse amount
		c.amountVarName, c.amountVarKind, c.amountValue, c.amountIsVar, err = compiler.ParseOperandParam(
			params[3], ctx.SymbolTable, scope, constLookup)
		if err != nil {
			return fmt.Errorf("SHIFTL: amount: %w", err)
		}

	} else {
		// New syntax: <dest> = <source> << <amount>
		if paramCount != 5 {
			return fmt.Errorf("SHIFTL: wrong number of parameters (%d), expected 5", paramCount)
		}

		if params[1] != "=" {
			return fmt.Errorf("SHIFTL: expected '=' at position 2, got %q", params[1])
		}

		if params[3] != "<<" {
			return fmt.Errorf("SHIFTL: expected '<<' at position 4, got %q", params[3])
		}

		// Parse destination
		destName := params[0]
		destSym := ctx.SymbolTable.Lookup(destName, scope)
		if destSym == nil {
			return fmt.Errorf("SHIFTL: unknown variable %q", destName)
		}
		if destSym.IsConst() {
			return fmt.Errorf("SHIFTL: cannot assign to constant %q", destName)
		}
		c.destVarName = destSym.FullName()
		c.destVarKind = destSym.GetVarKind()

		// Parse source
		var err error
		c.sourceVarName, c.sourceVarKind, c.sourceValue, c.sourceIsVar, err = compiler.ParseOperandParam(
			params[2], ctx.SymbolTable, scope, constLookup)
		if err != nil {
			return fmt.Errorf("SHIFTL: source: %w", err)
		}

		// Parse amount
		c.amountVarName, c.amountVarKind, c.amountValue, c.amountIsVar, err = compiler.ParseOperandParam(
			params[4], ctx.SymbolTable, scope, constLookup)
		if err != nil {
			return fmt.Errorf("SHIFTL: amount: %w", err)
		}
	}

	// Validate amount
	if c.amountIsVar {
		if c.amountVarKind == compiler.KindWord {
			return fmt.Errorf("SHIFTL: amount must be BYTE variable, got WORD %q", c.amountVarName)
		}
	} else {
		if c.amountValue > 255 {
			return fmt.Errorf("SHIFTL: amount constant %d out of BYTE range (0-255)", c.amountValue)
		}
	}

	return nil
}

func (c *ShiftLCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) {
	// Case 1: BYTE destination
	if c.destVarKind == compiler.KindByte {
		return c.generateByteShift(ctx)
	}
	
	// Case 2: WORD destination
	return c.generateWordShift(ctx)
}

// generateByteShift handles BYTE << amount
func (c *ShiftLCommand) generateByteShift(ctx *compiler.CompilerContext) ([]string, error) {
	var asm []string
	
	// Constant shift amount
	if !c.amountIsVar {
		amount := c.amountValue
		
		// Shift >= 8 bits -> zero
		if amount >= 8 {
			if amount > 0 {
				_, _ = fmt.Fprintf(os.Stderr, "%s:%d: warning: shift amount %d >= 8 bits, value will be zero\n",
					c.line.Filename, c.line.LineNo, amount)
			}
			
			// Store zero
			if c.sourceIsVar && c.sourceVarName == c.destVarName {
				// Same variable: just zero it
				asm = append(asm, "\tlda #0")
				asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
			} else {
				// Different: load zero and store
				asm = append(asm, "\tlda #0")
				asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
			}
	return asm, nil
}

		// Shift 0 -> copy source
		if amount == 0 {
			return c.generateByteCopy(), nil
		}
		
		// Constant shift 1-7
		return c.generateByteShiftConst(amount), nil
	}
	
	// Variable shift amount
	return c.generateByteShiftVar(ctx)
}

// generateByteCopy copies source to destination (BYTE)
func (c *ShiftLCommand) generateByteCopy() []string {
	var asm []string
	
	if c.sourceIsVar {
		if c.sourceVarName == c.destVarName {
			// Same variable, no copy needed
			return asm
		}
		// Different variable
		asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName))
		asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
	} else {
		// Literal
		val := uint8(c.sourceValue & 0xFF)
		asm = append(asm, fmt.Sprintf("\tlda #$%02x", val))
		asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
	}
	
	return asm
}

// generateByteShiftConst generates BYTE << constant (1-7)
func (c *ShiftLCommand) generateByteShiftConst(amount uint16) []string {
	var asm []string
	
	// Check if same variable
	if c.sourceIsVar && c.sourceVarName == c.destVarName {
		// Same variable: load to accumulator, shift, store back (faster)
		asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName))
		// Shift in accumulator (faster: 2 cycles vs 5 for memory)
		for i := uint16(0); i < amount; i++ {
			asm = append(asm, "\tasl")
		}
		asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
		return asm
	}
	
	// Different variables or literal source: shift in accumulator
	if c.sourceIsVar {
		// Variable source
		asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName))
	} else {
		// Literal source
		val := uint8(c.sourceValue & 0xFF)
		asm = append(asm, fmt.Sprintf("\tlda #$%02x", val))
	}
	
	// Shift in accumulator (faster: 2 cycles vs 5 for memory)
	for i := uint16(0); i < amount; i++ {
		asm = append(asm, "\tasl")
	}
	
	// Store result
	asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
	
	return asm
}

// generateByteShiftVar generates BYTE << variable
func (c *ShiftLCommand) generateByteShiftVar(ctx *compiler.CompilerContext) ([]string, error) {
	var asm []string
	
	// Check if same variable
	if c.sourceIsVar && c.sourceVarName == c.destVarName {
		// Same variable: load to accumulator, shift loop, store back (faster)
		// Generate labels
		loopLabel := ctx.GeneralStack.Push()
		ctx.GeneralStack.Pop()
		doneLabel := ctx.GeneralStack.Push()
		ctx.GeneralStack.Pop()
		
		// Load value to accumulator
		asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName))
		
		// Variable shift loop (in accumulator, 2 cycles vs 5 for memory)
		asm = append(asm, fmt.Sprintf("\tldx %s", c.amountVarName))
		asm = append(asm, fmt.Sprintf("\tbeq %s", doneLabel))
		asm = append(asm, loopLabel)
		asm = append(asm, "\tasl")
		asm = append(asm, "\tdex")
		asm = append(asm, fmt.Sprintf("\tbne %s", loopLabel))
		asm = append(asm, doneLabel)
		
		// Store result back
		asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
	
	return asm, nil
}

	// Different variables or literal: shift in accumulator (faster)
	// Generate labels
	loopLabel := ctx.GeneralStack.Push()
	ctx.GeneralStack.Pop()
	doneLabel := ctx.GeneralStack.Push()
	ctx.GeneralStack.Pop()
	
	// Load source to accumulator
	if c.sourceIsVar {
		asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName))
	} else {
		val := uint8(c.sourceValue & 0xFF)
		asm = append(asm, fmt.Sprintf("\tlda #$%02x", val))
	}
	
	// Load shift amount to X
	asm = append(asm, fmt.Sprintf("\tldx %s", c.amountVarName))
	asm = append(asm, fmt.Sprintf("\tbeq %s", doneLabel))
	
	// Shift loop (in accumulator, 2 cycles vs 5 for memory)
	asm = append(asm, loopLabel)
	asm = append(asm, "\tasl")
	asm = append(asm, "\tdex")
	asm = append(asm, fmt.Sprintf("\tbne %s", loopLabel))
	
	// Store result
	asm = append(asm, doneLabel)
	asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
	
	return asm, nil
}

// generateWordShift handles WORD << amount
func (c *ShiftLCommand) generateWordShift(ctx *compiler.CompilerContext) ([]string, error) {
	// Constant shift amount
	if !c.amountIsVar {
		amount := c.amountValue
		
		// Shift >= 16 bits -> zero
		if amount >= 16 {
			if amount > 0 {
				_, _ = fmt.Fprintf(os.Stderr, "%s:%d: warning: shift amount %d >= 16 bits, value will be zero\n",
					c.line.Filename, c.line.LineNo, amount)
			}
			
			// Store zero
			if c.sourceIsVar && c.sourceVarName == c.destVarName {
				// Same variable: zero both bytes
				asm := []string{
					"\tlda #0",
					fmt.Sprintf("\tsta %s", c.destVarName),
					fmt.Sprintf("\tsta %s+1", c.destVarName),
				}
				return asm, nil
			} else {
				// Different: load zero and store to both bytes
				asm := []string{
					"\tlda #0",
					fmt.Sprintf("\tsta %s", c.destVarName),
					fmt.Sprintf("\tsta %s+1", c.destVarName),
				}
				return asm, nil
			}
		}
		
		// Shift 0 -> copy source
		if amount == 0 {
			return c.generateWordCopy(), nil
		}
		
		// Constant shift 1-15
		return c.generateWordShiftConst(amount), nil
	}
	
	// Variable shift amount
	return c.generateWordShiftVar(ctx)
}

// generateWordCopy copies source to destination (WORD)
func (c *ShiftLCommand) generateWordCopy() []string {
	var asm []string
	
	if c.sourceIsVar && c.sourceVarName == c.destVarName {
		// Same variable, no copy needed
		return asm
	}
	
	if c.sourceIsVar {
		// Variable source
		if c.sourceVarKind == compiler.KindByte {
			// Byte -> Word (zero-extend)
			asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName))
			asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
			asm = append(asm, "\tlda #0")
			asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
		} else {
			// Word -> Word
			asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName))
			asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
			asm = append(asm, fmt.Sprintf("\tlda %s+1", c.sourceVarName))
			asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
		}
	} else {
		// Literal source
		lo := uint8(c.sourceValue & 0xFF)
		hi := uint8((c.sourceValue >> 8) & 0xFF)
		asm = append(asm, fmt.Sprintf("\tlda #$%02x", lo))
		asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
		if lo != hi {
			asm = append(asm, fmt.Sprintf("\tlda #$%02x", hi))
		}
		asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
	}
	
	return asm
}

// generateWordShiftConst generates WORD << constant (1-15)
func (c *ShiftLCommand) generateWordShiftConst(amount uint16) []string {
	var asm []string
	
	// Special case: shift >= 8
	if amount >= 8 && amount < 16 {
		remaining := amount - 8
		
		// Get source low byte
		if c.sourceIsVar {
			if c.sourceVarName == c.destVarName {
				// Same variable: read from destination (already has value)
				asm = append(asm, fmt.Sprintf("\tlda %s", c.destVarName))
			} else {
				// Different: read from source
				asm = append(asm, fmt.Sprintf("\tlda %s", c.sourceVarName))
			}
		} else {
			// Literal
			lo := uint8(c.sourceValue & 0xFF)
			asm = append(asm, fmt.Sprintf("\tlda #$%02x", lo))
		}
		
		// Store to destination high byte
		asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
		
		// Zero low byte
		asm = append(asm, "\tlda #0")
		asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
		
		// Shift high byte remaining bits
		for i := uint16(0); i < remaining; i++ {
			asm = append(asm, fmt.Sprintf("\tasl %s+1", c.destVarName))
		}
		
		return asm
	}
	
	// Shift 1-7: normal copy and shift
	copyAsm := c.generateWordCopy()
	asm = append(asm, copyAsm...)
	
	// Apply shift
	for i := uint16(0); i < amount; i++ {
		asm = append(asm, fmt.Sprintf("\tasl %s", c.destVarName))
		asm = append(asm, fmt.Sprintf("\trol %s+1", c.destVarName))
	}
	
	return asm
}

// generateWordShiftVar generates WORD << variable
func (c *ShiftLCommand) generateWordShiftVar(ctx *compiler.CompilerContext) ([]string, error) {
	var asm []string
	
	// Copy source to destination if needed
	copyAsm := c.generateWordCopy()
	asm = append(asm, copyAsm...)
	
	// Generate labels
	loopLabel := ctx.GeneralStack.Push()
	ctx.GeneralStack.Pop()
	doneLabel := ctx.GeneralStack.Push()
	ctx.GeneralStack.Pop()
	
	// Variable shift loop
	asm = append(asm, fmt.Sprintf("\tldx %s", c.amountVarName))
	asm = append(asm, fmt.Sprintf("\tbeq %s", doneLabel))
	asm = append(asm, loopLabel)
	asm = append(asm, fmt.Sprintf("\tasl %s", c.destVarName))
	asm = append(asm, fmt.Sprintf("\trol %s+1", c.destVarName))
	asm = append(asm, "\tdex")
	asm = append(asm, fmt.Sprintf("\tbne %s", loopLabel))
	asm = append(asm, doneLabel)
	
	return asm, nil
}

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

