use std::num::NonZero;

use rustc_macros::{BlobDecodable, Decodable, Encodable, PrintAttribute, StableHash};
use rustc_span::{ErrorGuaranteed, Symbol, sym};

use crate::RustcVersion;
use crate::attrs::PrintAttribute;

/// The version placeholder that recently stabilized features contain inside the
/// `since` field of the `#[stable]` attribute.
///
/// For more, see [this pull request](https://github.com/rust-lang/rust/pull/100591).
pub const VERSION_PLACEHOLDER: &str = concat!("CURRENT_RUSTC_VERSIO", "N");
// Note that the `concat!` macro above prevents `src/tools/replace-version-placeholder` from
// replacing the constant with the current version. Hardcoding the tool to skip this file doesn't
// work as the file can (and at some point will) be moved around.
//
// Turning the `concat!` macro into a string literal will make Pietro cry. That'd be sad :(

/// Represents the following attributes:
///
/// - `#[stable]`
/// - `#[unstable]`
#[derive(Encodable, BlobDecodable, Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[derive(StableHash, PrintAttribute)]
pub struct Stability {
    pub level: StabilityLevel,
    pub feature: Symbol,
}

impl Stability {
    pub fn is_unstable(&self) -> bool {
        self.level.is_unstable()
    }

    pub fn is_stable(&self) -> bool {
        self.level.is_stable()
    }

    pub fn stable_since(&self) -> Option<StableSince> {
        self.level.stable_since()
    }
}

/// Represents the `#[rustc_const_unstable]` and `#[rustc_const_stable]` attributes.
#[derive(Encodable, Decodable, Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[derive(StableHash, PrintAttribute)]
pub struct ConstStability {
    pub level: StabilityLevel,
    pub feature: Symbol,
    /// whether the function has a `#[rustc_promotable]` attribute
    pub promotable: bool,
    /// This is true iff the `const_stable_indirect` attribute is present.
    pub const_stable_indirect: bool,
}

impl ConstStability {
    pub fn from_partial(
        PartialConstStability { level, feature, promotable }: PartialConstStability,
        const_stable_indirect: bool,
    ) -> Self {
        Self { const_stable_indirect, level, feature, promotable }
    }

    /// The stability assigned to unmarked items when -Zforce-unstable-if-unmarked is set.
    pub fn unmarked(const_stable_indirect: bool, regular_stab: Stability) -> Self {
        Self {
            feature: regular_stab.feature,
            promotable: false,
            level: regular_stab.level,
            const_stable_indirect,
        }
    }

    pub fn is_const_unstable(&self) -> bool {
        self.level.is_unstable()
    }

    pub fn is_const_stable(&self) -> bool {
        self.level.is_stable()
    }
}

/// Excludes `const_stable_indirect`. This is necessary because when `-Zforce-unstable-if-unmarked`
/// is set, we need to encode standalone `#[rustc_const_stable_indirect]` attributes
#[derive(Encodable, Decodable, Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[derive(StableHash, PrintAttribute)]
pub struct PartialConstStability {
    pub level: StabilityLevel,
    pub feature: Symbol,
    /// whether the function has a `#[rustc_promotable]` attribute
    pub promotable: bool,
}

impl PartialConstStability {
    pub fn is_const_unstable(&self) -> bool {
        self.level.is_unstable()
    }

    pub fn is_const_stable(&self) -> bool {
        self.level.is_stable()
    }
}

/// The available stability levels.
#[derive(Encodable, BlobDecodable, PartialEq, Copy, Clone, Debug, Eq, Hash)]
#[derive(StableHash, PrintAttribute)]
pub enum StabilityLevel {
    /// `#[unstable]`
    Unstable {
        /// Reason for the current stability level.
        reason: UnstableReason,
        /// Relevant `rust-lang/rust` issue.
        issue: Option<NonZero<u32>>,
        /// If part of a feature is stabilized and a new feature is added for the remaining parts,
        /// then the `implied_by` attribute is used to indicate which now-stable feature previously
        /// contained an item.
        ///
        /// ```pseudo-Rust
        /// #[unstable(feature = "foo", issue = "...")]
        /// fn foo() {}
        /// #[unstable(feature = "foo", issue = "...")]
        /// fn foobar() {}
        /// ```
        ///
        /// ...becomes...
        ///
        /// ```pseudo-Rust
        /// #[stable(feature = "foo", since = "1.XX.X")]
        /// fn foo() {}
        /// #[unstable(feature = "foobar", issue = "...", implied_by = "foo")]
        /// fn foobar() {}
        /// ```
        implied_by: Option<Symbol>,
        old_name: Option<Symbol>,
    },
    /// `#[stable]`
    Stable {
        /// Rust release which stabilized this feature.
        since: StableSince,
        /// This is `Some` if this item allowed to be referred to on stable via unstable modules;
        /// the `Symbol` is the deprecation message printed in that case.
        allowed_through_unstable_modules: Option<Symbol>,
    },
}

/// Rust release in which a feature is stabilized.
#[derive(Encodable, BlobDecodable, PartialEq, Copy, Clone, Debug, Eq, PartialOrd, Ord, Hash)]
#[derive(StableHash, PrintAttribute)]
pub enum StableSince {
    /// also stores the original symbol for printing
    Version(RustcVersion),
    /// Stabilized in the upcoming version, whatever number that is.
    Current,
    /// Failed to parse a stabilization version.
    Err(ErrorGuaranteed),
}

impl StabilityLevel {
    pub fn is_unstable(&self) -> bool {
        matches!(self, StabilityLevel::Unstable { .. })
    }
    pub fn is_stable(&self) -> bool {
        matches!(self, StabilityLevel::Stable { .. })
    }
    pub fn stable_since(&self) -> Option<StableSince> {
        match *self {
            StabilityLevel::Stable { since, .. } => Some(since),
            StabilityLevel::Unstable { .. } => None,
        }
    }
}

#[derive(Encodable, BlobDecodable, PartialEq, Copy, Clone, Debug, Eq, Hash)]
#[derive(StableHash, PrintAttribute)]
pub enum UnstableReason {
    None,
    Default,
    Some(Symbol),
}

/// Represents the `#[rustc_default_body_unstable]` attribute.
#[derive(Encodable, Decodable, Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[derive(StableHash, PrintAttribute)]
pub struct DefaultBodyStability {
    pub level: StabilityLevel,
    pub feature: Symbol,
}

impl UnstableReason {
    pub fn from_opt_reason(reason: Option<Symbol>) -> Self {
        // UnstableReason::Default constructed manually
        match reason {
            Some(r) => Self::Some(r),
            None => Self::None,
        }
    }

    pub fn to_opt_reason(&self) -> Option<Symbol> {
        match self {
            Self::None => None,
            Self::Default => Some(sym::unstable_location_reason_default),
            Self::Some(r) => Some(*r),
        }
    }
}
