import type { Syms } from "./resolver_syms.ts";
import { Pos } from "./token.ts";
import { GenericArgsMap, VType } from "./vtype.ts";

export type Mod = {
    filePath: string;
    ast: Stmt[];
};

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

export type StmtKind =
    | { type: "error" }
    | { type: "mod_block"; ident: string; stmts: Stmt[] }
    | { type: "mod_file"; ident: string; filePath: string }
    | { type: "mod"; ident: string; mod: Mod }
    | { type: "break"; expr?: Expr }
    | { type: "return"; expr?: Expr }
    | FnStmtKind
    | { type: "let"; param: Param; value: Expr }
    | { type: "type_alias"; param: Param }
    | { type: "assign"; assignType: AssignType; subject: Expr; value: Expr }
    | { type: "expr"; expr: Expr };

export type FnStmtKind = {
    type: "fn";
    ident: string;
    genericParams?: GenericParam[];
    params: Param[];
    returnType?: EType;
    body: Expr;
    sym?: Sym;
    vtype?: VType;
};

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

export type StmtDetails = {
    pub: boolean;
    annos: Anno[];
};

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: "ref"; subject: Expr }
    | { type: "ref_mut"; subject: Expr }
    | { type: "deref"; subject: Expr }
    | { type: "array"; exprs: Expr[] }
    | { type: "struct"; fields: Field[] }
    | { type: "field"; subject: Expr; ident: string }
    | { type: "index"; subject: Expr; value: Expr }
    | {
        type: "call";
        subject: Expr;
        args: Expr[];
        genericArgs?: GenericArgsMap;
    }
    | { 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 Field = {
    ident: string;
    expr: Expr;
    pos: Pos;
};

export type Param = {
    id: number;
    index?: number;
    ident: string;
    mut: boolean;
    etype?: EType;
    pos: Pos;
    sym?: Sym;
    vtype?: VType;
};

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

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

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: "ref"; subject: EType }
    | { type: "ref_mut"; subject: EType }
    | { type: "ptr"; subject: EType }
    | { type: "ptr_mut"; subject: EType }
    | { type: "array"; subject: EType }
    | { type: "struct"; fields: Param[] }
    | { type: "type_of"; expr: Expr };

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

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

export class AstCreator {
    private nextNodeId = 0;

    public stmt(kind: StmtKind, pos: Pos, details?: StmtDetails): Stmt {
        const id = this.genId();
        return { kind, pos, details, id };
    }

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

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

    public param(val: Omit<Param, "id">): Param {
        const id = this.genId();
        return { ...val, id };
    }

    public genericParam(val: Omit<GenericParam, "id">): GenericParam {
        const id = this.genId();
        return { ...val, id };
    }

    private genId(): number {
        const id = this.nextNodeId;
        this.nextNodeId += 1;
        return id;
    }
}

export class AnnoView {
    public constructor(private details?: StmtDetails) {}

    public has(...idents: string[]): boolean {
        return this.details?.annos.some((anno) =>
            idents.some((ident) => anno.ident === ident)
        ) ?? false;
    }

    public get(ident: string): Anno {
        const anno = this.details?.annos.find((anno) => anno.ident === ident);
        if (!anno) {
            throw new Error();
        }
        return anno;
    }
}

export function forceType(v: unknown): { type: string } {
    return v as { type: string };
}