import {
    AstCreator,
    ETypeKind,
    Expr,
    ExprKind,
    Stmt,
    StmtKind,
} from "../ast.ts";
import {
    AstVisitor,
    visitField,
    VisitRes,
    visitStmts,
} from "../ast_visitor.ts";
import { Pos } from "../token.ts";

export class StructLiteralDesugarer implements AstVisitor {
    public constructor(
        private astCreator: AstCreator,
    ) {}

    public desugar(stmts: Stmt[]) {
        visitStmts(stmts, this);
    }

    visitStructExpr(expr: Expr): VisitRes {
        if (expr.kind.type !== "struct") {
            throw new Error();
        }
        const npos: Pos = { index: 0, line: 1, col: 1 };
        const Expr = (kind: ExprKind, pos = npos) =>
            this.astCreator.expr(kind, pos);
        const Stmt = (kind: StmtKind, pos = npos) =>
            this.astCreator.stmt(kind, pos);
        const EType = (kind: ETypeKind, pos = npos) =>
            this.astCreator.etype(kind, pos);

        const std = (ident: string): Expr =>
            Expr({
                type: "path",
                subject: Expr({
                    type: "ident",
                    ident: "std",
                }),
                ident,
            });

        const fields = expr.kind.fields;

        expr.kind = {
            type: "block",
            stmts: [
                Stmt({
                    type: "let",
                    param: this.astCreator.param({
                        ident: "::value",
                        pos: npos,
                    }),
                    value: Expr({
                        type: "call",
                        subject: Expr({
                            type: "etype_args",
                            subject: std("struct_new"),
                            etypeArgs: [
                                EType({
                                    type: "type_of",
                                    expr: Expr({ ...expr.kind }),
                                }),
                            ],
                        }),
                        args: [],
                    }),
                }),
                ...expr.kind.fields
                    .map((field) =>
                        Stmt({
                            type: "assign",
                            assignType: "=",
                            subject: Expr({
                                type: "field",
                                subject: Expr({
                                    type: "ident",
                                    ident: "::value",
                                }),
                                ident: field.ident,
                            }),
                            value: field.expr,
                        })
                    ),
            ],
            expr: Expr({ type: "ident", ident: "::value" }),
        };

        for (const field of fields) {
            visitField(field, this);
        }

        return "stop";
    }
}