// Allow unused assignments - required by miette::Diagnostic derive macro
#![allow(unused_assignments)]

use alloc::{boxed::Box, string::String, sync::Arc, vec::Vec};

use miden_assembly_syntax::{
    Felt, Path, Word,
    ast::{SymbolResolutionError, constants::ConstEvalError},
    debuginfo::{SourceFile, SourceSpan},
    diagnostics::{Diagnostic, RelatedError, RelatedLabel, miette},
};

// LINKER ERROR
// ================================================================================================

/// An error which can be generated while linking modules and resolving procedure references.
#[derive(Debug, thiserror::Error, Diagnostic)]
#[non_exhaustive]
pub enum LinkerError {
    #[error("there are no modules to analyze")]
    #[diagnostic()]
    Empty,
    #[error(transparent)]
    #[diagnostic(transparent)]
    SymbolResolution(#[from] Box<SymbolResolutionError>),
    #[error(transparent)]
    #[diagnostic(transparent)]
    ConstEval(#[from] Box<ConstEvalError>),
    #[error("linking failed")]
    #[diagnostic(help("see diagnostics for details"))]
    Related {
        #[related]
        errors: Box<[RelatedError]>,
    },
    #[error("linking failed")]
    #[diagnostic(help("see diagnostics for details"))]
    Failed {
        #[related]
        labels: Box<[RelatedLabel]>,
    },
    #[error("found a cycle in the call graph, involving these procedures: {}", nodes.join(", "))]
    #[diagnostic()]
    Cycle { nodes: Box<[String]> },
    #[error("duplicate definition found for module '{path}'")]
    #[diagnostic()]
    DuplicateModule { path: Arc<Path> },
    #[error("undefined module '{path}'")]
    #[diagnostic()]
    UndefinedModule {
        #[label]
        span: SourceSpan,
        #[source_code]
        source_file: Option<Arc<SourceFile>>,
        path: Arc<Path>,
    },
    #[error("undefined item '{path}'")]
    #[diagnostic(help(
        "you might be missing an import, or the containing library has not been linked"
    ))]
    UndefinedSymbol {
        #[label]
        span: SourceSpan,
        #[source_code]
        source_file: Option<Arc<SourceFile>>,
        path: Arc<Path>,
    },
    #[error("invalid syscall: '{callee}' is not an exported kernel procedure")]
    #[diagnostic()]
    InvalidSysCallTarget {
        #[label("call occurs here")]
        span: SourceSpan,
        #[source_code]
        source_file: Option<Arc<SourceFile>>,
        callee: Arc<Path>,
    },
    #[error("invalid procedure reference: path refers to a non-procedure item")]
    #[diagnostic()]
    InvalidInvokeTarget {
        #[label("this path resolves to {path}, which is not a procedure")]
        span: SourceSpan,
        #[source_code]
        source_file: Option<Arc<SourceFile>>,
        path: Arc<Path>,
    },
    #[error("value for key {key} already present in the advice map")]
    #[diagnostic(help(
        "previous values at key were '{prev_values:?}'. Operation would have replaced them with '{new_values:?}'",
    ))]
    AdviceMapKeyAlreadyPresent {
        key: Word,
        prev_values: Vec<Felt>,
        new_values: Vec<Felt>,
    },
    #[error("undefined type alias")]
    #[diagnostic()]
    UndefinedType {
        #[label]
        span: SourceSpan,
        #[source_code]
        source_file: Option<Arc<SourceFile>>,
    },
    #[error("invalid type reference")]
    #[diagnostic(help("the item this path resolves to is not a type definition"))]
    InvalidTypeRef {
        #[label]
        span: SourceSpan,
        #[source_code]
        source_file: Option<Arc<SourceFile>>,
    },
    #[error("invalid constant reference")]
    #[diagnostic(help("the item this path resolves to is not a constant definition"))]
    InvalidConstantRef {
        #[label]
        span: SourceSpan,
        #[source_code]
        source_file: Option<Arc<SourceFile>>,
    },
}

impl From<SymbolResolutionError> for LinkerError {
    #[inline]
    fn from(value: SymbolResolutionError) -> Self {
        Self::SymbolResolution(Box::new(value))
    }
}

impl From<ConstEvalError> for LinkerError {
    #[inline]
    fn from(value: ConstEvalError) -> Self {
        Self::ConstEval(Box::new(value))
    }
}
