#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

use ruff_text_size::{Ranged, TextSize};

use crate::edit::Edit;

/// Indicates if a fix can be applied.
#[derive(
    Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, is_macro::Is, get_size2::GetSize,
)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
pub enum Applicability {
    /// The fix is unsafe and should only be displayed for manual application by the user.
    ///
    /// The fix is likely to be incorrect or the resulting code may have invalid syntax.
    DisplayOnly,

    /// The fix is unsafe and should only be applied with user opt-in.
    ///
    /// The fix may be what the user intended, but it is uncertain. The resulting code will have
    /// valid syntax, but may lead to a change in runtime behavior, the removal of user comments,
    /// or both.
    Unsafe,

    /// The fix is safe and can always be applied.
    ///
    /// The fix is definitely what the user intended, or maintains the exact meaning of the code.
    /// User comments are preserved, unless the fix removes an entire statement or expression.
    Safe,
}

/// Indicates the level of isolation required to apply a fix.
#[derive(Default, Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum IsolationLevel {
    /// The fix should be applied as long as no other fixes in the same group have been applied.
    Group(u32),
    /// The fix should be applied as long as it does not overlap with any other fixes.
    #[default]
    NonOverlapping,
}

/// A collection of [`Edit`] elements to be applied to a source file.
#[derive(Debug, PartialEq, Eq, Clone, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Fix {
    /// The [`Edit`] elements to be applied, sorted by [`Edit::start`] in ascending order.
    edits: Vec<Edit>,
    /// The [`Applicability`] of the fix.
    applicability: Applicability,
    /// The [`IsolationLevel`] of the fix.
    isolation_level: IsolationLevel,
}

impl Fix {
    /// Create a new [`Fix`] that is [safe](Applicability::Safe) to apply from an [`Edit`] element.
    pub fn safe_edit(edit: Edit) -> Self {
        Self {
            edits: vec![edit],
            applicability: Applicability::Safe,
            isolation_level: IsolationLevel::default(),
        }
    }

    /// Create a new [`Fix`] that is [safe](Applicability::Safe) to apply from multiple [`Edit`] elements.
    pub fn safe_edits(edit: Edit, rest: impl IntoIterator<Item = Edit>) -> Self {
        let mut edits: Vec<Edit> = std::iter::once(edit).chain(rest).collect();
        edits.sort_by_key(|edit| (edit.start(), edit.end()));
        Self {
            edits,
            applicability: Applicability::Safe,
            isolation_level: IsolationLevel::default(),
        }
    }

    /// Create a new [`Fix`] that is [unsafe](Applicability::Unsafe) to apply from an [`Edit`] element.
    pub fn unsafe_edit(edit: Edit) -> Self {
        Self {
            edits: vec![edit],
            applicability: Applicability::Unsafe,
            isolation_level: IsolationLevel::default(),
        }
    }

    /// Create a new [`Fix`] that is [unsafe](Applicability::Unsafe) to apply from multiple [`Edit`] elements.
    pub fn unsafe_edits(edit: Edit, rest: impl IntoIterator<Item = Edit>) -> Self {
        let mut edits: Vec<Edit> = std::iter::once(edit).chain(rest).collect();
        edits.sort_by_key(|edit| (edit.start(), edit.end()));
        Self {
            edits,
            applicability: Applicability::Unsafe,
            isolation_level: IsolationLevel::default(),
        }
    }

    /// Create a new [`Fix`] that should only [display](Applicability::DisplayOnly) and not apply from an [`Edit`] element .
    pub fn display_only_edit(edit: Edit) -> Self {
        Self {
            edits: vec![edit],
            applicability: Applicability::DisplayOnly,
            isolation_level: IsolationLevel::default(),
        }
    }

    /// Create a new [`Fix`] with the specified [`Applicability`] to apply an [`Edit`] element.
    pub fn applicable_edit(edit: Edit, applicability: Applicability) -> Self {
        Self {
            edits: vec![edit],
            applicability,
            isolation_level: IsolationLevel::default(),
        }
    }

    /// Create a new [`Fix`] with the specified [`Applicability`] to apply multiple [`Edit`] elements.
    pub fn applicable_edits(
        edit: Edit,
        rest: impl IntoIterator<Item = Edit>,
        applicability: Applicability,
    ) -> Self {
        let mut edits: Vec<Edit> = std::iter::once(edit).chain(rest).collect();
        edits.sort_by_key(|edit| (edit.start(), edit.end()));
        Self {
            edits,
            applicability,
            isolation_level: IsolationLevel::default(),
        }
    }

    /// Return the [`TextSize`] of the first [`Edit`] in the [`Fix`].
    pub fn min_start(&self) -> Option<TextSize> {
        self.edits.first().map(Edit::start)
    }

    /// Return a slice of the [`Edit`] elements in the [`Fix`], sorted by [`Edit::start`] in ascending order.
    pub fn edits(&self) -> &[Edit] {
        &self.edits
    }

    pub fn into_edits(self) -> Vec<Edit> {
        self.edits
    }

    /// Return the [`Applicability`] of the [`Fix`].
    pub fn applicability(&self) -> Applicability {
        self.applicability
    }

    /// Return the [`IsolationLevel`] of the [`Fix`].
    pub fn isolation(&self) -> IsolationLevel {
        self.isolation_level
    }

    /// Create a new [`Fix`] with the given [`IsolationLevel`].
    #[must_use]
    pub fn isolate(mut self, isolation: IsolationLevel) -> Self {
        self.isolation_level = isolation;
        self
    }

    /// Return [`true`] if this [`Fix`] should be applied with at a given [`Applicability`].
    pub fn applies(&self, applicability: Applicability) -> bool {
        self.applicability >= applicability
    }

    /// Create a new [`Fix`] with the given [`Applicability`].
    #[must_use]
    pub fn with_applicability(mut self, applicability: Applicability) -> Self {
        self.applicability = applicability;
        self
    }
}
