use crate::Workspace;
use fancy_display::FancyDisplay;
use itertools::Itertools;
use miette::{Diagnostic, LabeledSpan};
use pixi_manifest::{EnvironmentName, TaskName};
use rattler_conda_types::Platform;
use std::error::Error;
use std::fmt::{Display, Formatter};
use std::path::PathBuf;
use thiserror::Error;

/// An error that occurs when data is requested for a platform that is not supported.
/// TODO: Make this error better by also explaining to the user why a certain platform was not
///  supported and with suggestions as how to fix it.
#[derive(Debug, Clone)]
pub struct UnsupportedPlatformError {
    /// Platforms supported by the environment
    pub environments_platforms: Vec<Platform>,

    /// The environment that the platform is not supported for.
    pub environment: EnvironmentName,

    /// The platform that was requested
    pub platform: Platform,
}

impl Error for UnsupportedPlatformError {}

impl Display for UnsupportedPlatformError {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match &self.environment {
            EnvironmentName::Default => {
                write!(
                    f,
                    "The workspace does not support '{}'.\n\
                    Add it with 'pixi workspace platform add {}'.",
                    self.platform, self.platform
                )
            }
            EnvironmentName::Named(name) => write!(
                f,
                "the environment '{}' does not support '{}'",
                name, self.platform
            ),
        }
    }
}

impl Diagnostic for UnsupportedPlatformError {
    fn code(&self) -> Option<Box<dyn Display + '_>> {
        Some(Box::new("unsupported-platform".to_string()))
    }

    fn help(&self) -> Option<Box<dyn Display + '_>> {
        Some(Box::new(format!(
            "supported platforms are {}",
            self.environments_platforms.iter().format(", ")
        )))
    }

    fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
        None
    }
}

/// Errors that can occur while resolving workspace build variants.
#[derive(Debug, Diagnostic, Error)]
pub enum VariantsError {
    #[error("failed to read variant file '{path}'")]
    #[diagnostic(code(workspace::variants::read_file))]
    ReadVariantFile {
        /// Absolute path to the variant file that failed to read.
        path: PathBuf,
        #[source]
        source: std::io::Error,
    },
}

/// An error that occurs when a task is requested which could not be found.
/// TODO: Make this error better.
///     - Include names that might have been meant instead
///     - If the tasks is only available for a certain platform, explain that.
#[derive(Debug, Clone, Diagnostic, Error)]
#[error("the task '{0}' could not be found", task_name.fancy_display())]
pub struct UnknownTask<'p> {
    /// The project that the platform is not supported for.
    pub project: &'p Workspace,

    /// The environment that the platform is not supported for.
    pub environment: EnvironmentName,

    /// The platform that was requested (if any)
    pub platform: Option<Platform>,

    /// The name of the task
    pub task_name: TaskName,
}
