use rustc_abi::{ArmCall, CanonAbi, ExternAbi, InterruptKind, X86Call};

use crate::spec::{Arch, Target};

/// Mapping for ExternAbi to CanonAbi according to a Target
///
/// A maybe-transitional structure circa 2025 for hosting future experiments in
/// encapsulating arch-specific ABI lowering details to make them more testable.
#[derive(Clone, Debug)]
pub struct AbiMap {
    arch: ArchKind,
    os: OsKind,
}

/// result from trying to map an ABI
#[derive(Copy, Clone, Debug)]
pub enum AbiMapping {
    /// this ABI is exactly mapped for this platform
    Direct(CanonAbi),
    /// we don't yet warn on this, but we will
    Deprecated(CanonAbi),
    /// ABI we do not map for this platform: it must not reach codegen
    Invalid,
}

impl AbiMapping {
    /// optionally get a [CanonAbi], even if Deprecated
    pub fn into_option(self) -> Option<CanonAbi> {
        match self {
            Self::Direct(abi) | Self::Deprecated(abi) => Some(abi),
            Self::Invalid => None,
        }
    }

    /// get a [CanonAbi] even if Deprecated, panicking if Invalid
    #[track_caller]
    pub fn unwrap(self) -> CanonAbi {
        self.into_option().unwrap()
    }

    pub fn is_mapped(self) -> bool {
        self.into_option().is_some()
    }
}

impl AbiMap {
    /// create an AbiMap according to arbitrary fields on the [Target]
    pub fn from_target(target: &Target) -> Self {
        // the purpose of this little exercise is to force listing what affects these mappings
        let arch = match target.arch {
            Arch::AArch64 => ArchKind::Aarch64,
            Arch::AmdGpu => ArchKind::Amdgpu,
            Arch::Arm => ArchKind::Arm(if target.llvm_target.starts_with("thumbv8m") {
                ArmVer::ThumbV8M
            } else {
                ArmVer::Other
            }),
            Arch::Avr => ArchKind::Avr,
            Arch::LoongArch32 | Arch::LoongArch64 => ArchKind::LoongArch,
            Arch::Msp430 => ArchKind::Msp430,
            Arch::Nvptx64 => ArchKind::Nvptx,
            Arch::RiscV32 | Arch::RiscV64 => ArchKind::Riscv,
            Arch::X86 => ArchKind::X86,
            Arch::X86_64 => ArchKind::X86_64,
            _ => ArchKind::Other,
        };

        let os = if target.is_like_darwin {
            OsKind::Apple
        } else if target.is_like_windows {
            OsKind::Windows
        } else if target.is_like_vexos {
            OsKind::VEXos
        } else {
            OsKind::Other
        };

        AbiMap { arch, os }
    }

