// rasm-core/src/codegen/encode.rs
//
//! # Кодировщик инструкций x86_64
//!
//! Этот модуль берёт [`Instruction`] из AST и превращает его в последовательность байт,
//! записывая их в [`Emitter`]. Параллельно заполняется [`RelocTable`] для меток.
//!
//! ## Как читать этот файл
//!
//! Каждая инструкция — отдельная ветка в `encode_instruction`. Общий паттерн:
//!
//! 1. Смотрим на операнды (`Operand::Register`, `Operand::Immediate`, `Operand::Memory`).
//! 2. Выбираем вариант опкода (у `MOV` их около 30 в реальном x86, здесь — основные).
//! 3. Решаем, нужен ли REX-префикс.
//! 4. Эмиттим: `[REX] [opcode] [ModRM] [SIB] [disp] [imm]`.
//!
//! ## Соглашения по кодированию регистров
//!
//! | Регистр | Номер | REX.B нужен? |
//! |---------|-------|--------------|
//! | RAX     | 0     | нет          |
//! | RCX     | 1     | нет          |
//! | RDX     | 2     | нет          |
//! | RBX     | 3     | нет          |
//! | RSP     | 4     | нет          |
//! | RBP     | 5     | нет          |
//! | RSI     | 6     | нет          |
//! | RDI     | 7     | нет          |
//! | R8      | 8     | да (REX.B=1) |
//! | R9      | 9     | да           |
//! | ...     | ...   | да           |
//! | R15     | 15    | да           |

use crate::{
    codegen::{
        emit::Emitter,
        err::{CodegenError, CodegenErrorKind},
        reloc::{RelocKind, RelocTable},
    },
    isa::{GPRs, Instruction, MemoryAddress, MnemonicKind, Operand, Registers, Scale},
    str_pool::StrId,
};
use std::collections::HashMap;

// =========================================================================
// Публичная точка входа
// =========================================================================

