import * as ast from "./ast/mod.ts";
import {
    Pos,
    prettyPrintReport,
    printStackTrace,
    Report,
    Span,
} from "./diagnostics.ts";
import * as hir from "./middle/hir.ts";

export class Ctx {
    private fileIds = new Ids<File>();
    private files = new Map<Key<File>, FileInfo>();

    private reports: Report[] = [];

    public fileHasChildWithIdent(file: File, childIdent: string): boolean {
        return this.files.get(idKey(file))!
            .subFiles.has(childIdent);
    }

    public addFile(
        ident: string,
        absPath: string,
        relPath: string,
        superFile: File | undefined,
        text: string,
    ): File {
        const file = this.fileIds.nextThenStep();
        this.files.set(idKey(file), {
            ident,
            absPath,
            relPath,
            superFile,
            subFiles: new Map(),
            text,
        });
        if (superFile) {
            this.files.get(idKey(superFile))!
                .subFiles.set(ident, file);
        }
        return file;
    }

    public addFileAst(file: File, ast: ast.File) {
        this.files.get(idKey(file))!.ast = ast;
    }

    public fileInfo(file: File): FileInfo {
        return this.files.get(idKey(file))!;
    }

    public entryFile(): File {
        return keyId(0);
    }

    public iterFiles(): Iterator<File> {
        return this.files.keys()
            .map((key) => keyId(key));
    }

    private identIds = new Ids<IdentId>();
    private identStringToId = new Map<string, IdentId>();
    private identIdToString = new Map<Key<IdentId>, string>();

    public internIdent(ident: string): IdentId {
        if (this.identStringToId.has(ident)) {
            return this.identStringToId.get(ident)!;
        }
        const id = this.identIds.nextThenStep();
        this.identStringToId.set(ident, id);
        this.identIdToString.set(idKey(id), ident);
        return id;
    }

    public identText(ident: IdentId): string {
        return this.identIdToString.get(idKey(ident))!;
    }

    private stmtIds = new Ids<hir.StmtId>();
    private stmts = new Map<Key<hir.StmtId>, hir.Stmt>();

    public internStmt(kind: hir.StmtKind, span: Span): hir.StmtId {
        const id = this.stmtIds.nextThenStep();
        this.stmts.set(idKey(id), { kind, span });
        return id;
    }

    public filePosLineText(file: File, pos: Pos): string {
        const fileTextLines = this.fileInfo(file).text.split("\n");
        return fileTextLines[pos.line - 1];
    }

    public fileSpanText(file: File, span: Span): string {
        let result = "";
        const fileTextLines = this.fileInfo(file).text.split("\n");

        for (let i = 0; i < fileTextLines.length; i++) {
            if (i > span.end.line - 1) {
                break;
            }
            if (i >= span.begin.line - 1) {
                result += fileTextLines[i] + "\n";
            }
        }
        return result;
    }

    public report(rep: Report) {
        this.reports.push(rep);
        this.reportImmediately(rep);
    }

    public enableReportImmediately = false;
    public enableStacktrace = false;
    private reportImmediately(rep: Report) {
        if (this.enableReportImmediately) {
            prettyPrintReport(this, rep);
            if (this.enableStacktrace) {
                printStackTrace();
            }
        }
    }

    public printAsts() {
        for (const [_, info] of this.files) {
            console.log(`${info.absPath}:`);
            console.log(JSON.stringify(info.ast!, null, 2));
        }
    }
}

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

export type FileInfo = {
    ident: string;
    absPath: string;
    relPath: string;
    superFile?: File;
    subFiles: Map<string, File>;
    text: string;
    ast?: ast.File;
};

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

export type IdBase = { key: number };

export type Key<IdType extends IdBase> = IdType["key"];
export const idKey = <IdType extends IdBase>(id: IdType): Key<IdType> => id.key;
export const keyId = <IdType extends IdBase>(
    key: Key<IdType>,
): IdType => ({ key } as IdType);

export class Ids<IdType extends IdBase> {
    private next = 0;
    public nextThenStep(): IdType {
        const key = this.next;
        this.next += 1;
        return { key } as IdType;
    }
}