    /// lower an [ExternAbi] to a [CanonAbi] if this AbiMap allows
    pub fn canonize_abi(&self, extern_abi: ExternAbi, has_c_varargs: bool) -> AbiMapping {
        let AbiMap { os, arch } = *self;

        if extern_abi == ExternAbi::Swift {
            // Per https://www.swift.org/blog/abi-stability-and-more/, Swift's ABI
            // is only stable on Apple platforms, so we reject it elsewhere.
            match os {
                OsKind::Apple => {}
                _ => return AbiMapping::Invalid,
            }
        }

        let canon_abi = match (extern_abi, arch) {
            // infallible lowerings
            (ExternAbi::C { .. }, _) => CanonAbi::C,
            (ExternAbi::Rust | ExternAbi::RustCall, _) => CanonAbi::Rust,
            (ExternAbi::Unadjusted, _) => CanonAbi::C,

            (ExternAbi::RustCold, _) if self.os == OsKind::Windows => CanonAbi::Rust,
            (ExternAbi::RustCold, _) => CanonAbi::RustCold,
            (ExternAbi::RustPreserveNone, _) => CanonAbi::RustPreserveNone,

            (ExternAbi::Custom, _) => CanonAbi::Custom,

            (ExternAbi::Swift, _) => CanonAbi::Swift,

            (ExternAbi::System { .. }, ArchKind::X86)
                if os == OsKind::Windows && !has_c_varargs =>
            {
                CanonAbi::X86(X86Call::Stdcall)
            }
            (ExternAbi::System { .. }, ArchKind::Arm(..)) if self.os == OsKind::VEXos => {
                // Calls to VEXos APIs do not use VFP registers.
                CanonAbi::Arm(ArmCall::Aapcs)
            }
            (ExternAbi::System { .. }, _) => CanonAbi::C,

            // fallible lowerings
            /* multi-platform */
            // always and forever
            (ExternAbi::RustInvalid, _) => return AbiMapping::Invalid,

            (ExternAbi::EfiApi, ArchKind::Arm(..)) => CanonAbi::Arm(ArmCall::Aapcs),
            (ExternAbi::EfiApi, ArchKind::X86_64) => CanonAbi::X86(X86Call::Win64),
            (
                ExternAbi::EfiApi,
                ArchKind::Aarch64 | ArchKind::LoongArch | ArchKind::Riscv | ArchKind::X86,
            ) => CanonAbi::C,
            (ExternAbi::EfiApi, _) => return AbiMapping::Invalid,

            /* arm */
            (ExternAbi::Aapcs { .. }, ArchKind::Arm(..)) => CanonAbi::Arm(ArmCall::Aapcs),
            (ExternAbi::Aapcs { .. }, _) => return AbiMapping::Invalid,

            (ExternAbi::CmseNonSecureCall, ArchKind::Arm(ArmVer::ThumbV8M)) => {
                CanonAbi::Arm(ArmCall::CCmseNonSecureCall)
            }
            (ExternAbi::CmseNonSecureEntry, ArchKind::Arm(ArmVer::ThumbV8M)) => {
                CanonAbi::Arm(ArmCall::CCmseNonSecureEntry)
            }
            (ExternAbi::CmseNonSecureCall | ExternAbi::CmseNonSecureEntry, ..) => {
                return AbiMapping::Invalid;
            }

            /* gpu */
            (ExternAbi::PtxKernel, ArchKind::Nvptx) => CanonAbi::GpuKernel,
            (ExternAbi::GpuKernel, ArchKind::Amdgpu | ArchKind::Nvptx) => CanonAbi::GpuKernel,
            (ExternAbi::PtxKernel | ExternAbi::GpuKernel, _) => return AbiMapping::Invalid,

            /* x86 */
            (ExternAbi::Cdecl { .. }, ArchKind::X86) => CanonAbi::C,
            (ExternAbi::Cdecl { .. }, _) => return AbiMapping::Deprecated(CanonAbi::C),

            (ExternAbi::Fastcall { .. }, ArchKind::X86) => CanonAbi::X86(X86Call::Fastcall),
            (ExternAbi::Fastcall { .. }, _) if os == OsKind::Windows => {
                return AbiMapping::Deprecated(CanonAbi::C);
            }
            (ExternAbi::Fastcall { .. }, _) => return AbiMapping::Invalid,

            (ExternAbi::Stdcall { .. }, ArchKind::X86) => CanonAbi::X86(X86Call::Stdcall),
            (ExternAbi::Stdcall { .. }, _) if os == OsKind::Windows => {
                return AbiMapping::Deprecated(CanonAbi::C);
            }
            (ExternAbi::Stdcall { .. }, _) => return AbiMapping::Invalid,

            (ExternAbi::Thiscall { .. }, ArchKind::X86) => CanonAbi::X86(X86Call::Thiscall),
            (ExternAbi::Thiscall { .. }, _) => return AbiMapping::Invalid,

            (ExternAbi::Vectorcall { .. }, ArchKind::X86 | ArchKind::X86_64) => {
                CanonAbi::X86(X86Call::Vectorcall)
            }
            (ExternAbi::Vectorcall { .. }, _) => return AbiMapping::Invalid,

            (ExternAbi::SysV64 { .. }, ArchKind::X86_64) => CanonAbi::X86(X86Call::SysV64),
            (ExternAbi::Win64 { .. }, ArchKind::X86_64) => CanonAbi::X86(X86Call::Win64),
            (ExternAbi::SysV64 { .. } | ExternAbi::Win64 { .. }, _) => return AbiMapping::Invalid,

            /* interrupts */
            (ExternAbi::AvrInterrupt, ArchKind::Avr) => CanonAbi::Interrupt(InterruptKind::Avr),
            (ExternAbi::AvrNonBlockingInterrupt, ArchKind::Avr) => {
                CanonAbi::Interrupt(InterruptKind::AvrNonBlocking)
            }
            (ExternAbi::Msp430Interrupt, ArchKind::Msp430) => {
                CanonAbi::Interrupt(InterruptKind::Msp430)
            }
            (ExternAbi::RiscvInterruptM, ArchKind::Riscv) => {
                CanonAbi::Interrupt(InterruptKind::RiscvMachine)
            }
            (ExternAbi::RiscvInterruptS, ArchKind::Riscv) => {
                CanonAbi::Interrupt(InterruptKind::RiscvSupervisor)
            }
            (ExternAbi::X86Interrupt, ArchKind::X86 | ArchKind::X86_64) => {
                CanonAbi::Interrupt(InterruptKind::X86)
            }
            (
                ExternAbi::AvrInterrupt
                | ExternAbi::AvrNonBlockingInterrupt
                | ExternAbi::Msp430Interrupt
                | ExternAbi::RiscvInterruptM
                | ExternAbi::RiscvInterruptS
                | ExternAbi::X86Interrupt,
                _,
            ) => return AbiMapping::Invalid,
        };

        AbiMapping::Direct(canon_abi)
    }
}

#[derive(Debug, PartialEq, Copy, Clone)]
enum ArchKind {
    Aarch64,
    Amdgpu,
    Arm(ArmVer),
    Avr,
    LoongArch,
    Msp430,
    Nvptx,
    Riscv,
    X86,
    X86_64,
    /// Architectures which don't need other considerations for ABI lowering
    Other,
}

#[derive(Debug, PartialEq, Copy, Clone)]
enum OsKind {
    Apple,
    Windows,
    VEXos,
    Other,
}

#[derive(Debug, PartialEq, Copy, Clone)]
enum ArmVer {
    ThumbV8M,
    Other,
}
