//! Ошибки и варнинги AST-анализа в этом модуле.
//!
//! [`ASTAnalyseErrorKind`] различает две категории находок:
//! - [`ASTAnalyseErrorKind::Error`] — находки, означающие, что программа
//!   гарантированно ведёт себя не так, как задумано, либо вообще не может
//!   быть корректно собрана (дублирующаяся метка, деление на ноль). Сам
//!   AST-анализатор всё равно их не блокирует (см. `ast_analysis::mod`) —
//!   разделение чисто семантическое, для вывода и фильтрации находок.
//! - [`ASTAnalyseErrorKind::Warning`] — стилистические/потенциально
//!   нежелательные, но синтаксически корректные находки.
use std::fmt::Display;

use crate::{isa::Instruction, lexer::token::Span, str_pool::StrId};

/// Находки, которые означают, что программа гарантированно ошибочна.
#[derive(Debug)]
pub enum ASTAnalyseErrorVariant {
    /// # Дублирующаяся метка
    ///
    /// Одно и то же имя метки определено более одного раза. Резолвинг
    /// переходов не может однозначно определить, какое определение имелось
    /// в виду.
    ///
    /// ## Пример
    /// ```x86asm
    /// foo:
    ///     ret
    /// foo: ; <-- повторное определение `foo`
    ///     ret
    /// ```
    DuplicateLabel {
        name: StrId,
        /// Позиция первого (исходного) определения метки.
        first_pos: Span,
    },

    /// # Деление на ноль
    ///
    /// `div`/`idiv` с буквальным нулём в качестве делителя — гарантированный
    /// сбой (исключение деления на ноль) при выполнении.
    ///
    /// ## Пример
    /// ```x86asm
    /// mov rax, 10
    /// div 0 ; <-- гарантированный сбой
    /// ```
    DivisionByZero(Instruction),
}

impl Display for ASTAnalyseErrorVariant {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::DuplicateLabel { .. } => write!(f, "label is defined more than once"),
            Self::DivisionByZero(_) => write!(f, "division by zero"),
        }
    }
}

/// Стилистические/потенциально нежелательные, но синтаксически корректные находки.
#[derive(Debug)]
pub enum ASTAnalyseWarningKind {
    /// # Ненужная инструкция
    ///
    /// ## Пример
    /// ```x86asm
    /// mov rdi, 42 ; <-- НЕНУЖНО
    /// mov rdi 0
    /// ```
    UnneededInstruction {
        /// Инструкция, которая будет перезаписана и не несёт смысла.
        unneeded: Instruction,
        /// Позиция ненужной инструкции в исходном коде.
        unneeded_pos: Span,
        /// Инструкция, которая фактически перезаписывает значение.
        useful: Instruction,
        /// Позиция перезаписывающей (нужной) инструкции в исходном коде.
        useful_pos: Span,
    },

    /// # Длинное имя метки
    ///
    /// Название метки считается длинным, если его длина >= 40 символов.
    ///
    /// ## Пример
    /// ```x86asm
    /// PushAllLocalGitCommitsToRemoteRepository:
    ///     mov rsi, 242
    ///     ret
    /// ```
    LabelNameIsTooLong(String),

    /// # Недостижимый код
    ///
    /// Инструкции после безусловного перехода (`jmp`) или `ret`, перед
    /// которыми до следующей метки нет ни одной точки входа — они
    /// синтаксически гарантированно никогда не выполнятся.
    ///
    /// ## Пример
    /// ```x86asm
    /// jmp end
    /// mov rax, 1 ; <-- недостижимо
    /// end:
    ///     ret
    /// ```
    DeadCodeAfterTerminator {
        /// Инструкция-терминатор (`jmp`/`ret`), после которой код недостижим.
        terminator: Instruction,
        terminator_pos: Span,
        /// Первая недостижимая инструкция.
        dead_instruction: Instruction,
        dead_pos: Span,
    },