/// Кодирует одну инструкцию в байты и записывает их в `emit`.
/// При необходимости добавляет записи в `relocs`.
///
/// `label_offsets` — карта `StrId -> байтовый_офсет`, заполненная на первом проходе codegen.
pub fn encode_instruction(
    instr: &Instruction,
    emit: &mut Emitter,
    relocs: &mut RelocTable,
    label_offsets: &HashMap<StrId, usize>,
) -> Result<(), CodegenError> {
    match instr.mnemonic {
        MnemonicKind::Mov => encode_mov(instr, emit, relocs)?,
        MnemonicKind::Push => encode_push(instr, emit)?,
        MnemonicKind::Pop => encode_pop(instr, emit)?,
        MnemonicKind::Add => encode_alu(instr, emit, relocs, 0x00, 0x81, 0)?,
        MnemonicKind::Sub => encode_alu(instr, emit, relocs, 0x28, 0x81, 5)?,
        MnemonicKind::And => encode_alu(instr, emit, relocs, 0x20, 0x81, 4)?,
        MnemonicKind::Or => encode_alu(instr, emit, relocs, 0x08, 0x81, 1)?,
        MnemonicKind::Xor => encode_alu(instr, emit, relocs, 0x30, 0x81, 6)?,
        MnemonicKind::Cmp => encode_alu(instr, emit, relocs, 0x38, 0x81, 7)?,
        MnemonicKind::Test => encode_test(instr, emit)?,
        MnemonicKind::Inc => encode_unary(instr, emit, relocs, 0xFF, 0)?,
        MnemonicKind::Dec => encode_unary(instr, emit, relocs, 0xFF, 1)?,
        MnemonicKind::Neg => encode_unary(instr, emit, relocs, 0xF7, 3)?,
        MnemonicKind::Not => encode_unary(instr, emit, relocs, 0xF7, 2)?,
        MnemonicKind::Mul => encode_unary(instr, emit, relocs, 0xF7, 4)?,
        MnemonicKind::Div => encode_unary(instr, emit, relocs, 0xF7, 6)?,
        MnemonicKind::Idiv => encode_unary(instr, emit, relocs, 0xF7, 7)?,
        MnemonicKind::Imul => encode_imul(instr, emit, relocs)?,
        MnemonicKind::Lea => encode_lea(instr, emit, relocs)?,
        MnemonicKind::Shl => encode_shift(instr, emit, 4)?,
        MnemonicKind::Shr => encode_shift(instr, emit, 5)?,
        MnemonicKind::Sar => encode_shift(instr, emit, 7)?,
        MnemonicKind::Cqo => {
            emit.emit_rex_w();
            emit.emit_u8(0x99);
        }
        MnemonicKind::Nop => {
            emit.emit_u8(0x90);
        }
        MnemonicKind::Ret => {
            emit.emit_u8(0xC3);
        }
        MnemonicKind::Syscall => {
            emit.emit_bytes(&[0x0F, 0x05]);
        }

        // Переходы и вызовы
        MnemonicKind::Jmp => {
            encode_jump(instr, emit, relocs, label_offsets, 0xE9, None, Some(0xEB))?
        }
        MnemonicKind::Je => encode_jump(
            instr,
            emit,
            relocs,
            label_offsets,
            0x0F,
            Some(0x84),
            Some(0x74),
        )?,
        MnemonicKind::Jne => encode_jump(
            instr,
            emit,
            relocs,
            label_offsets,
            0x0F,
            Some(0x85),
            Some(0x75),
        )?,
        MnemonicKind::Jg => encode_jump(
            instr,
            emit,
            relocs,
            label_offsets,
            0x0F,
            Some(0x8F),
            Some(0x7F),
        )?,
        MnemonicKind::Jge => encode_jump(
            instr,
            emit,
            relocs,
            label_offsets,
            0x0F,
            Some(0x8D),
            Some(0x7D),
        )?,
        MnemonicKind::Jl => encode_jump(
            instr,
            emit,
            relocs,
            label_offsets,
            0x0F,
            Some(0x8C),
            Some(0x7C),
        )?,
        MnemonicKind::Jle => encode_jump(
            instr,
            emit,
            relocs,
            label_offsets,
            0x0F,
            Some(0x8E),
            Some(0x7E),
        )?,
        MnemonicKind::Ja => encode_jump(
            instr,
            emit,
            relocs,
            label_offsets,
            0x0F,
            Some(0x87),
            Some(0x77),
        )?,
        MnemonicKind::Jae => encode_jump(
            instr,
            emit,
            relocs,
            label_offsets,
            0x0F,
            Some(0x83),
            Some(0x73),
        )?,
        MnemonicKind::Jb => encode_jump(
            instr,
            emit,
            relocs,
            label_offsets,
            0x0F,
            Some(0x82),
            Some(0x72),
        )?,
        MnemonicKind::Jbe => encode_jump(
            instr,
            emit,
            relocs,
            label_offsets,
            0x0F,
            Some(0x86),
            Some(0x76),
        )?,
        MnemonicKind::Jo => encode_jump(
            instr,
            emit,
            relocs,
            label_offsets,
            0x0F,
            Some(0x80),
            Some(0x70),
        )?,
        MnemonicKind::Jno => encode_jump(
            instr,
            emit,
            relocs,
            label_offsets,
            0x0F,
            Some(0x81),
            Some(0x71),
        )?,
        MnemonicKind::Js => encode_jump(
            instr,
            emit,
            relocs,
            label_offsets,
            0x0F,
            Some(0x88),
            Some(0x78),
        )?,
        MnemonicKind::Jns => encode_jump(
            instr,
            emit,
            relocs,
            label_offsets,
            0x0F,
            Some(0x89),
            Some(0x79),
        )?,
        MnemonicKind::Jp => encode_jump(
            instr,
            emit,
            relocs,
            label_offsets,
            0x0F,
            Some(0x8A),
            Some(0x7A),
        )?,
        MnemonicKind::Jnp => encode_jump(
            instr,
            emit,
            relocs,
            label_offsets,
            0x0F,
            Some(0x8B),
            Some(0x7B),
        )?,
        MnemonicKind::Call => encode_call(instr, emit, relocs, label_offsets)?,
        MnemonicKind::Db => {
            return Err(CodegenError::new(
                CodegenErrorKind::UnsupportedOperandCombination,
            ));
        }
    }
    Ok(())
}

// =========================================================================
// Вспомогательные функции для регистров
// =========================================================================

