package optimizer

import "strings"

// passLoadElimination removes redundant memory-to-register loads.
// Pattern: lda X; ... (X not modified) ...; lda X  → remove second
// Applies to A, X, Y registers identically.
func passLoadElimination(lines []asmLine, cfg *Config) []asmLine {
	rs := newRegState()
	var result []asmLine

	for _, line := range lines {
		if !line.isCode {
			if line.isLabel {
				rs.reset()
			}
			result = append(result, line)
			continue
		}

		if isChainBreaker(line.opcode) {
			rs.reset()
			result = append(result, line)
			continue
		}

		// Check for redundant memory load
		if line.opcode == "lda" && !isImmediate(line.operand) {
			if isIndexedOperand(line.operand) {
				updateRegState(line, rs)
			} else if isIOAddr(line.operand, cfg) {
				// I/O register reads may return different values — don't track
				rs.resetA()
				updateRegState(line, rs)
			} else if rs.a.src == line.operand {
				continue // redundant, remove
			} else {
				rs.a.src = line.operand
			}
		} else if line.opcode == "ldx" && !isImmediate(line.operand) {
			if isIndexedOperand(line.operand) {
				updateRegState(line, rs)
			} else if isIOAddr(line.operand, cfg) {
				rs.resetX()
				updateRegState(line, rs)
			} else if rs.x.src == line.operand {
				continue
			} else {
				rs.x.src = line.operand
			}
		} else if line.opcode == "ldy" && !isImmediate(line.operand) {
			if isIndexedOperand(line.operand) {
				updateRegState(line, rs)
			} else if isIOAddr(line.operand, cfg) {
				rs.resetY()
				updateRegState(line, rs)
			} else if rs.y.src == line.operand {
				continue
			} else {
				rs.y.src = line.operand
			}
		} else {
			updateRegState(line, rs)
		}

		result = append(result, line)
	}

	return result
}

// isIndexedOperand returns true for addressing modes where the effective address
// depends on X or Y registers (e.g. (zp),y, $xxxx,x, zp,x).
// Such loads can't be tracked by operand text alone since the register may change.
func isIndexedOperand(operand string) bool {
	return strings.Contains(operand, "(") || strings.Contains(operand, ",")
}

// isIOAddr returns true if operand is a hex address marked as I/O in the config.
func isIOAddr(operand string, cfg *Config) bool {
	if cfg == nil {
		return false
	}
	if !strings.HasPrefix(operand, "$") {
		return false
	}
	addr := parseHexOrDec(operand)
	return addr >= 0 && addr < 65536 && cfg.IOMap[addr]
}

func isImmediate(operand string) bool {
	return len(operand) > 0 && operand[0] == '#'
}
