//! # Модуль дизассемблирования
//!
//! Принимает байты -> возвращает AST.
pub mod decode;
pub mod err;
pub mod format;

use decode::DecodedInstruction;
pub use err::{DisasmError, DisasmErrorKind};
use format::format_instruction;

use crate::isa::MnemonicKind;
use std::collections::HashMap;

/// Дизассемблирует весь буфер байт, возвращая список декодированных инструкций.
///
/// При ошибке декодирования возвращает ошибку на первой же непонятной инструкции.
pub fn disassemble_all(
    bytes: &[u8],
    start_offset: Option<usize>,
) -> Result<Vec<DecodedInstruction>, DisasmError> {
    let mut result = Vec::new();
    let mut offset = start_offset.unwrap_or_default();

    while offset < bytes.len() {
        let decoded = decode::decode_instruction(bytes, &mut offset)?;
        result.push(decoded);
    }

    Ok(result)
}

/// Синтезирует имена меток на основе целевых адресов jump/call инструкций.
///
/// Возвращает `HashMap<адрес, имя_метки>`, где адрес — абсолютный адрес цели.
pub fn synthesize_labels(decoded: &[DecodedInstruction]) -> HashMap<String, u64> {
    let mut label_set: Vec<u64> = Vec::new();

    for instr in decoded {
        let next_offset = instr.offset + instr.bytes_len;
        if matches_jump_or_call(instr.instruction.mnemonic)
            && let Some(target) = extract_jump_target(instr)
        {
            let abs_target = next_offset as i64 + target;
            if abs_target >= 0 {
                let addr = abs_target as u64;
                if !label_set.contains(&addr) {
                    label_set.push(addr);
                }
            }
        }
    }

    label_set.sort();
    label_set
        .into_iter()
        .map(|addr| (format!("loc_{:X}", addr), addr))
        .collect()
}

/// Форматирует все декодированные инструкции в строки ассемблера.
pub fn format_all(
    decoded: &[DecodedInstruction],
    labels: Option<&HashMap<String, u64>>,
    base_address: Option<u64>,
) -> Vec<String> {
    let mut output = Vec::new();

    // Строим reverse map: адрес → имя метки
    let label_at_addr: HashMap<u64, &str> = labels
        .map(|lbl| {
            lbl.iter()
                .map(|(name, &addr)| (addr, name.as_str()))
                .collect()
        })
        .unwrap_or_default();

    for instr in decoded {
        let addr = base_address.unwrap_or(0) + instr.offset as u64;

        if let Some(&name) = label_at_addr.get(&addr) {
            output.push(format!("{}:", name));
        }

        let line = format_instruction(&instr.instruction);

        // Для jump/call подменяем immediate на имя метки, если возможно
        if matches_jump_or_call(instr.instruction.mnemonic)
            && labels.is_some()
            && let Some(rel) = extract_jump_target(instr)
        {
            let next_addr = addr + instr.bytes_len as u64;
            let abs_target = (next_addr as i64 + rel) as u64;
            let resolved = labels.and_then(|lbl| {
                lbl.iter()
                    .find(|(_, a)| **a == abs_target)
                    .map(|(name, _)| name.clone())
            });
            if let Some(label_name) = resolved {
                let mnemonic = format_mnemonic_name(instr.instruction.mnemonic);
                output.push(format!("    {} {}", mnemonic, label_name));
                continue;
            }
        }

        output.push(line);
    }

    output
}

/// Форматирует декодированные инструкции, подставляя имена меток для jump/call.
pub fn disassemble_to_text(bytes: &[u8]) -> Result<Vec<String>, DisasmError> {
    disassemble_to_text_from(bytes, 0)
}

/// Дизассемблирует буфер байт в текст, начиная с указанного смещения.
///
/// `start_offset` — смещение в исходном буфере, с которого начинать
/// декодирование (полезно для BIN-файлов, где код не с нулевого офсета).
/// Адреса инструкций в выводе будут отсчитываться от `start_offset`.
pub fn disassemble_to_text_from(
    bytes: &[u8],
    start_offset: usize,
) -> Result<Vec<String>, DisasmError> {
    let decoded = disassemble_all(bytes, Some(start_offset))?;
    let labels = synthesize_labels(&decoded);
    Ok(format_all(&decoded, Some(&labels), None))
}

// =========================================================================
// Внутренние утилиты
// =========================================================================

fn matches_jump_or_call(m: MnemonicKind) -> bool {
    matches!(
        m,
        MnemonicKind::Jmp
            | MnemonicKind::Je
            | MnemonicKind::Jne
            | MnemonicKind::Jg
            | MnemonicKind::Jge
            | MnemonicKind::Jl
            | MnemonicKind::Jle
            | MnemonicKind::Ja
            | MnemonicKind::Jae
            | MnemonicKind::Jb
            | MnemonicKind::Jbe
            | MnemonicKind::Call
    )
}

fn extract_jump_target(instr: &DecodedInstruction) -> Option<i64> {
    if instr.instruction.operands.is_empty() {
        return None;
    }
    match &instr.instruction.operands[0] {
        crate::isa::Operand::Immediate(val) => Some(*val),
        _ => None,
    }
}

fn format_mnemonic_name(m: MnemonicKind) -> String {
    format::format_instruction(&crate::isa::Instruction {
        mnemonic: m,
        operands: vec![],
    })
    .trim()
    .to_string()
}