/// Возвращает 4-битный номер GPR-регистра (0–15).
fn gpr_code(r: GPRs) -> u8 {
    match r {
        GPRs::Rax => 0,
        GPRs::Rcx => 1,
        GPRs::Rdx => 2,
        GPRs::Rbx => 3,
        GPRs::Rsp => 4,
        GPRs::Rbp => 5,
        GPRs::Rsi => 6,
        GPRs::Rdi => 7,
        GPRs::R8 => 8,
        GPRs::R9 => 9,
        GPRs::R10 => 10,
        GPRs::R11 => 11,
        GPRs::R12 => 12,
        GPRs::R13 => 13,
        GPRs::R14 => 14,
        GPRs::R15 => 15,
    }
}

/// Возвращает 4-битный номер любого регистра.
/// Для сегментных/специальных возвращает ошибку.
fn reg_code(r: Registers) -> Result<u8, CodegenError> {
    match r {
        Registers::GPR(gpr) => Ok(gpr_code(gpr)),
        Registers::Rip => Ok(5), // RIP кодируется как RBP(5) в ModRM с mod=00
        _ => Err(CodegenError::new(
            CodegenErrorKind::UnsupportedOperandCombination,
        )),
    }
}

/// Нижние 3 бита номера регистра (поле rm/reg в ModRM).
#[inline]
fn reg3(code: u8) -> u8 {
    code & 0x7
}

/// Бит расширения (4-й бит) — нужен для REX.R / REX.B / REX.X.
#[inline]
fn reg_ext(code: u8) -> bool {
    code > 7
}

/// Кодирование масштаба SIB в 2-битное поле.
fn scale_bits(s: Scale) -> u8 {
    match s {
        Scale::One => 0,
        Scale::Two => 1,
        Scale::Four => 2,
        Scale::Eight => 3,
    }
}

// =========================================================================
// Кодирование адресации памяти [base + index*scale + disp]
// =========================================================================

/// Эмиттит ModRM + опциональный SIB + опциональный displacement
/// для операнда `Memory`. `reg_field` — поле reg в ModRM (обычно номер другого регистра).
///
/// Возвращает `(rex_b, rex_x)` — биты расширения для REX-префикса.
fn emit_memory_operand(
    emit: &mut Emitter,
    mem: &MemoryAddress,
    reg_field: u8,
    mut relocs: Option<&mut RelocTable>,
) -> Result<(bool, bool), CodegenError> {
    if mem.label.is_some() && mem.base != Some(Registers::Rip) {
        return Err(CodegenError::new(
            CodegenErrorKind::UnsupportedOperandCombination,
        ));
    }

    let base_code = match mem.base {
        Some(r) => reg_code(r)?,
        None => 5, // без базы: mod=00, rm=101 означает disp32
    };
    let rex_b = reg_ext(base_code);

    // Нужен ли SIB? Да, если: есть index, OR base=RSP(4), OR base=R12(12)
    let need_sib = mem.index.is_some()
        || reg3(base_code) == 4 // RSP/R12 в поле rm требуют SIB
        || mem.base.is_none(); // без базы тоже через SIB

    let rm_field = if need_sib { 4 } else { reg3(base_code) };

    // Выбираем mod по размеру displacement
    let mod_ = if mem.label.is_some() {
        0b00 // RIP-relative disp32 with symbolic relocation
    } else if mem.displacement == 0 && reg3(base_code) != 5 {
        // RBP(5)/R13(13) нельзя кодировать с mod=00 — будет принято как disp32
        0b00
    } else if mem.displacement >= -128 && mem.displacement <= 127 {
        0b01 // disp8
    } else {
        0b10 // disp32
    };

    emit.emit_modrm(mod_, reg_field & 0x7, rm_field);

    let rex_x;
    if need_sib {
        let (idx_code, rx) = mem.index.map_or((4, false), |gpr| {
            let c = gpr_code(gpr);
            (c, reg_ext(c))
        });
        rex_x = rx;
        let scale = mem.scale.map(scale_bits).unwrap_or(0);
        emit.emit_sib(scale, reg3(idx_code), reg3(base_code));
    } else {
        rex_x = false;
    }

    // Displacement
    match mod_ {
        0b00 if mem.label.is_some() => {
            let patch_offset = emit.offset();
            emit.emit_i32(mem.displacement);
            let relocs = relocs.as_mut().ok_or_else(|| {
                CodegenError::new(CodegenErrorKind::UnsupportedOperandCombination)
            })?;
            relocs.add(patch_offset, mem.label.unwrap(), RelocKind::Rel32);
        }
        0b00 if mem.base.is_none() => emit.emit_i32(mem.displacement),
        0b01 => emit.emit_i8(mem.displacement as i8),
        0b10 => emit.emit_i32(mem.displacement),
        _ => {}
    }

    Ok((rex_b, rex_x))
}

