//! Pattern types for the AST.

use indexmap::IndexMap;

use super::Expression;

/// A pattern is a comma-separated list of pattern parts.
#[derive(Debug, Clone, PartialEq)]
pub struct Pattern {
    pub parts: Vec<PatternPart>,
}

impl Pattern {
    pub fn new(parts: Vec<PatternPart>) -> Self {
        Self { parts }
    }

    pub fn single(part: PatternPart) -> Self {
        Self { parts: vec![part] }
    }

    /// Get all variable names defined in this pattern.
    pub fn variable_names(&self) -> Vec<String> {
        let mut names = Vec::new();
        for part in &self.parts {
            if let Some(var) = &part.variable {
                names.push(var.clone());
            }
            Self::collect_element_variables(&part.element, &mut names);
        }
        names
    }

    fn collect_element_variables(element: &PatternElement, names: &mut Vec<String>) {
        match element {
            PatternElement::Chain(chain) => {
                if let Some(var) = &chain.start.variable {
                    names.push(var.clone());
                }
                for (rel, node) in &chain.chain {
                    if let Some(var) = &rel.variable {
                        names.push(var.clone());
                    }
                    if let Some(var) = &node.variable {
                        names.push(var.clone());
                    }
                }
            }
            PatternElement::ShortestPath(inner) | PatternElement::AllShortestPaths(inner) => {
                Self::collect_element_variables(inner, names);
            }
        }
    }
}

/// A pattern part, optionally with a variable binding.
#[derive(Debug, Clone, PartialEq)]
pub struct PatternPart {
    /// Optional variable to bind this pattern to (for path assignment).
    pub variable: Option<String>,
    /// The pattern element.
    pub element: PatternElement,
}

impl PatternPart {
    pub fn new(element: PatternElement) -> Self {
        Self {
            variable: None,
            element,
        }
    }

    pub fn with_variable(variable: impl Into<String>, element: PatternElement) -> Self {
        Self {
            variable: Some(variable.into()),
            element,
        }
    }
}

/// A pattern element, which can be a simple chain or a path function.
#[derive(Debug, Clone, PartialEq)]
pub enum PatternElement {
    /// A chain of nodes and relationships.
    Chain(PatternChain),
    /// `shortestPath((a)-[*]->(b))`
    ShortestPath(Box<PatternElement>),
    /// `allShortestPaths((a)-[*]->(b))`
    AllShortestPaths(Box<PatternElement>),
}

/// A chain of alternating nodes and relationships.
#[derive(Debug, Clone, PartialEq)]
pub struct PatternChain {
    /// The starting node.
    pub start: NodePattern,
    /// Chain of (relationship, node) pairs.
    pub chain: Vec<(RelationshipPattern, NodePattern)>,
}

impl PatternChain {
    pub fn new(start: NodePattern) -> Self {
        Self {
            start,
            chain: vec![],
        }
    }

    pub fn with_chain(
        start: NodePattern,
        chain: Vec<(RelationshipPattern, NodePattern)>,
    ) -> Self {
        Self { start, chain }
    }

    /// Add a relationship and node to the chain.
    pub fn add(&mut self, rel: RelationshipPattern, node: NodePattern) {
        self.chain.push((rel, node));
    }

    /// Get all nodes in the chain.
    pub fn nodes(&self) -> Vec<&NodePattern> {
        let mut nodes = vec![&self.start];
        for (_, node) in &self.chain {
            nodes.push(node);
        }
        nodes
    }

    /// Get all relationships in the chain.
    pub fn relationships(&self) -> Vec<&RelationshipPattern> {
        self.chain.iter().map(|(rel, _)| rel).collect()
    }
}

/// A node pattern: `(variable:Label1:Label2 {prop: value})`
#[derive(Debug, Clone, PartialEq)]
pub struct NodePattern {
    /// Optional variable name.
    pub variable: Option<String>,
    /// Labels on this node.
    pub labels: Vec<String>,
    /// Properties to match or set.
    pub properties: Option<Properties>,
}

impl NodePattern {
    pub fn new() -> Self {
        Self {
            variable: None,
            labels: vec![],
            properties: None,
        }
    }

    pub fn with_variable(variable: impl Into<String>) -> Self {
        Self {
            variable: Some(variable.into()),
            labels: vec![],
            properties: None,
        }
    }

    pub fn with_label(variable: Option<String>, label: impl Into<String>) -> Self {
        Self {
            variable,
            labels: vec![label.into()],
            properties: None,
        }
    }

    pub fn with_labels(variable: Option<String>, labels: Vec<String>) -> Self {
        Self {
            variable,
            labels,
            properties: None,
        }
    }
}

impl Default for NodePattern {
    fn default() -> Self {
        Self::new()
    }
}

