use std::fmt;
use std::str::FromStr;

/// Identifies a Plane workspace by its URL slug (e.g. "jakutainc", "ourhomii").
/// Used as the config file key and the path segment in Plane API requests.
/// Must be non-empty, containing only lowercase alphanumeric characters, hyphens, and underscores.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct WorkspaceSlug(String);

impl FromStr for WorkspaceSlug {
    type Err = anyhow::Error;

    fn from_str(raw_value: &str) -> anyhow::Result<Self> {
        if raw_value.is_empty() {
            anyhow::bail!("workspace slug cannot be empty");
        }
        let all_valid = raw_value
            .chars()
            .all(|ch| ch.is_ascii_lowercase() || ch.is_ascii_digit() || ch == '-' || ch == '_');
        if !all_valid {
            anyhow::bail!(
                "workspace slug must contain only lowercase alphanumeric, hyphens, and underscores: {raw_value}"
            );
        }
        Ok(Self(raw_value.to_owned()))
    }
}

impl fmt::Display for WorkspaceSlug {
    fn fmt(&self, slug_output: &mut fmt::Formatter<'_>) -> fmt::Result {
        slug_output.write_str(&self.0)
    }
}

impl serde::Serialize for WorkspaceSlug {
    fn serialize<S: serde::Serializer>(&self, slug_serializer: S) -> Result<S::Ok, S::Error> {
        slug_serializer.serialize_str(&self.0)
    }
}

impl<'de> serde::Deserialize<'de> for WorkspaceSlug {
    fn deserialize<D: serde::Deserializer<'de>>(slug_deserializer: D) -> Result<Self, D::Error> {
        let slug_text = <String as serde::Deserialize>::deserialize(slug_deserializer)?;
        slug_text.parse().map_err(serde::de::Error::custom)
    }
}

/// String access for workspace slugs used in URL construction and display
impl WorkspaceSlug {
    /// Returns the slug as a string slice for embedding in API URL paths
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

/// Plane API authentication token (e.g. "pl_...").
/// Validated to be non-empty on construction — the Plane API validates token correctness on use.
#[derive(Debug, Clone)]
pub struct ApiToken(String);

impl FromStr for ApiToken {
    type Err = anyhow::Error;

    fn from_str(raw_value: &str) -> anyhow::Result<Self> {
        if raw_value.is_empty() {
            anyhow::bail!("API token cannot be empty");
        }
        Ok(Self(raw_value.to_owned()))
    }
}

impl serde::Serialize for ApiToken {
    fn serialize<S: serde::Serializer>(&self, token_serializer: S) -> Result<S::Ok, S::Error> {
        token_serializer.serialize_str(&self.0)
    }
}

impl<'de> serde::Deserialize<'de> for ApiToken {
    fn deserialize<D: serde::Deserializer<'de>>(token_deserializer: D) -> Result<Self, D::Error> {
        let token_text = <String as serde::Deserialize>::deserialize(token_deserializer)?;
        token_text.parse().map_err(serde::de::Error::custom)
    }
}

/// String access for API tokens used in HTTP Authorization headers
impl ApiToken {
    /// Returns the token as a string slice for embedding in request headers
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

/// Identifies a Plane project by its slug within a workspace (e.g. "ai-lab", "nro").
/// Must be non-empty, containing only lowercase alphanumeric characters, hyphens, and underscores.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProjectSlug(String);

impl FromStr for ProjectSlug {
    type Err = anyhow::Error;

    fn from_str(raw_value: &str) -> anyhow::Result<Self> {
        if raw_value.is_empty() {
            anyhow::bail!("project identifier cannot be empty");
        }
        let lowered = raw_value.to_ascii_lowercase();
        let all_valid = lowered
            .chars()
            .all(|ch| ch.is_ascii_lowercase() || ch.is_ascii_digit() || ch == '-' || ch == '_');
        if !all_valid {
            anyhow::bail!(
                "project identifier must be alphanumeric with hyphens or _: {raw_value}"
            );
        }
        Ok(Self(lowered))
    }
}

impl fmt::Display for ProjectSlug {
    fn fmt(&self, project_output: &mut fmt::Formatter<'_>) -> fmt::Result {
        project_output.write_str(&self.0)
    }
}

impl serde::Serialize for ProjectSlug {
    fn serialize<S: serde::Serializer>(&self, project_serializer: S) -> Result<S::Ok, S::Error> {
        project_serializer.serialize_str(&self.0)
    }
}

impl<'de> serde::Deserialize<'de> for ProjectSlug {
    fn deserialize<D: serde::Deserializer<'de>>(project_deserializer: D) -> Result<Self, D::Error> {
        let project_text = <String as serde::Deserialize>::deserialize(project_deserializer)?;
        project_text.parse().map_err(serde::de::Error::custom)
    }
}

/// String access for project slugs used in URL construction and display
impl ProjectSlug {
    /// Returns the slug as a string slice for embedding in API URL paths
    pub fn as_str(&self) -> &str {
        &self.0
    }
}