// =========================================================================
// MOV
// =========================================================================

fn encode_mov(
    instr: &Instruction,
    emit: &mut Emitter,
    relocs: &mut RelocTable,
) -> Result<(), CodegenError> {
    if instr.operands.len() != 2 {
        return Err(CodegenError::new(CodegenErrorKind::WrongOperandCount {
            mnemonic: instr.mnemonic,
            expected: 2,
            got: instr.operands.len(),
        }));
    }

    match (&instr.operands[0], &instr.operands[1]) {
        // MOV r64, imm64   ->   REX.W + B8+rd  imm64
        (Operand::Register(Registers::GPR(dst)), Operand::Immediate(imm)) => {
            let d = gpr_code(*dst);
            emit.emit_rex(true, false, false, reg_ext(d));
            emit.emit_u8(0xB8 | reg3(d));
            emit.emit_i64(*imm);
        }

        // MOV r64, r64   ->   REX.W + 89 /r  (src -> dst)
        (Operand::Register(Registers::GPR(dst)), Operand::Register(Registers::GPR(src))) => {
            let d = gpr_code(*dst);
            let s = gpr_code(*src);
            emit.emit_rex(true, reg_ext(s), false, reg_ext(d));
            emit.emit_u8(0x89);
            emit.emit_modrm(0b11, reg3(s), reg3(d));
        }

        // MOV r64, [mem]   ->   REX.W + 8B /r
        (Operand::Register(Registers::GPR(dst)), Operand::Memory(mem)) => {
            let d = gpr_code(*dst);
            // REX: W=1, R=расширение dst, X и B — из памяти
            let (rex_b, rex_x) = {
                // сначала узнаём rex_b/rex_x через dry-run? нет — просто считаем заранее
                let base_code = mem.base.map(reg_code).transpose()?.unwrap_or(5);
                let idx_code = mem.index.map(gpr_code).unwrap_or(4);
                (reg_ext(base_code), reg_ext(idx_code))
            };
            emit.emit_rex(true, reg_ext(d), rex_x, rex_b);
            emit.emit_u8(0x8B);
            emit_memory_operand(emit, mem, reg3(d), Some(relocs))?;
        }

        // MOV [mem], r64   ->   REX.W + 89 /r
        (Operand::Memory(mem), Operand::Register(Registers::GPR(src))) => {
            let s = gpr_code(*src);
            let base_code = mem.base.map(reg_code).transpose()?.unwrap_or(5);
            let idx_code = mem.index.map(gpr_code).unwrap_or(4);
            emit.emit_rex(true, reg_ext(s), reg_ext(idx_code), reg_ext(base_code));
            emit.emit_u8(0x89);
            emit_memory_operand(emit, mem, reg3(s), Some(relocs))?;
        }

        // MOV [mem], imm32   ->   REX.W + C7 /0  imm32
        (Operand::Memory(mem), Operand::Immediate(imm)) => {
            let base_code = mem.base.map(reg_code).transpose()?.unwrap_or(5);
            let idx_code = mem.index.map(gpr_code).unwrap_or(4);
            emit.emit_rex(true, false, reg_ext(idx_code), reg_ext(base_code));
            emit.emit_u8(0xC7);
            emit_memory_operand(emit, mem, 0, Some(relocs))?;
            // imm усекается до 32 бит (sign-extended)
            emit.emit_i32(*imm as i32);
        }

        _ => {
            return Err(CodegenError::new(
                CodegenErrorKind::UnsupportedOperandCombination,
            ));
        }
    }
    Ok(())
}

// =========================================================================
// PUSH / POP
// =========================================================================

fn encode_push(instr: &Instruction, emit: &mut Emitter) -> Result<(), CodegenError> {
    if instr.operands.len() != 1 {
        return Err(CodegenError::new(CodegenErrorKind::WrongOperandCount {
            mnemonic: instr.mnemonic,
            expected: 1,
            got: instr.operands.len(),
        }));
    }
    match &instr.operands[0] {
        // PUSH r64   ->   (REX.B если нужен) + 50+rd
        Operand::Register(Registers::GPR(r)) => {
            let code = gpr_code(*r);
            if reg_ext(code) {
                emit.emit_rex(false, false, false, true);
            }
            emit.emit_u8(0x50 | reg3(code));
        }
        // PUSH imm32 (sign-extended)   ->   68 imm32
        Operand::Immediate(imm) => {
            emit.emit_u8(0x68);
            emit.emit_i32(*imm as i32);
        }
        _ => {
            return Err(CodegenError::new(
                CodegenErrorKind::UnsupportedOperandCombination,
            ));
        }
    }
    Ok(())
}

