use serde::{Deserialize, Serialize};

// ── Enums ─────────────────────────────────────────────────────────────────────

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum ContentKind {
    Blog,
    Recipe,
    Project,
    Activity,
}

impl ContentKind {
    /// URL path segment for this content type in the given language.
    ///
    /// Matches the localized route patterns in `site/config/routes.ncl`.
    pub fn url_segment_for_lang(&self, lang: &str) -> &'static str {
        match (self, lang) {
            (Self::Recipe, "es") => "recetas",
            (Self::Project, "es") => "proyectos",
            (Self::Activity, "es") => "actividades",
            (Self::Blog, _) => "blog",
            (Self::Recipe, _) => "recipes",
            (Self::Project, _) => "projects",
            (Self::Activity, _) => "activities",
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "PascalCase")]
pub enum OntologyLevel {
    Axiom,
    Tension,
    Practice,
    Project,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum OntologyInstance {
    Framework,
    Implementation,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "PascalCase")]
pub enum Pole {
    Yang,
    Yin,
    Spiral,
}

/// Edge kinds that can appear between any two graph nodes.
///
/// Declared* variants come from explicit `.ncl` `graph` fields.
/// Auto* variants are generated as backlinks.
/// Ontology* variants are read directly from `core.ncl` edges.
/// SharedTag is computed from shared tag arrays.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum EdgeKind {
    // Content → Ontology/Content (declared)
    Implements,
    RelatedTo,
    PartOf,
    References,
    Extends,
    // Auto-generated backlinks
    ImplementedBy,
    RelatedFrom,
    ParentOf,
    ReferencedBy,
    ExtendedBy,
    // Ontology native (from core.ncl edges)
    ManifestsIn,
    Resolves,
    Complements,
    DependsOn,
    DocumentedIn,
    // Implicit — suppressed when a Declared/Auto edge exists for the same pair
    SharedTag,
}

impl EdgeKind {
    /// Returns the inverse kind used for auto-backlinks.
    /// Ontology and SharedTag edges have no automatic backlink.
    pub fn backlink(&self) -> Option<EdgeKind> {
        match self {
            Self::Implements => Some(Self::ImplementedBy),
            Self::RelatedTo => Some(Self::RelatedFrom),
            Self::PartOf => Some(Self::ParentOf),
            Self::References => Some(Self::ReferencedBy),
            Self::Extends => Some(Self::ExtendedBy),
            _ => None,
        }
    }

    pub fn default_weight(&self) -> EdgeWeight {
        match self {
            Self::Implements | Self::ManifestsIn | Self::Resolves => EdgeWeight::High,
            Self::RelatedTo | Self::Complements | Self::DependsOn | Self::DocumentedIn => {
                EdgeWeight::Medium
            }
            Self::PartOf | Self::References | Self::Extends => EdgeWeight::Medium,
            _ => EdgeWeight::Low,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "snake_case")]
pub enum EdgeWeight {
    High,
    Medium,
    Low,
}

/// Determines merge precedence when two edges share the same (from, to, kind)
/// key.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "snake_case")]
pub enum EdgeSource {
    Declared,     // explicit in content .ncl graph field — highest
    AutoBacklink, // generated from a Declared edge
    Implicit,     // SharedTag — lowest, suppressed by any Declared/Auto on same pair
}

// ── Nodes ─────────────────────────────────────────────────────────────────────

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum GraphNode {
    Content {
        id: String,
        title: String,
        content_kind: ContentKind,
        lang: String,
        url: String,
        tags: Vec<String>,
        excerpt: Option<String>,
    },
    Ontology {
        id: String,
        name: String,
        level: OntologyLevel,
        pole: Option<Pole>,
        description: String,
        instance: OntologyInstance,
        artifact_paths: Vec<String>,
    },
    Adr {
        id: String,
        title: String,
        status: String,
        instance: OntologyInstance,
    },
}

impl GraphNode {
    pub fn id(&self) -> &str {
        match self {
            Self::Content { id, .. } => id,
            Self::Ontology { id, .. } => id,
            Self::Adr { id, .. } => id,
        }
    }

    pub fn title(&self) -> &str {
        match self {
            Self::Content { title, .. } => title,
            Self::Ontology { name, .. } => name,
            Self::Adr { title, .. } => title,
        }
    }

    pub fn tags(&self) -> &[String] {
        match self {
            Self::Content { tags, .. } => tags,
            _ => &[],
        }
    }

    /// The URL slug (last path segment) for content nodes, `None` for others.
    /// When the slug differs from `id` (e.g. `id = "post-2024"`, url ends in
    /// `"modern-post-title"`), this is what the PostViewer passes as `node_id`.
    pub fn url_slug(&self) -> Option<&str> {
        if let Self::Content { url, .. } = self {
            url.rsplit('/').next()
        } else {
            None
        }
    }
}

// ── Edges ─────────────────────────────────────────────────────────────────────

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphEdge {
    pub from: String,
    pub to: String,
    pub kind: EdgeKind,
    pub weight: EdgeWeight,
    pub source: EdgeSource,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub note: Option<String>,
}

// ── Graph ─────────────────────────────────────────────────────────────────────

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ContentGraph {
    pub nodes: Vec<GraphNode>,
    pub edges: Vec<GraphEdge>,
}

// ── Raw NCL deserialization types
// ─────────────────────────────────────────────

/// Parsed from the `graph` field of a content .ncl file.
#[derive(Debug, Deserialize, Default)]
pub struct GraphMeta {
    #[serde(default)]
    pub implements: Vec<String>,
    #[serde(default)]
    pub related_to: Vec<String>,
    #[serde(default)]
    pub part_of: Vec<String>,
    #[serde(default)]
    pub references: Vec<String>,
    #[serde(default)]
    pub extends: Vec<String>,
}

/// Raw ontology node as exported from `core.ncl`.
#[derive(Debug, Deserialize)]
pub struct OntologyNodeRaw {
    pub id: String,
    pub name: String,
    pub level: OntologyLevel,
    pub pole: Option<Pole>,
    pub description: String,
    #[serde(default)]
    pub artifact_paths: Vec<String>,
}

/// Raw ontology edge as exported from `core.ncl`.
#[derive(Debug, Deserialize)]
pub struct OntologyEdgeRaw {
    pub from: String,
    pub to: String,
    pub kind: String,
    pub weight: String,
    #[serde(default)]
    pub note: Option<String>,
}

impl OntologyEdgeRaw {
    pub fn to_edge_kind(&self) -> Option<EdgeKind> {
        match self.kind.as_str() {
            "ManifestsIn" => Some(EdgeKind::ManifestsIn),
            "Resolves" => Some(EdgeKind::Resolves),
            "Complements" => Some(EdgeKind::Complements),
            "DependsOn" => Some(EdgeKind::DependsOn),
            "DocumentedIn" => Some(EdgeKind::DocumentedIn),
            _ => None,
        }
    }

    pub fn to_weight(&self) -> EdgeWeight {
        match self.weight.as_str() {
            "High" => EdgeWeight::High,
            "Medium" => EdgeWeight::Medium,
            _ => EdgeWeight::Low,
        }
    }
}

/// Raw ADR as exported from `adrs/*.ncl`.
#[derive(Debug, Deserialize)]
pub struct AdrRaw {
    pub id: String,
    pub title: String,
    pub status: String,
}
