use alloc::{string::String, vec::Vec};

use crate::span::Span;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExprId(pub u32);

#[derive(Debug, Default, Clone)]
pub struct Ast {
    exprs: Vec<SpannedExpr>,
}

impl Ast {
    pub fn new() -> Self {
        Self { exprs: Vec::new() }
    }

    pub fn push(&mut self, expr: Expr, span: Span) -> ExprId {
        let id = ExprId(self.exprs.len() as u32);
        self.exprs.push(SpannedExpr { expr, span });
        id
    }

    pub fn get(&self, id: ExprId) -> &SpannedExpr {
        &self.exprs[id.0 as usize]
    }

    pub fn span(&self, id: ExprId) -> Span {
        self.get(id).span
    }

    pub fn len(&self) -> usize {
        self.exprs.len()
    }

    pub fn is_empty(&self) -> bool {
        self.exprs.is_empty()
    }
}

#[derive(Debug, Clone)]
pub struct SpannedExpr {
    pub expr: Expr,
    pub span: Span,
}

#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
    Literal(Literal),
    Ident(String),
    Object(Vec<Field>),
    Array(Vec<ExprId>),
    Let {
        bindings: Vec<Field>,
        body: ExprId,
    },
    Import(String),
    Path {
        base: ExprId,
        field: String,
    },
    Call {
        callee: ExprId,
        args: Vec<ExprId>,
    },
    Function {
        params: Vec<Param>,
        body: ExprId,
    },
    Match {
        scrutinee: ExprId,
        arms: Vec<MatchArm>,
    },
    Unary {
        op: UnaryOp,
        expr: ExprId,
    },
    Binary {
        op: BinaryOp,
        lhs: ExprId,
        rhs: ExprId,
    },
    Default {
        base: ExprId,
        fallback: ExprId,
    },
    CompareConstraint {
        op: CompareOp,
        value: ExprId,
    },
    RegexConstraint(String),
    Wildcard,
}

#[derive(Debug, Clone, PartialEq)]
pub struct Field {
    pub path: Vec<String>,
    pub value: ExprId,
    pub span: Span,
}

#[derive(Debug, Clone, PartialEq)]
pub struct Param {
    pub name: String,
    pub constraint: Option<ExprId>,
    pub span: Span,
}

#[derive(Debug, Clone, PartialEq)]
pub struct MatchArm {
    pub pattern: ExprId,
    pub body: ExprId,
    pub span: Span,
}

#[derive(Debug, Clone, PartialEq)]
pub enum Literal {
    String(String),
    Int(i64),
    Float(f64),
    Bool(bool),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnaryOp {
    Neg,
    Not,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinaryOp {
    Add,
    Sub,
    Mul,
    Div,
    Concat,
    Equal,
    NotEqual,
    Greater,
    GreaterEqual,
    Less,
    LessEqual,
    LogicalAnd,
    LogicalOr,
    And,
    Patch,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompareOp {
    Gt,
    Gte,
    Lt,
    Lte,
    Eq,
}