fn encode_pop(instr: &Instruction, emit: &mut Emitter) -> Result<(), CodegenError> {
    if instr.operands.len() != 1 {
        return Err(CodegenError::new(CodegenErrorKind::WrongOperandCount {
            mnemonic: instr.mnemonic,
            expected: 1,
            got: instr.operands.len(),
        }));
    }
    match &instr.operands[0] {
        Operand::Register(Registers::GPR(r)) => {
            let code = gpr_code(*r);
            if reg_ext(code) {
                emit.emit_rex(false, false, false, true);
            }
            emit.emit_u8(0x58 | reg3(code));
        }
        _ => {
            return Err(CodegenError::new(
                CodegenErrorKind::UnsupportedOperandCombination,
            ));
        }
    }
    Ok(())
}

// =========================================================================
// ALU: ADD, SUB, AND, OR, XOR, CMP
//
// Параметры:
//   opcode_rm_r   — опкод для "reg op= reg/mem" (например ADD: 0x01)
//   opcode_imm    — опкод для "rm op= imm" (всегда 0x81 для 32-битных imm)
//   imm_reg_field — поле /digit в ModRM для immediate-формы
// =========================================================================

fn encode_alu(
    instr: &Instruction,
    emit: &mut Emitter,
    relocs: &mut RelocTable,
    opcode_rm_r: u8,
    opcode_imm: u8,
    imm_reg_field: u8,
) -> Result<(), CodegenError> {
    if instr.operands.len() != 2 {
        return Err(CodegenError::new(CodegenErrorKind::WrongOperandCount {
            mnemonic: instr.mnemonic,
            expected: 2,
            got: instr.operands.len(),
        }));
    }

    match (&instr.operands[0], &instr.operands[1]) {
        // op r64, r64
        (Operand::Register(Registers::GPR(dst)), Operand::Register(Registers::GPR(src))) => {
            let d = gpr_code(*dst);
            let s = gpr_code(*src);
            emit.emit_rex(true, reg_ext(s), false, reg_ext(d));
            emit.emit_u8(opcode_rm_r | 0x01); // 64-bit variant
            emit.emit_modrm(0b11, reg3(s), reg3(d));
        }

        // op r64, imm32
        (Operand::Register(Registers::GPR(dst)), Operand::Immediate(imm)) => {
            let d = gpr_code(*dst);
            // Оптимизация: если imm помещается в i8 — используем /2 форму (opcode 0x83, 1 байт imm)
            if *imm >= -128 && *imm <= 127 {
                emit.emit_rex(true, false, false, reg_ext(d));
                emit.emit_u8(0x83);
                emit.emit_modrm(0b11, imm_reg_field, reg3(d));
                emit.emit_i8(*imm as i8);
            } else {
                emit.emit_rex(true, false, false, reg_ext(d));
                emit.emit_u8(opcode_imm);
                emit.emit_modrm(0b11, imm_reg_field, reg3(d));
                emit.emit_i32(*imm as i32);
            }
        }

        // op r64, [mem]
        (Operand::Register(Registers::GPR(dst)), Operand::Memory(mem)) => {
            let d = gpr_code(*dst);
            let base_code = mem.base.map(reg_code).transpose()?.unwrap_or(5);
            let idx_code = mem.index.map(gpr_code).unwrap_or(4);
            emit.emit_rex(true, reg_ext(d), reg_ext(idx_code), reg_ext(base_code));
            emit.emit_u8(opcode_rm_r | 0x03); // r <- r/m
            emit_memory_operand(emit, mem, reg3(d), Some(relocs))?;
        }

        // op [mem], r64
        (Operand::Memory(mem), Operand::Register(Registers::GPR(src))) => {
            let s = gpr_code(*src);
            let base_code = mem.base.map(reg_code).transpose()?.unwrap_or(5);
            let idx_code = mem.index.map(gpr_code).unwrap_or(4);
            emit.emit_rex(true, reg_ext(s), reg_ext(idx_code), reg_ext(base_code));
            emit.emit_u8(opcode_rm_r | 0x01); // r/m <- r
            emit_memory_operand(emit, mem, reg3(s), Some(relocs))?;
        }

        _ => {
            return Err(CodegenError::new(
                CodegenErrorKind::UnsupportedOperandCombination,
            ));
        }
    }
    Ok(())
}

