import * as path from "jsr:@std/path";
import { Parser } from "./parser/parser.ts";
import * as ast from "./ast/mod.ts";
import { Ctx } from "./ctx.ts";
import { File } from "./ctx.ts";

async function main() {
    const filePath = Deno.args[0];
    const compiler = new PackCompiler(filePath, new NullEmitter());
    compiler.enableDebug();
    await compiler.compile();
}

export type Pack = {
    rootMod: Mod;
};

export type Mod = null;

export interface PackEmitter {
    emit(pack: Pack): void;
}

export class NullEmitter implements PackEmitter {
    emit(pack: Pack): void {
    }
}

export class PackCompiler {
    private ctx = new Ctx();

    public constructor(
        private entryFilePath: string,
        private emitter: PackEmitter,
    ) {}

    public async compile() {
        await FileTreeAstCollector
            .fromEntryFile(this.ctx, this.entryFilePath)
            .collect();
        this.ctx.printAsts();
    }

    public enableDebug() {
        this.ctx.enableReportImmediately = true;
        this.ctx.enableStacktrace = true;
    }
}

type _P = { file: File };
export class FileTreeAstCollector implements ast.Visitor<[_P]> {
    private subFilePromise = Promise.resolve();

    private constructor(
        private ctx: Ctx,
        private superFile: File | undefined,
        private ident: string,
        private absPath: string,
        private relPath: string,
    ) {}

    public static fromEntryFile(
        ctx: Ctx,
        entryFilePath: string,
    ): FileTreeAstCollector {
        return new FileTreeAstCollector(
            ctx,
            undefined,
            "root",
            entryFilePath,
            entryFilePath,
        );
    }

    public async collect(): Promise<void> {
        const text = await Deno.readTextFile(this.absPath);
        const file = this.ctx.addFile(
            this.ident,
            this.absPath,
            this.relPath,
            this.superFile,
            text,
        );
        const fileAst = new Parser(this.ctx, file).parse();
        this.ctx.addFileAst(file, fileAst);
        ast.visitFile(this, fileAst, { file });
        await this.subFilePromise;
    }

    visitModFileItem(
        item: ast.Item,
        kind: ast.ModFileItem,
        { file }: _P,
    ): ast.VisitRes {
        const ident = this.ctx.identText(item.ident.id);
        const { filePath: relPath } = kind;
        const absPath = path.join(path.dirname(this.absPath), relPath);
        this.subFilePromise = this.subFilePromise
            .then(() => {
                if (this.ctx.fileHasChildWithIdent(file, ident)) {
                    this.ctx.report({
                        severity: "fatal",
                        msg: `module '${ident}' already declared`,
                        file,
                        span: item.span,
                    });
                    Deno.exit(1);
                }
                return new FileTreeAstCollector(
                    this.ctx,
                    file,
                    ident,
                    absPath,
                    relPath,
                )
                    .collect();
            });
        return "stop";
    }
}

main();