//! Statement and clause types for the AST.

use super::{Expression, OrderItem, Pattern, PatternPart};

/// A complete Cypher statement.
#[derive(Debug, Clone, PartialEq)]
pub enum Statement {
    /// A regular query (SELECT-like).
    Query(Query),
    /// A schema command (CREATE INDEX, etc.).
    SchemaCommand(SchemaCommand),
}

/// A Cypher query, possibly a UNION of multiple single queries.
#[derive(Debug, Clone, PartialEq)]
pub struct Query {
    /// The parts of the query (connected by UNION).
    pub parts: Vec<SingleQuery>,
    /// Whether to use UNION ALL (preserves duplicates).
    pub union_all: bool,
}

impl Query {
    pub fn single(query: SingleQuery) -> Self {
        Self {
            parts: vec![query],
            union_all: false,
        }
    }

    pub fn union(parts: Vec<SingleQuery>, all: bool) -> Self {
        Self {
            parts,
            union_all: all,
        }
    }
}

/// A single query (sequence of clauses).
#[derive(Debug, Clone, PartialEq)]
pub struct SingleQuery {
    pub clauses: Vec<Clause>,
}

impl SingleQuery {
    pub fn new(clauses: Vec<Clause>) -> Self {
        Self { clauses }
    }
}

/// A Cypher clause.
#[derive(Debug, Clone, PartialEq)]
pub enum Clause {
    Match(MatchClause),
    OptionalMatch(MatchClause),
    Create(CreateClause),
    Merge(MergeClause),
    Delete(DeleteClause),
    Set(SetClause),
    Remove(RemoveClause),
    With(WithClause),
    Return(ReturnClause),
    Unwind(UnwindClause),
    Foreach(ForeachClause),
    Call(CallClause),
}

/// MATCH clause.
#[derive(Debug, Clone, PartialEq)]
pub struct MatchClause {
    pub pattern: Pattern,
    pub where_clause: Option<Expression>,
    pub hints: Vec<MatchHint>,
}

impl MatchClause {
    pub fn new(pattern: Pattern) -> Self {
        Self {
            pattern,
            where_clause: None,
            hints: vec![],
        }
    }

    pub fn with_where(pattern: Pattern, where_clause: Expression) -> Self {
        Self {
            pattern,
            where_clause: Some(where_clause),
            hints: vec![],
        }
    }
}

/// Query hints for MATCH.
#[derive(Debug, Clone, PartialEq)]
pub enum MatchHint {
    UseIndex(String, String, String), // variable, label, property
    UseScan(String, String),          // variable, label
    UseJoin(Vec<String>),             // variables
}

/// CREATE clause.
#[derive(Debug, Clone, PartialEq)]
pub struct CreateClause {
    pub pattern: Pattern,
}

impl CreateClause {
    pub fn new(pattern: Pattern) -> Self {
        Self { pattern }
    }
}

/// MERGE clause.
#[derive(Debug, Clone, PartialEq)]
pub struct MergeClause {
    pub pattern: PatternPart,
    pub on_create: Option<SetClause>,
    pub on_match: Option<SetClause>,
}

impl MergeClause {
    pub fn new(pattern: PatternPart) -> Self {
        Self {
            pattern,
            on_create: None,
            on_match: None,
        }
    }
}

/// DELETE clause.
#[derive(Debug, Clone, PartialEq)]
pub struct DeleteClause {
    pub expressions: Vec<Expression>,
    pub detach: bool,
}

impl DeleteClause {
    pub fn new(expressions: Vec<Expression>) -> Self {
        Self {
            expressions,
            detach: false,
        }
    }

    pub fn detach(expressions: Vec<Expression>) -> Self {
        Self {
            expressions,
            detach: true,
        }
    }
}

/// SET clause.
#[derive(Debug, Clone, PartialEq)]
pub struct SetClause {
    pub items: Vec<SetItem>,
}

impl SetClause {
    pub fn new(items: Vec<SetItem>) -> Self {
        Self { items }
    }
}

/// A single SET operation.
#[derive(Debug, Clone, PartialEq)]
pub enum SetItem {
    /// Set a property: `n.name = 'value'`
    Property(Expression, String, Expression),
    /// Set all properties: `n = {props}`
    AllProperties(String, Expression),
    /// Merge properties: `n += {props}`
    MergeProperties(String, Expression),
    /// Add labels: `n:Label1:Label2`
    Labels(String, Vec<String>),
}