// =========================================================================
// TEST
// =========================================================================

fn encode_test(instr: &Instruction, emit: &mut Emitter) -> Result<(), CodegenError> {
    if instr.operands.len() != 2 {
        return Err(CodegenError::new(CodegenErrorKind::WrongOperandCount {
            mnemonic: instr.mnemonic,
            expected: 2,
            got: instr.operands.len(),
        }));
    }
    match (&instr.operands[0], &instr.operands[1]) {
        (Operand::Register(Registers::GPR(a)), Operand::Register(Registers::GPR(b))) => {
            let ac = gpr_code(*a);
            let bc = gpr_code(*b);
            emit.emit_rex(true, reg_ext(bc), false, reg_ext(ac));
            emit.emit_u8(0x85);
            emit.emit_modrm(0b11, reg3(bc), reg3(ac));
        }
        (Operand::Register(Registers::GPR(a)), Operand::Immediate(imm)) => {
            let ac = gpr_code(*a);
            emit.emit_rex(true, false, false, reg_ext(ac));
            emit.emit_u8(0xF7);
            emit.emit_modrm(0b11, 0, reg3(ac));
            emit.emit_i32(*imm as i32);
        }
        _ => {
            return Err(CodegenError::new(
                CodegenErrorKind::UnsupportedOperandCombination,
            ));
        }
    }
    Ok(())
}

// =========================================================================
// Унарные: INC, DEC, NEG, NOT, MUL, DIV, IDIV
// =========================================================================

fn encode_unary(
    instr: &Instruction,
    emit: &mut Emitter,
    relocs: &mut RelocTable,
    opcode: u8,
    reg_field: u8,
) -> Result<(), CodegenError> {
    if instr.operands.len() != 1 {
        return Err(CodegenError::new(CodegenErrorKind::WrongOperandCount {
            mnemonic: instr.mnemonic,
            expected: 1,
            got: instr.operands.len(),
        }));
    }
    match &instr.operands[0] {
        Operand::Register(Registers::GPR(r)) => {
            let code = gpr_code(*r);
            emit.emit_rex(true, false, false, reg_ext(code));
            emit.emit_u8(opcode);
            emit.emit_modrm(0b11, reg_field, reg3(code));
        }
        Operand::Memory(mem) => {
            let base_code = mem.base.map(reg_code).transpose()?.unwrap_or(5);
            let idx_code = mem.index.map(gpr_code).unwrap_or(4);
            emit.emit_rex(true, false, reg_ext(idx_code), reg_ext(base_code));
            emit.emit_u8(opcode);
            emit_memory_operand(emit, mem, reg_field, Some(relocs))?;
        }
        _ => {
            return Err(CodegenError::new(
                CodegenErrorKind::UnsupportedOperandCombination,
            ));
        }
    }
    Ok(())
}

// =========================================================================
// IMUL
// =========================================================================