/// A relationship pattern: `-[variable:TYPE*min..max {props}]->`
#[derive(Debug, Clone, PartialEq)]
pub struct RelationshipPattern {
    /// Optional variable name.
    pub variable: Option<String>,
    /// Relationship types (OR'd together).
    pub types: Vec<String>,
    /// Variable length specification.
    pub length: Option<RelationshipLength>,
    /// Properties to match or set.
    pub properties: Option<Properties>,
    /// Direction of the relationship.
    pub direction: RelationshipDirection,
}

impl RelationshipPattern {
    pub fn new(direction: RelationshipDirection) -> Self {
        Self {
            variable: None,
            types: vec![],
            length: None,
            properties: None,
            direction,
        }
    }

    pub fn with_type(direction: RelationshipDirection, rel_type: impl Into<String>) -> Self {
        Self {
            variable: None,
            types: vec![rel_type.into()],
            length: None,
            properties: None,
            direction,
        }
    }

    pub fn with_variable_and_type(
        variable: impl Into<String>,
        rel_type: impl Into<String>,
        direction: RelationshipDirection,
    ) -> Self {
        Self {
            variable: Some(variable.into()),
            types: vec![rel_type.into()],
            length: None,
            properties: None,
            direction,
        }
    }
}

/// The direction of a relationship.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum RelationshipDirection {
    /// Left to right: `(a)-[r]->(b)`
    Outgoing,
    /// Right to left: `(a)<-[r]-(b)`
    Incoming,
    /// Either direction: `(a)-[r]-(b)`
    #[default]
    Both,
}

/// Variable length relationship specification.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RelationshipLength {
    /// Minimum length (default 1).
    pub min: Option<u32>,
    /// Maximum length (None means unlimited).
    pub max: Option<u32>,
    /// Whether min was explicitly specified (true if user wrote *0.. or *1.., false if just *)
    pub min_explicit: bool,
}

impl RelationshipLength {
    pub fn new(min: Option<u32>, max: Option<u32>) -> Self {
        Self {
            min,
            max,
            min_explicit: min.is_some(),
        }
    }

    pub fn new_explicit(min: Option<u32>, max: Option<u32>, min_explicit: bool) -> Self {
        Self {
            min,
            max,
            min_explicit,
        }
    }

    pub fn exact(n: u32) -> Self {
        Self {
            min: Some(n),
            max: Some(n),
            min_explicit: true,
        }
    }

    pub fn any() -> Self {
        Self {
            min: None,
            max: None,
            min_explicit: false,
        }
    }

    pub fn at_least(n: u32) -> Self {
        Self {
            min: Some(n),
            max: None,
            min_explicit: true,
        }
    }

    pub fn at_most(n: u32) -> Self {
        Self {
            min: None,
            max: Some(n),
            min_explicit: false,
        }
    }

    pub fn range(min: u32, max: u32) -> Self {
        Self {
            min: Some(min),
            max: Some(max),
            min_explicit: true,
        }
    }
}

/// Properties for a node or relationship pattern.
#[derive(Debug, Clone, PartialEq)]
pub enum Properties {
    /// Map literal: `{name: 'Alice', age: 30}`
    Map(IndexMap<String, Expression>),
    /// Parameter reference: `$props`
    Parameter(String),
}

impl Properties {
    pub fn map(props: IndexMap<String, Expression>) -> Self {
        Properties::Map(props)
    }

    pub fn parameter(name: impl Into<String>) -> Self {
        Properties::Parameter(name.into())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_node_pattern() {
        let node = NodePattern::with_label(Some("n".to_string()), "Person");
        assert_eq!(node.variable, Some("n".to_string()));
        assert_eq!(node.labels, vec!["Person".to_string()]);
    }

    #[test]
    fn test_relationship_pattern() {
        let rel = RelationshipPattern::with_variable_and_type(
            "r",
            "KNOWS",
            RelationshipDirection::Outgoing,
        );
        assert_eq!(rel.variable, Some("r".to_string()));
        assert_eq!(rel.types, vec!["KNOWS".to_string()]);
        assert_eq!(rel.direction, RelationshipDirection::Outgoing);
    }

    #[test]
    fn test_pattern_chain() {
        let start = NodePattern::with_variable("a");
        let rel = RelationshipPattern::with_type(RelationshipDirection::Outgoing, "KNOWS");
        let end = NodePattern::with_variable("b");

        let mut chain = PatternChain::new(start);
        chain.add(rel, end);

        assert_eq!(chain.nodes().len(), 2);
        assert_eq!(chain.relationships().len(), 1);
    }

    #[test]
    fn test_relationship_length() {
        let any = RelationshipLength::any();
        assert_eq!(any.min, None);
        assert_eq!(any.max, None);

        let exact = RelationshipLength::exact(3);
        assert_eq!(exact.min, Some(3));
        assert_eq!(exact.max, Some(3));

        let range = RelationshipLength::range(1, 5);
        assert_eq!(range.min, Some(1));
        assert_eq!(range.max, Some(5));
    }
}
