import { Pos } from "./token.ts";
import { VType } from "./vtype.ts";

export type Stmt = {
    kind: StmtKind;
    pos: Pos;
    id: number;
};

export type StmtKind =
    | { type: "error" }
    | { type: "import"; path: Expr }
    | { type: "break"; expr?: Expr }
    | { type: "return"; expr?: Expr }
    | {
        type: "fn";
        ident: string;
        genericParams?: GenericParam[];
        params: Param[];
        returnType?: EType;
        body: Expr;
        anno?: Anno;
        vtype?: VType;
    }
    | { type: "let"; param: Param; value: Expr }
    | { type: "assign"; assignType: AssignType; subject: Expr; value: Expr }
    | { type: "expr"; expr: Expr };

export type AssignType = "=" | "+=" | "-=";

export type Expr = {
    kind: ExprKind;
    pos: Pos;
    vtype?: VType;
    id: number;
};

export type ExprKind =
    | { type: "error" }
    | { type: "int"; value: number }
    | { type: "string"; value: string }
    | { type: "ident"; ident: string }
    | {
        type: "sym";
        ident: string;
        sym: Sym;
    }
    | { type: "group"; expr: Expr }
    | { type: "field"; subject: Expr; ident: string }
    | { type: "index"; subject: Expr; value: Expr }
    | { type: "call"; subject: Expr; args: Expr[] }
    | { type: "path"; subject: Expr; ident: string }
    | { type: "etype_args"; subject: Expr; etypeArgs: EType[] }
    | { type: "unary"; unaryType: UnaryType; subject: Expr }
    | { type: "binary"; binaryType: BinaryType; left: Expr; right: Expr }
    | { type: "if"; cond: Expr; truthy: Expr; falsy?: Expr; elsePos?: Pos }
    | { type: "bool"; value: boolean }
    | { type: "null" }
    | { type: "loop"; body: Expr }
    | { type: "block"; stmts: Stmt[]; expr?: Expr }
    | { type: "while"; cond: Expr; body: Expr }
    | { type: "for_in"; param: Param; value: Expr; body: Expr }
    | {
        type: "for";
        decl?: Stmt;
        cond?: Expr;
        incr?: Stmt;
        body: Expr;
    };

export type UnaryType = "not" | "-";
export type BinaryType =
    | "+"
    | "*"
    | "=="
    | "-"
    | "/"
    | "!="
    | "<"
    | ">"
    | "<="
    | ">="
    | "or"
    | "and";

export type Param = {
    ident: string;
    etype?: EType;
    pos: Pos;
    vtype?: VType;
};

export type Sym = {
    ident: string;
    pos?: Pos;
} & SymKind;

export type SymKind =
    | { type: "let"; stmt: Stmt; param: Param }
    | { type: "let_static"; stmt: Stmt; param: Param }
    | { type: "fn"; stmt: Stmt }
    | { type: "fn_param"; param: Param }
    | { type: "closure"; inner: Sym }
    | { type: "generic"; genericParam: GenericParam };

export type EType = {
    kind: ETypeKind;
    pos: Pos;
    id: number;
};

export type ETypeKind =
    | { type: "error" }
    | { type: "null" }
    | { type: "int" }
    | { type: "bool" }
    | { type: "string" }
    | { type: "ident"; ident: string }
    | {
        type: "sym";
        ident: string;
        sym: Sym;
    }
    | { type: "array"; inner: EType }
    | { type: "struct"; fields: Param[] };

export type GenericParam = {
    ident: string;
    pos: Pos;
    vtype?: VType;
};

export type Anno = {
    ident: string;
    values: Expr[];
    pos: Pos;
};

export class AstCreator {
    private nextNodeId = 0;

    public stmt(kind: StmtKind, pos: Pos): Stmt {
        const id = this.nextNodeId;
        this.nextNodeId += 1;
        return { kind, pos, id };
    }

    public expr(kind: ExprKind, pos: Pos): Expr {
        const id = this.nextNodeId;
        this.nextNodeId += 1;
        return { kind, pos, id };
    }

    public etype(kind: ETypeKind, pos: Pos): EType {
        const id = this.nextNodeId;
        this.nextNodeId += 1;
        return { kind, pos, id };
    }
}