fn encode_imul(
    instr: &Instruction,
    emit: &mut Emitter,
    relocs: &mut RelocTable,
) -> Result<(), CodegenError> {
    match instr.operands.len() {
        // IMUL r64   ->   F7 /5  (RAX * r, результат в RDX:RAX)
        1 => encode_unary(instr, emit, relocs, 0xF7, 5)?,

        // IMUL r64, r/m64   ->   REX.W + 0F AF /r
        2 => {
            if let (
                Operand::Register(Registers::GPR(dst)),
                Operand::Register(Registers::GPR(src)),
            ) = (&instr.operands[0], &instr.operands[1])
            {
                let d = gpr_code(*dst);
                let s = gpr_code(*src);
                emit.emit_rex(true, reg_ext(d), false, reg_ext(s));
                emit.emit_bytes(&[0x0F, 0xAF]);
                emit.emit_modrm(0b11, reg3(d), reg3(s));
            } else {
                return Err(CodegenError::new(
                    CodegenErrorKind::UnsupportedOperandCombination,
                ));
            }
        }

        // IMUL r64, r/m64, imm32   ->   REX.W + 69 /r  imm32
        3 => {
            if let (
                Operand::Register(Registers::GPR(dst)),
                Operand::Register(Registers::GPR(src)),
                Operand::Immediate(imm),
            ) = (&instr.operands[0], &instr.operands[1], &instr.operands[2])
            {
                let d = gpr_code(*dst);
                let s = gpr_code(*src);
                emit.emit_rex(true, reg_ext(d), false, reg_ext(s));
                emit.emit_u8(0x69);
                emit.emit_modrm(0b11, reg3(d), reg3(s));
                emit.emit_i32(*imm as i32);
            } else {
                return Err(CodegenError::new(
                    CodegenErrorKind::UnsupportedOperandCombination,
                ));
            }
        }

        n => {
            return Err(CodegenError::new(CodegenErrorKind::WrongOperandCount {
                mnemonic: instr.mnemonic,
                expected: 2,
                got: n,
            }));
        }
    }
    Ok(())
}

// =========================================================================
// LEA
// =========================================================================

fn encode_lea(
    instr: &Instruction,
    emit: &mut Emitter,
    relocs: &mut RelocTable,
) -> Result<(), CodegenError> {
    if instr.operands.len() != 2 {
        return Err(CodegenError::new(CodegenErrorKind::WrongOperandCount {
            mnemonic: instr.mnemonic,
            expected: 2,
            got: instr.operands.len(),
        }));
    }
    if let (Operand::Register(Registers::GPR(dst)), Operand::Memory(mem)) =
        (&instr.operands[0], &instr.operands[1])
    {
        let d = gpr_code(*dst);
        let base_code = mem.base.map(reg_code).transpose()?.unwrap_or(5);
        let idx_code = mem.index.map(gpr_code).unwrap_or(4);
        emit.emit_rex(true, reg_ext(d), reg_ext(idx_code), reg_ext(base_code));
        emit.emit_u8(0x8D);
        emit_memory_operand(emit, mem, reg3(d), Some(relocs))?;
    } else {
        return Err(CodegenError::new(
            CodegenErrorKind::UnsupportedOperandCombination,
        ));
    }
    Ok(())
}

// =========================================================================
// Сдвиги: SHL, SHR, SAR
// =========================================================================

fn encode_shift(
    instr: &Instruction,
    emit: &mut Emitter,
    reg_field: u8,
) -> Result<(), CodegenError> {
    if instr.operands.len() != 2 {
        return Err(CodegenError::new(CodegenErrorKind::WrongOperandCount {
            mnemonic: instr.mnemonic,
            expected: 2,
            got: instr.operands.len(),
        }));
    }
    let dst = match &instr.operands[0] {
        Operand::Register(Registers::GPR(r)) => gpr_code(*r),
        _ => {
            return Err(CodegenError::new(
                CodegenErrorKind::UnsupportedOperandCombination,
            ));
        }
    };

    match &instr.operands[1] {
        // SHL r64, 1   ->   REX.W + D1 /4
        Operand::Immediate(1) => {
            emit.emit_rex(true, false, false, reg_ext(dst));
            emit.emit_u8(0xD1);
            emit.emit_modrm(0b11, reg_field, reg3(dst));
        }
        // SHL r64, imm8   ->   REX.W + C1 /4  imm8
        Operand::Immediate(imm) => {
            emit.emit_rex(true, false, false, reg_ext(dst));
            emit.emit_u8(0xC1);
            emit.emit_modrm(0b11, reg_field, reg3(dst));
            emit.emit_u8(*imm as u8);
        }
        // SHL r64, CL   ->   REX.W + D3 /4
        Operand::Register(Registers::GPR(GPRs::Rcx)) => {
            emit.emit_rex(true, false, false, reg_ext(dst));
            emit.emit_u8(0xD3);
            emit.emit_modrm(0b11, reg_field, reg3(dst));
        }
        _ => {
            return Err(CodegenError::new(
                CodegenErrorKind::UnsupportedOperandCombination,
            ));
        }
    }
    Ok(())
}

// =========================================================================
// Переходы: JMP, JE, JNE, JG, ...
//
// Условные прыжки (Jcc) кодируются двухбайтовым опкодом: 0F 8x rel32.
// Безусловный JMP: E9 rel32.
// =========================================================================

