use crate::error::Span;

/// This essentially is the root of the AST.
#[derive(Debug, Clone)]
pub struct File {
    pub items: Vec<TopLevelItem>,
    pub span: Span,
}

/// Everything that can be found at the top level of the source
#[derive(Debug, Clone)]
pub enum TopLevelItem {
    Import(ImportDef),
    ComponentDef(ComponentDef),
    FnDef(FnDef),
    Node(NodeInvocation),
    Stmt(Stmt),
}

/// Import definitions
/// Example: `import "colors.nbl" as colors`
#[derive(Debug, Clone)]
pub struct ImportDef {
    pub(crate) def: ImportDefType,
    pub(crate) span: Span,
}

/// Types of import definition. Includes,
/// Module (e.g. `import "file" ...`), and Library (e.g. stdlib).
#[derive(Debug, Clone)]
pub enum ImportDefType {
    Module(String, String),
    Library(String, String),
}

/// Component definitions
#[derive(Debug, Clone)]
pub struct ComponentDef {
    pub name: String,
    pub interface: ComponentInterface,
    pub body: Vec<NodeItem>,
    pub span: Span,
}

#[derive(Debug, Clone)]
pub enum ComponentInterface {
    Loose(String),          // (any: props)
    Strict(Vec<Parameter>), // (a, b?, c: Int)
    None,
}

#[derive(Debug, Clone)]
pub struct Parameter {
    pub name: String,
    pub type_hint: Option<String>,
    pub is_optional: bool,
}

/// Function definition
#[derive(Debug, Clone)]
pub struct FnDef {
    pub name: String,
    pub params: Vec<FnParam>,
    pub return_type: Option<String>,
    pub body: Vec<FnItem>,
    pub span: Span,
}

/// Parameters in a function
#[derive(Debug, Clone)]
pub struct FnParam {
    pub name: String,
    pub type_hint: Option<String>,
}

#[derive(Debug, Clone)]
pub enum FnItem {
    Node(NodeInvocation),
    Stmt(Stmt),
}

// Nodes
#[derive(Debug, Clone)]
pub struct NodeInvocation {
    pub type_name: String,
    pub id: Option<Expr>,
    pub body: Vec<NodeItem>,
    pub span: Span,
}

#[derive(Debug, Clone)]
pub enum NodeItem {
    Prop(String, Expr),
    Child(NodeInvocation),
    Stmt(Stmt),
}

// Expressions
#[derive(Debug, Clone)]
pub struct Block {
    pub stmts: Vec<Stmt>,
    pub terminator: Option<Expr>,
}

/// Statements supported in the language
#[derive(Debug, Clone)]
pub enum Stmt {
    Assign(Expr, Expr, Span),
    Local(String, Option<String>, Expr),
    Global(String, Option<String>, Expr),
    For(Vec<String>, Expr, Block),
    While(Expr, Block),
    Return(Option<Expr>, Span),
    Expr(Expr),
}

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

/// Kinds of expression supported in the language
#[derive(Debug, Clone)]
pub enum ExprKind {
    Binary(Box<Expr>, String, Box<Expr>),
    Unary(String, Box<Expr>),
    Literal(Literal),
    Variable(String),
    // bool true = safe (?.)
    Field(Box<Expr>, String, bool),
    Index(Box<Expr>, Box<Expr>),
    Call(Box<Expr>, Vec<Expr>),
    Lambda(Vec<(String, Option<String>)>, Box<LambdaBody>),
    If(Box<IfExpr>),
    Match(Box<Expr>, Vec<MatchArm>),
    Range(Box<Expr>, Box<Expr>, bool), // bool is inclusive
}

/// Lambda functions
#[derive(Debug, Clone)]
pub enum LambdaBody {
    Expr(Expr),
    Block(Vec<Stmt>, Option<Expr>),
}

/// If/else expressions
#[derive(Debug, Clone)]
pub struct IfExpr {
    pub condition: Expr,
    pub then_branch: (Vec<Stmt>, Option<Expr>),
    pub else_ifs: Vec<(Expr, (Vec<Stmt>, Option<Expr>))>,
    pub else_branch: Option<(Vec<Stmt>, Option<Expr>)>,
}

/// Match
#[derive(Debug, Clone)]
pub struct MatchArm {
    pub pattern: String,
    pub body: LambdaBody,
}

#[derive(Debug, Clone)]
pub enum Literal {
    Int(i64),
    Float(f64),
    Bool(bool),
    Str(String),
    List(Vec<Expr>),
    Map(Vec<(String, Expr)>),
    Null,
}
