//! LLVM diagnostic reports.

use libc::c_uint;
use rustc_span::InnerSpan;

pub(crate) use self::Diagnostic::*;
use self::OptimizationDiagnosticKind::*;
use super::{DiagnosticInfo, SMDiagnostic};
use crate::llvm::Value;

#[derive(Copy, Clone, Debug)]
pub(crate) enum OptimizationDiagnosticKind {
    OptimizationRemark,
    OptimizationMissed,
    OptimizationAnalysis,
    OptimizationAnalysisFPCommute,
    OptimizationAnalysisAliasing,
    OptimizationFailure,
    OptimizationRemarkOther,
}

pub(crate) struct OptimizationDiagnostic<'ll> {
    pub kind: OptimizationDiagnosticKind,
    pub pass_name: String,
    #[expect(dead_code)]
    pub function: &'ll Value,
    pub line: c_uint,
    pub column: c_uint,
    pub filename: String,
    pub message: String,
}

impl<'ll> OptimizationDiagnostic<'ll> {
    unsafe fn unpack(kind: OptimizationDiagnosticKind, di: &'ll DiagnosticInfo) -> Self {
        let mut function = None;
        let mut line = 0;
        let mut column = 0;

        let mut message = None;
        let mut filename = None;
        let pass_name = super::build_string(|pass_name| {
            message = super::build_string(|message| {
                filename = super::build_string(|filename| unsafe {
                    super::LLVMRustUnpackOptimizationDiagnostic(
                        di,
                        pass_name,
                        &mut function,
                        &mut line,
                        &mut column,
                        filename,
                        message,
                    )
                })
                .ok()
            })
            .ok()
        })
        .ok();

        let mut filename = filename.unwrap_or_default();
        if filename.is_empty() {
            filename.push_str("<unknown file>");
        }

        OptimizationDiagnostic {
            kind,
            pass_name: pass_name.expect("got a non-UTF8 pass name from LLVM"),
            function: function.unwrap(),
            line,
            column,
            filename,
            message: message.expect("got a non-UTF8 OptimizationDiagnostic message from LLVM"),
        }
    }
}

pub(crate) struct SrcMgrDiagnostic {
    pub level: super::DiagnosticLevel,
    pub message: String,
    pub source: Option<(String, Vec<InnerSpan>)>,
}

impl SrcMgrDiagnostic {
    pub(crate) unsafe fn unpack(diag: &SMDiagnostic) -> SrcMgrDiagnostic {
        // Recover the post-substitution assembly code from LLVM for better
        // diagnostics.
        let mut have_source = false;
        let mut buffer = String::new();
        let mut level = super::DiagnosticLevel::Error;
        let mut loc = 0;
        let mut ranges = [0; 8];
        let mut num_ranges = ranges.len() / 2;
        let message = super::build_string(|message| {
            buffer = super::build_string(|buffer| unsafe {
                have_source = super::LLVMRustUnpackSMDiagnostic(
                    diag,
                    message,
                    buffer,
                    &mut level,
                    &mut loc,
                    ranges.as_mut_ptr(),
                    &mut num_ranges,
                );
            })
            .expect("non-UTF8 inline asm");
        })
        .expect("non-UTF8 SMDiagnostic");

        SrcMgrDiagnostic {
            message,
            level,
            source: have_source.then(|| {
                let mut spans = vec![InnerSpan::new(loc as usize, loc as usize)];
                for i in 0..num_ranges {
                    spans.push(InnerSpan::new(ranges[i * 2] as usize, ranges[i * 2 + 1] as usize));
                }
                (buffer, spans)
            }),
        }
    }
}

#[derive(Clone)]
pub(crate) struct InlineAsmDiagnostic {
    pub level: super::DiagnosticLevel,
    pub cookie: u64,
    pub message: String,
    pub source: Option<(String, Vec<InnerSpan>)>,
}

impl InlineAsmDiagnostic {
    unsafe fn unpackInlineAsm(di: &DiagnosticInfo) -> Self {
        let mut cookie = 0;
        let mut message = None;
        let mut level = super::DiagnosticLevel::Error;

        unsafe {
            super::LLVMRustUnpackInlineAsmDiagnostic(di, &mut level, &mut cookie, &mut message);
        }

        InlineAsmDiagnostic {
            level,
            cookie,
            message: super::twine_to_string(message.unwrap()),
            source: None,
        }
    }

    unsafe fn unpackSrcMgr(di: &DiagnosticInfo) -> Self {
        let mut cookie = 0;
        let smdiag =
            unsafe { SrcMgrDiagnostic::unpack(super::LLVMRustGetSMDiagnostic(di, &mut cookie)) };
        InlineAsmDiagnostic {
            level: smdiag.level,
            cookie,
            message: smdiag.message,
            source: smdiag.source,
        }
    }
}

pub(crate) enum Diagnostic<'ll> {
    Optimization(OptimizationDiagnostic<'ll>),
    InlineAsm(InlineAsmDiagnostic),
    PGO(&'ll DiagnosticInfo),
    Linker(&'ll DiagnosticInfo),
    Unsupported(&'ll DiagnosticInfo),

    /// LLVM has other types that we do not wrap here.
    #[expect(dead_code)]
    UnknownDiagnostic(&'ll DiagnosticInfo),
}

impl<'ll> Diagnostic<'ll> {
    pub(crate) unsafe fn unpack(di: &'ll DiagnosticInfo) -> Self {
        use super::DiagnosticKind as Dk;

        unsafe {
            let kind = super::LLVMRustGetDiagInfoKind(di);
            match kind {
                Dk::InlineAsm => InlineAsm(InlineAsmDiagnostic::unpackInlineAsm(di)),

                Dk::OptimizationRemark => {
                    Optimization(OptimizationDiagnostic::unpack(OptimizationRemark, di))
                }
                Dk::OptimizationRemarkOther => {
                    Optimization(OptimizationDiagnostic::unpack(OptimizationRemarkOther, di))
                }
                Dk::OptimizationRemarkMissed => {
                    Optimization(OptimizationDiagnostic::unpack(OptimizationMissed, di))
                }

                Dk::OptimizationRemarkAnalysis => {
                    Optimization(OptimizationDiagnostic::unpack(OptimizationAnalysis, di))
                }

                Dk::OptimizationRemarkAnalysisFPCommute => {
                    Optimization(OptimizationDiagnostic::unpack(OptimizationAnalysisFPCommute, di))
                }

                Dk::OptimizationRemarkAnalysisAliasing => {
                    Optimization(OptimizationDiagnostic::unpack(OptimizationAnalysisAliasing, di))
                }

                Dk::OptimizationFailure => {
                    Optimization(OptimizationDiagnostic::unpack(OptimizationFailure, di))
                }

                Dk::PGOProfile => PGO(di),
                Dk::Linker => Linker(di),
                Dk::Unsupported => Unsupported(di),

                Dk::SrcMgr => InlineAsm(InlineAsmDiagnostic::unpackSrcMgr(di)),

                _ => UnknownDiagnostic(di),
            }
        }
    }
}