/// REMOVE clause.
#[derive(Debug, Clone, PartialEq)]
pub struct RemoveClause {
    pub items: Vec<RemoveItem>,
}

impl RemoveClause {
    pub fn new(items: Vec<RemoveItem>) -> Self {
        Self { items }
    }
}

/// A single REMOVE operation.
#[derive(Debug, Clone, PartialEq)]
pub enum RemoveItem {
    /// Remove a property: `n.name`
    Property(Expression, String),
    /// Remove labels: `n:Label1:Label2`
    Labels(String, Vec<String>),
}

/// WITH clause.
#[derive(Debug, Clone, PartialEq)]
pub struct WithClause {
    pub projection: Projection,
    pub where_clause: Option<Expression>,
}

impl WithClause {
    pub fn new(projection: Projection) -> Self {
        Self {
            projection,
            where_clause: None,
        }
    }

    pub fn with_where(projection: Projection, where_clause: Expression) -> Self {
        Self {
            projection,
            where_clause: Some(where_clause),
        }
    }
}

/// RETURN clause.
#[derive(Debug, Clone, PartialEq)]
pub struct ReturnClause {
    pub projection: Projection,
}

impl ReturnClause {
    pub fn new(projection: Projection) -> Self {
        Self { projection }
    }
}

/// Projection (shared by WITH and RETURN).
#[derive(Debug, Clone, PartialEq)]
pub struct Projection {
    pub distinct: bool,
    pub items: ProjectionItems,
    pub order_by: Option<Vec<OrderItem>>,
    pub skip: Option<Expression>,
    pub limit: Option<Expression>,
}

impl Projection {
    pub fn all() -> Self {
        Self {
            distinct: false,
            items: ProjectionItems::All,
            order_by: None,
            skip: None,
            limit: None,
        }
    }

    pub fn items(items: Vec<ProjectionItem>) -> Self {
        Self {
            distinct: false,
            items: ProjectionItems::Explicit(items),
            order_by: None,
            skip: None,
            limit: None,
        }
    }
}

/// Projection items.
#[derive(Debug, Clone, PartialEq)]
pub enum ProjectionItems {
    /// Return all variables: `*`
    All,
    /// Explicit list of items.
    Explicit(Vec<ProjectionItem>),
}

/// A single projection item.
#[derive(Debug, Clone, PartialEq)]
pub struct ProjectionItem {
    pub expression: Expression,
    pub alias: Option<String>,
    /// Original source text of the expression (for column naming when no alias).
    /// This preserves exact case and spacing as written in the query.
    pub original_text: Option<String>,
}

impl ProjectionItem {
    pub fn new(expression: Expression) -> Self {
        Self {
            expression,
            alias: None,
            original_text: None,
        }
    }

    pub fn with_alias(expression: Expression, alias: impl Into<String>) -> Self {
        Self {
            expression,
            alias: Some(alias.into()),
            original_text: None,
        }
    }

    pub fn with_original_text(expression: Expression, original_text: String) -> Self {
        Self {
            expression,
            alias: None,
            original_text: Some(original_text),
        }
    }

    /// Get the column name for this projection item.
    /// Returns the alias if set, otherwise the original text if available.
    pub fn column_name(&self) -> Option<&str> {
        self.alias.as_deref().or(self.original_text.as_deref())
    }
}

/// UNWIND clause.
#[derive(Debug, Clone, PartialEq)]
pub struct UnwindClause {
    pub expression: Expression,
    pub variable: String,
}

impl UnwindClause {
    pub fn new(expression: Expression, variable: impl Into<String>) -> Self {
        Self {
            expression,
            variable: variable.into(),
        }
    }
}

/// FOREACH clause.
#[derive(Debug, Clone, PartialEq)]
pub struct ForeachClause {
    pub variable: String,
    pub expression: Expression,
    pub clauses: Vec<UpdateClause>,
}

impl ForeachClause {
    pub fn new(variable: impl Into<String>, expression: Expression, clauses: Vec<UpdateClause>) -> Self {
        Self {
            variable: variable.into(),
            expression,
            clauses,
        }
    }
}

/// Update clauses allowed in FOREACH.
#[derive(Debug, Clone, PartialEq)]
pub enum UpdateClause {
    Create(CreateClause),
    Merge(MergeClause),
    Set(SetClause),
    Delete(DeleteClause),
    Remove(RemoveClause),
    Foreach(ForeachClause),
}

/// CALL clause.
#[derive(Debug, Clone, PartialEq)]
pub struct CallClause {
    pub procedure: ProcedureCall,
    pub yield_items: Option<YieldClause>,
}

