export class Reporter {
    private reports: Report[] = [];
    private errorOccured = false;

    report(report: Report) {
        if (report.severity === "error") {
            this.errorOccured = true;
        }
        this.reports.push(report);
    }

    ok(): boolean {
        return !this.errorOccured;
    }

    printAll() {
        for (const report of this.reports) {
            printReport(report);
        }
    }

    abort(): never {
        this.printAll();
        console.error("fatal: a fatal error occured");
        Deno.exit(1);
    }

    ofFile(file: FileInfo): FileReporter {
        return new FileReporter(this, file);
    }
}

export class FileReporter {
    constructor(
        private reporter: Reporter,
        private file: FileInfo,
    ) {}

    error(loc: Loc, message: string) {
        this.reporter.report({
            severity: "error",
            file: this.file,
            loc,
            message,
        });
    }
    info(loc: Loc, message: string) {
        this.reporter.report({
            severity: "info",
            file: this.file,
            loc,
            message,
        });
    }

    abort(): never {
        this.reporter.abort();
    }
}

export type Report = {
    severity: Severity;
    file: FileInfo;
    loc: Loc;
    message: string;
};

export type Severity = "error" | "info";

export type Loc = {
    idx: number;
    line: number;
    col: number;
};

export type FileInfo = {
    filename: string;
    text: string;
};

function printReport(report: Report) {
    const { severity, file: { filename, text }, message, loc } = report;
    const { line, col } = loc;

    const severityColor = ({
        "error": "red",
        "info": "blue",
    } as { [Key in typeof severity]: string })[severity];

    console.error(
        `%c${severity}%c: ${message}\n  %c--> ${filename}:${line}%c`,
        `color: ${severityColor}; font-weight: bold;`,
        "color: lightwhite; font-weight: bold;",
        "color: gray;",
        "",
    );
    if (!text) {
        return;
    }

    const newlines = text
        .split("")
        .map((ch, idx) => ch === "\n" ? idx : null)
        .filter((v) => v !== null);
    const lineText = text.slice(newlines[line - 2] + 1, newlines[line - 1]);
    const lineNumberText = line.toString();

    console.error(
        `${" ".repeat(lineNumberText.length)}%c|\n` +
            `${lineNumberText}|%c${lineText}\n` +
            `${" ".repeat(lineNumberText.length)}%c|` +
            `${" ".repeat(col - 1)}%c^\n` +
            `${" ".repeat(lineNumberText.length)}%c|%c`,
        "color: cyan;",
        "color: lightwhite;",
        "color: cyan;",
        `color: ${severityColor}; font-weight: bold;`,
        "color: cyan;",
        "",
    );
}