    /// # Переход на самого себя
    ///
    /// Безусловный `jmp` сразу на метку, под которой он сам находится —
    /// бесконечный цикл без какого-либо прогресса.
    ///
    /// ## Пример
    /// ```x86asm
    /// spin:
    ///     jmp spin ; <-- бесконечный цикл
    /// ```
    SelfLoop { label: StrId, jmp_pos: Span },

    /// # Результат сравнения не используется
    ///
    /// `cmp`/`test` устанавливает флаги, но до следующей метки/конца
    /// программы ни один условный переход их не читает — сравнение
    /// бессмысленно.
    ///
    /// ## Пример
    /// ```x86asm
    /// cmp rax, 5 ; <-- результат никогда не проверяется
    /// mov rdi, 1
    /// ret
    /// ```
    UnusedComparison(Instruction),

    /// # Бессмысленное самоприсваивание
    ///
    /// `mov reg, reg` с одним и тем же регистром в обоих операндах —
    /// не имеет никакого эффекта.
    ///
    /// ## Пример
    /// ```x86asm
    /// mov rax, rax ; <-- ничего не делает
    /// ```
    RedundantSelfAssign(Instruction),

    /// # Бессмысленная арифметика
    ///
    /// `add`/`sub` с нулевым иммедиатом — не имеет никакого эффекта.
    ///
    /// ## Пример
    /// ```x86asm
    /// add rax, 0 ; <-- ничего не делает
    /// ```
    NoOpArithmetic(Instruction),

    /// # Несбалансированный стек
    ///
    /// Суммарное количество `push` не совпадает с суммарным количеством
    /// `pop` за всю программу. Считается глобально, без учёта `call`/`jmp`
    /// между ними (это эвристика, а не точный анализ стека вызовов).
    ///
    /// ## Пример
    /// ```x86asm
    /// push rax
    /// push rbx
    /// pop rbx
    /// ret ; <-- `rax` остался запушен и никогда не выгружен
    /// ```
    UnbalancedStack { pushes: usize, pops: usize },
}

impl Display for ASTAnalyseWarningKind {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::UnneededInstruction { .. } => write!(f, "unnecessary instruction"),
            Self::LabelNameIsTooLong(name) => write!(f, "label name is too long: `{name}`"),
            Self::DeadCodeAfterTerminator { .. } => write!(f, "unreachable code"),
            Self::SelfLoop { .. } => write!(f, "infinite loop with no progress"),
            Self::UnusedComparison(_) => write!(f, "result of comparison is never used"),
            Self::RedundantSelfAssign(_) => write!(f, "instruction has no effect"),
            Self::NoOpArithmetic(_) => write!(f, "instruction has no effect"),
            Self::UnbalancedStack { pushes, pops } => {
                write!(f, "unbalanced stack: {pushes} push(es) vs {pops} pop(s)")
            }
        }
    }
}

#[derive(Debug)]
pub enum ASTAnalyseErrorKind {
    Error(ASTAnalyseErrorVariant),
    Warning(ASTAnalyseWarningKind),
}

impl Display for ASTAnalyseErrorKind {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Error(err) => write!(f, "{err}"),
            Self::Warning(warn) => write!(f, "{warn}"),
        }
    }
}

#[derive(Debug)]
pub struct ASTAnalyseError {
    pub kind: ASTAnalyseErrorKind,
    pub pos: Span,
}

impl ASTAnalyseError {
    pub fn new(kind: ASTAnalyseErrorKind, pos: Span) -> Self {
        Self { kind, pos }
    }

    /// `true`, если находка относится к категории [`ASTAnalyseErrorKind::Error`]
    /// (гарантированная проблема), а не [`ASTAnalyseErrorKind::Warning`]
    /// (стилистическая находка).
    pub fn is_error(&self) -> bool {
        matches!(self.kind, ASTAnalyseErrorKind::Error(_))
    }

    /// `true`, если находка — варнинг (см. [`Self::is_error`]).
    pub fn is_warning(&self) -> bool {
        !self.is_error()
    }
}

impl Display for ASTAnalyseError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.kind)
    }
}
