import { IdBase, IdMap } from "@slige/common";
import * as ast from "@slige/ast";
import { Ty } from "@slige/ty";

export type Fn = {
    label: string;
    locals: IdMap<LocalId, Local>;
    blocks: IdMap<BlockId, Block>;
    entry: BlockId;
    paramLocals: IdMap<LocalId, number>;
    astItem: ast.Item;
    astItemKind: ast.FnItem;
};

export type LocalId = IdBase & { readonly _: unique symbol };

export type Local = {
    id: LocalId;
    ty: Ty;
    mut: boolean;
    ident?: ast.Ident;
};

export type BlockId = IdBase & { readonly _: unique symbol };

export type Block = {
    id: BlockId;
    stmts: Stmt[];
    terminator: Ter;
};

export type Stmt = {
    kind: StmtKind;
};

export type StmtKind =
    | { tag: "error" }
    | { tag: "assign" } & AssignStmt
    | { tag: "fake_read"; place: Place }
    | { tag: "deinit"; place: Place }
    | { tag: "live"; local: LocalId }
    | { tag: "dead"; local: LocalId }
    | { tag: "mention"; place: Place };

export type AssignStmt = { place: Place; rval: RVal };

export type Ter = {
    kind: TerKind;
};

export type TerKind =
    | { tag: "unset" }
    | { tag: "goto"; target: BlockId }
    | {
        tag: "switch";
        discr: Operand;
        targets: SwitchTarget[];
        otherwise: BlockId;
    }
    | { tag: "return" }
    | { tag: "unreachable" }
    | { tag: "drop"; place: Place; target: BlockId };

export type SwitchTarget = {
    value: number;
    target: BlockId;
};

export type Place = {
    local: LocalId;
    proj: ProjElem[];
};

// https://doc.rust-lang.org/beta/nightly-rustc/rustc_middle/mir/type.PlaceElem.html
export type ProjElem =
    | { tag: "deref" }
    | { tag: "field"; fieldIdx: number }
    | { tag: "index"; local: LocalId }
    | { tag: "downcast"; variantIdx: number };

// https://doc.rust-lang.org/beta/nightly-rustc/rustc_middle/mir/enum.Rvalue.html
export type RVal =
    | { tag: "error" }
    | { tag: "use"; operand: Operand }
    | { tag: "repeat"; operand: Operand; length: Const }
    | { tag: "ref"; place: Place; mut: boolean }
    | { tag: "ptr"; place: Place; mut: boolean }
    | { tag: "binary"; binaryType: BinaryType; left: Operand; right: Operand }
    | { tag: "unary"; unaryType: UnaryType; operand: Operand }
    | { tag: "adt"; ty: Ty; fields: Operand[]; variant?: ast.Variant }
    | { tag: "call"; func: Operand; args: Operand[] }
    | { tag: "builtin"; builtinId: string; args: Operand[] };

export type BinaryType =
    | "add"
    | "sub"
    | "mul"
    | "div"
    | "rem"
    | "xor"
    | "and"
    | "or"
    | "shl"
    | "shr"
    | "eq"
    | "ne"
    | "lt"
    | "lte"
    | "gt"
    | "gte";

export type UnaryType = "not" | "neg";

export type Operand =
    | { tag: "error" }
    | { tag: "copy"; place: Place }
    | { tag: "move"; place: Place }
    | { tag: "const"; val: Const };

export type Const =
    | { tag: "null" }
    | { tag: "int"; value: number }
    | { tag: "str"; value: string }
    | { tag: "fn"; item: ast.Item; kind: ast.FnItem };
