use std::fmt;

use rustc_abi::ExternAbi;
use rustc_feature::Features;
use rustc_session::Session;
use rustc_session::parse::feature_err;
use rustc_span::symbol::sym;
use rustc_span::{Span, Symbol};

pub(crate) fn enabled_names(features: &rustc_feature::Features, span: Span) -> Vec<&'static str> {
    ExternAbi::ALL_VARIANTS
        .into_iter()
        .filter(|abi| extern_abi_enabled(features, span, **abi).is_ok())
        .map(|abi| abi.as_str())
        .collect()
}

pub(crate) fn extern_abi_enabled(
    features: &rustc_feature::Features,
    span: Span,
    abi: ExternAbi,
) -> Result<(), UnstableAbi> {
    extern_abi_stability(abi).or_else(|unstable @ UnstableAbi { feature, .. }| {
        if features.enabled(feature) || span.allows_unstable(feature) {
            Ok(())
        } else {
            Err(unstable)
        }
    })
}

pub(crate) fn gate_unstable_abi(sess: &Session, features: &Features, span: Span, abi: ExternAbi) {
    match extern_abi_enabled(features, span, abi) {
        Ok(_) => (),
        Err(unstable_abi) => {
            let explain = unstable_abi.to_string();
            feature_err(sess, unstable_abi.feature, span, explain).emit();
        }
    }
}

pub struct UnstableAbi {
    abi: ExternAbi,
    feature: Symbol,
    explain: GateReason,
}

enum GateReason {
    Experimental,
    ImplDetail,
}

impl fmt::Display for UnstableAbi {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let Self { abi, .. } = self;
        match self.explain {
            GateReason::Experimental => {
                write!(f, "the extern {abi} ABI is experimental and subject to change")
            }
            GateReason::ImplDetail => {
                write!(f, "the extern {abi} ABI is an implementation detail and perma-unstable")
            }
        }
    }
}

pub fn extern_abi_stability(abi: ExternAbi) -> Result<(), UnstableAbi> {
    match abi {
        // stable ABIs
        ExternAbi::Rust
        | ExternAbi::C { .. }
        | ExternAbi::Cdecl { .. }
        | ExternAbi::Stdcall { .. }
        | ExternAbi::Fastcall { .. }
        | ExternAbi::Thiscall { .. }
        | ExternAbi::Aapcs { .. }
        | ExternAbi::Win64 { .. }
        | ExternAbi::SysV64 { .. }
        | ExternAbi::System { .. }
        | ExternAbi::EfiApi => Ok(()),
        ExternAbi::Unadjusted => {
            Err(UnstableAbi { abi, feature: sym::abi_unadjusted, explain: GateReason::ImplDetail })
        }
        // experimental
        ExternAbi::Vectorcall { .. } => Err(UnstableAbi {
            abi,
            feature: sym::abi_vectorcall,
            explain: GateReason::Experimental,
        }),
        ExternAbi::RustCall => Err(UnstableAbi {
            abi,
            feature: sym::unboxed_closures,
            explain: GateReason::Experimental,
        }),
        ExternAbi::RustCold => {
            Err(UnstableAbi { abi, feature: sym::rust_cold_cc, explain: GateReason::Experimental })
        }
        ExternAbi::RustPreserveNone => Err(UnstableAbi {
            abi,
            feature: sym::rust_preserve_none_cc,
            explain: GateReason::Experimental,
        }),
        ExternAbi::RustInvalid => {
            Err(UnstableAbi { abi, feature: sym::rustc_attrs, explain: GateReason::ImplDetail })
        }
        ExternAbi::GpuKernel => Err(UnstableAbi {
            abi,
            feature: sym::abi_gpu_kernel,
            explain: GateReason::Experimental,
        }),
        ExternAbi::PtxKernel => {
            Err(UnstableAbi { abi, feature: sym::abi_ptx, explain: GateReason::Experimental })
        }
        ExternAbi::Msp430Interrupt => Err(UnstableAbi {
            abi,
            feature: sym::abi_msp430_interrupt,
            explain: GateReason::Experimental,
        }),
        ExternAbi::X86Interrupt => Err(UnstableAbi {
            abi,
            feature: sym::abi_x86_interrupt,
            explain: GateReason::Experimental,
        }),
        ExternAbi::AvrInterrupt | ExternAbi::AvrNonBlockingInterrupt => Err(UnstableAbi {
            abi,
            feature: sym::abi_avr_interrupt,
            explain: GateReason::Experimental,
        }),
        ExternAbi::RiscvInterruptM | ExternAbi::RiscvInterruptS => Err(UnstableAbi {
            abi,
            feature: sym::abi_riscv_interrupt,
            explain: GateReason::Experimental,
        }),
        ExternAbi::CmseNonSecureCall => Err(UnstableAbi {
            abi,
            feature: sym::abi_cmse_nonsecure_call,
            explain: GateReason::Experimental,
        }),
        ExternAbi::CmseNonSecureEntry => Err(UnstableAbi {
            abi,
            feature: sym::cmse_nonsecure_entry,
            explain: GateReason::Experimental,
        }),
        ExternAbi::Custom => {
            Err(UnstableAbi { abi, feature: sym::abi_custom, explain: GateReason::Experimental })
        }
    }
}