impl CallClause {
    pub fn new(procedure: ProcedureCall) -> Self {
        Self {
            procedure,
            yield_items: None,
        }
    }

    pub fn with_yield(procedure: ProcedureCall, yield_items: YieldClause) -> Self {
        Self {
            procedure,
            yield_items: Some(yield_items),
        }
    }
}

/// A procedure call.
#[derive(Debug, Clone, PartialEq)]
pub struct ProcedureCall {
    pub namespace: Vec<String>,
    pub name: String,
    pub arguments: Vec<Expression>,
}

impl ProcedureCall {
    pub fn new(name: impl Into<String>, arguments: Vec<Expression>) -> Self {
        Self {
            namespace: vec![],
            name: name.into(),
            arguments,
        }
    }

    pub fn with_namespace(namespace: Vec<String>, name: impl Into<String>, arguments: Vec<Expression>) -> Self {
        Self {
            namespace,
            name: name.into(),
            arguments,
        }
    }

    pub fn full_name(&self) -> String {
        if self.namespace.is_empty() {
            self.name.clone()
        } else {
            format!("{}.{}", self.namespace.join("."), self.name)
        }
    }
}

/// YIELD clause.
#[derive(Debug, Clone, PartialEq)]
pub struct YieldClause {
    pub items: YieldItems,
    pub where_clause: Option<Expression>,
}

impl YieldClause {
    pub fn all() -> Self {
        Self {
            items: YieldItems::All,
            where_clause: None,
        }
    }

    pub fn items(items: Vec<YieldItem>) -> Self {
        Self {
            items: YieldItems::Explicit(items),
            where_clause: None,
        }
    }
}

/// Yield items.
#[derive(Debug, Clone, PartialEq)]
pub enum YieldItems {
    All,
    Explicit(Vec<YieldItem>),
}

/// A single yield item.
#[derive(Debug, Clone, PartialEq)]
pub struct YieldItem {
    pub name: String,
    pub alias: Option<String>,
}

impl YieldItem {
    pub fn new(name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            alias: None,
        }
    }

    pub fn with_alias(name: impl Into<String>, alias: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            alias: Some(alias.into()),
        }
    }
}

/// Schema commands.
#[derive(Debug, Clone, PartialEq)]
pub enum SchemaCommand {
    CreateIndex(CreateIndex),
    DropIndex(DropIndex),
    CreateConstraint(CreateConstraint),
    DropConstraint(DropConstraint),
}

/// CREATE INDEX command.
#[derive(Debug, Clone, PartialEq)]
pub struct CreateIndex {
    pub label: String,
    pub property: String,
}

/// DROP INDEX command.
#[derive(Debug, Clone, PartialEq)]
pub struct DropIndex {
    pub label: String,
    pub property: String,
}

/// CREATE CONSTRAINT command.
#[derive(Debug, Clone, PartialEq)]
pub struct CreateConstraint {
    pub variable: String,
    pub label: String,
    pub property: String,
    pub constraint_type: ConstraintType,
}

/// DROP CONSTRAINT command.
#[derive(Debug, Clone, PartialEq)]
pub struct DropConstraint {
    pub variable: String,
    pub label: String,
    pub property: String,
    pub constraint_type: ConstraintType,
}

/// Constraint types.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConstraintType {
    Unique,
    NotNull,
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ast::{NodePattern, PatternChain, PatternElement};

    #[test]
    fn test_simple_query() {
        let pattern = Pattern::single(PatternPart::new(PatternElement::Chain(
            PatternChain::new(NodePattern::with_variable("n")),
        )));
        let match_clause = Clause::Match(MatchClause::new(pattern));
        let return_clause = Clause::Return(ReturnClause::new(Projection::items(vec![
            ProjectionItem::new(Expression::var("n")),
        ])));

        let query = Query::single(SingleQuery::new(vec![match_clause, return_clause]));
        assert_eq!(query.parts.len(), 1);
        assert_eq!(query.parts[0].clauses.len(), 2);
    }

    #[test]
    fn test_projection() {
        let proj = Projection::items(vec![
            ProjectionItem::new(Expression::var("n")),
            ProjectionItem::with_alias(Expression::property(Expression::var("n"), "name"), "name"),
        ]);

        if let ProjectionItems::Explicit(items) = &proj.items {
            assert_eq!(items.len(), 2);
            assert_eq!(items[1].alias, Some("name".to_string()));
        } else {
            panic!("Expected explicit items");
        }
    }
}