fn encode_jump(
    instr: &Instruction,
    emit: &mut Emitter,
    relocs: &mut RelocTable,
    label_offsets: &HashMap<StrId, usize>,
    opcode1: u8,
    opcode2: Option<u8>, // Some(x) = условный прыжок rel32 (0F xx), None = безусловный rel32 (E9)
    opcode_rel8: Option<u8>, // Some(x) = короткий прыжок rel8 (EB или 7x), None = у rel32-прыжка нет rel8
) -> Result<(), CodegenError> {
    if instr.operands.len() != 1 {
        return Err(CodegenError::new(CodegenErrorKind::WrongOperandCount {
            mnemonic: instr.mnemonic,
            expected: 1,
            got: instr.operands.len(),
        }));
    }

    match &instr.operands[0] {
        Operand::Label(label_id) => {
            // Эмиттим опкод (всегда rel32 для меток)
            if let Some(op2) = opcode2 {
                emit.emit_bytes(&[opcode1, op2]); // условный: 0F 8x
            } else {
                emit.emit_u8(opcode1); // безусловный: E9
            }

            let patch_offset = emit.offset();

            // Проверяем, знаем ли уже адрес метки
            if let Some(&target) = label_offsets.get(label_id) {
                // Адрес известен (backward reference) — патчим сразу
                let rel32 = (target as i64) - (patch_offset as i64 + 4);
                emit.emit_i32(rel32 as i32);
            } else {
                // Forward reference — ставим заглушку 00 00 00 00, запоминаем в relocs
                emit.emit_i32(0);
                relocs.add(patch_offset, *label_id, RelocKind::Rel32);
            }
        }

        // JMP rel8/rel32 (immediate offset, e.g. from roundtrip decode)
        Operand::Immediate(imm) => {
            if let Some(op8) = opcode_rel8
                && *imm >= -128
                && *imm <= 127
            {
                // rel8
                emit.emit_u8(op8);
                emit.emit_i8(*imm as i8);
                return Ok(());
            }
            // rel32
            if let Some(op2) = opcode2 {
                emit.emit_bytes(&[opcode1, op2]); // условный: 0F 8x
            } else {
                emit.emit_u8(opcode1); // безусловный: E9
            }
            emit.emit_i32(*imm as i32);
        }

        // JMP r64   ->   FF /4
        Operand::Register(Registers::GPR(r)) if opcode2.is_none() && opcode1 == 0xE9 => {
            let code = gpr_code(*r);
            if reg_ext(code) {
                emit.emit_rex(false, false, false, true);
            }
            emit.emit_u8(0xFF);
            emit.emit_modrm(0b11, 4, reg3(code));
        }

        _ => {
            return Err(CodegenError::new(
                CodegenErrorKind::UnsupportedOperandCombination,
            ));
        }
    }
    Ok(())
}

// =========================================================================
// CALL
// =========================================================================

fn encode_call(
    instr: &Instruction,
    emit: &mut Emitter,
    relocs: &mut RelocTable,
    label_offsets: &HashMap<StrId, usize>,
) -> Result<(), CodegenError> {
    if instr.operands.len() != 1 {
        return Err(CodegenError::new(CodegenErrorKind::WrongOperandCount {
            mnemonic: instr.mnemonic,
            expected: 1,
            got: instr.operands.len(),
        }));
    }

    match &instr.operands[0] {
        // CALL label   ->   E8 rel32
        Operand::Label(label_id) => {
            emit.emit_u8(0xE8);
            let patch_offset = emit.offset();

            if let Some(&target) = label_offsets.get(label_id) {
                let rel32 = (target as i64) - (patch_offset as i64 + 4);
                emit.emit_i32(rel32 as i32);
            } else {
                emit.emit_i32(0);
                relocs.add(patch_offset, *label_id, RelocKind::Rel32);
            }
        }

        // CALL r64   ->   FF /2
        Operand::Register(Registers::GPR(r)) => {
            let code = gpr_code(*r);
            if reg_ext(code) {
                emit.emit_rex(false, false, false, true);
            }
            emit.emit_u8(0xFF);
            emit.emit_modrm(0b11, 2, reg3(code));
        }

        _ => {
            return Err(CodegenError::new(
                CodegenErrorKind::UnsupportedOperandCombination,
            ));
        }
    }
    Ok(())
}
