/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable no-async-promise-executor */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unused-vars */

import ts from "typescript";
import fs from "fs";
import ansiC from "ansi-colors";
import { ESLint } from "eslint";
import tsc from "tsc-prog";

import { lintPather, tscPather } from "../utils";
import { IBuiltConfig, IRunTime, ISummary } from "../Types.js";

import { PM_Base } from "./base.js";
import { makePromptInternal } from "../utils/makePrompt";

const eslint = new ESLint();
const formatter = await eslint.loadFormatter(
  "./node_modules/testeranto/dist/prebuild/esbuildConfigs/eslint-formatter-testeranto.mjs"
);

export abstract class PM_WithEslintAndTsc extends PM_Base {
  name: string;
  mode: "once" | "dev";
  summary: ISummary = {};

  constructor(configs: IBuiltConfig, name: string, mode: "once" | "dev") {
    super(configs);

    this.name = name;
    this.mode = mode;
    this.summary = {};

    // Initialize all test entries first
    this.configs.tests.forEach(([t, rt, tr, sidecars]) => {
      this.ensureSummaryEntry(t);
      sidecars.forEach(([sidecarName]) => {
        this.ensureSummaryEntry(sidecarName, true);
      });
    });
  }

  tscCheck = async ({
    entrypoint,
    addableFiles,
    platform,
  }: {
    platform: IRunTime;
    entrypoint: string;
    addableFiles: string[];
  }) => {
    console.log(ansiC.green(ansiC.inverse(`tsc < ${entrypoint}`)));
    try {
      this.typeCheckIsRunning(entrypoint);
    } catch (e) {
      console.error("error in tscCheck");
      console.error(e);
      console.error(entrypoint);
      console.error(JSON.stringify(this.summary, null, 2));
      process.exit(-1);
    }

    const program = tsc.createProgramFromConfig({
      basePath: process.cwd(), // always required, used for relative paths
      configFilePath: "tsconfig.json", // config to inherit from (optional)
      compilerOptions: {
        outDir: tscPather(entrypoint, platform, this.name),
        // declaration: true,
        // skipLibCheck: true,
        noEmit: true,
      },
      include: addableFiles, //["src/**/*"],
      // exclude: ["node_modules", "../testeranto"],
      // exclude: ["**/*.test.ts", "**/*.spec.ts"],
    });
    const tscPath = tscPather(entrypoint, platform, this.name);

    const allDiagnostics = program.getSemanticDiagnostics();

    const results: string[] = [];
    allDiagnostics.forEach((diagnostic) => {
      if (diagnostic.file) {
        const { line, character } = ts.getLineAndCharacterOfPosition(
          diagnostic.file,
          diagnostic.start!
        );
        const message = ts.flattenDiagnosticMessageText(
          diagnostic.messageText,
          "\n"
        );
        results.push(
          `${diagnostic.file.fileName} (${line + 1},${
            character + 1
          }): ${message}`
        );
      } else {
        results.push(
          ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n")
        );
      }
    });

    fs.writeFileSync(tscPath, results.join("\n"));
    this.typeCheckIsNowDone(entrypoint, results.length);
  };

  eslintCheck = async (
    entrypoint: string,
    platform: IRunTime,
    addableFiles: string[]
  ) => {
    console.log(ansiC.green(ansiC.inverse(`eslint < ${entrypoint}`)));

    try {
      this.lintIsRunning(entrypoint);
    } catch (e) {
      console.error("error in eslintCheck");
      console.error(e);
      console.error(entrypoint);
      console.error(JSON.stringify(this.summary, null, 2));
      process.exit(-1);
    }

    const filepath = lintPather(entrypoint, platform, this.name);
    if (fs.existsSync(filepath)) fs.rmSync(filepath);
    const results = (await eslint.lintFiles(addableFiles))
      .filter((r) => r.messages.length)
      .filter((r) => {
        return r.messages[0].ruleId !== null;
      })
      .map((r) => {
        delete r.source;
        return r;
      });

    fs.writeFileSync(filepath, await formatter.format(results));
    this.lintIsNowDone(entrypoint, results.length);
  };

  makePrompt = async (
    entryPoint: string,
    addableFiles: string[],
    platform: IRunTime
  ) => {
    await makePromptInternal(
      this.summary,
      this.name,
      entryPoint,
      addableFiles,
      platform
    );
    this.checkForShutdown();
  };

  private ensureSummaryEntry(src: string, isSidecar = false) {
    if (!this.summary[src]) {
      this.summary[src] = {
        typeErrors: undefined,
        staticErrors: undefined,
        runTimeErrors: undefined,
        prompt: undefined,
        failingFeatures: {},
      };
      if (isSidecar) {
        // Sidecars don't need all fields
        // delete this.summary[src].runTimeError;
        // delete this.summary[src].prompt;
      }
    }
    return this.summary[src];
  }

  typeCheckIsRunning = (src: string) => {
    if (!this.summary[src]) {
      throw `this.summary[${src}] is undefined`;
    }

    this.summary[src].typeErrors = "?";
  };

  typeCheckIsNowDone = (src: string, failures: number) => {
    if (!this.summary[src]) {
      throw `this.summary[${src}] is undefined`;
    }

    if (failures === 0) {
      console.log(ansiC.green(ansiC.inverse(`tsc > ${src}`)));
    } else {
      console.log(
        ansiC.red(ansiC.inverse(`tsc > ${src} failed ${failures} times`))
      );
    }

    this.summary[src].typeErrors = failures;
    this.writeBigBoard();
    this.checkForShutdown();
  };

  lintIsRunning = (src: string) => {
    if (!this.summary[src]) {
      throw `this.summary[${src}] is undefined`;
    }
    this.summary[src].staticErrors = "?";
    this.writeBigBoard();
  };

  lintIsNowDone = (src: string, failures: number) => {
    if (!this.summary[src]) {
      throw `this.summary[${src}] is undefined`;
    }

    if (failures === 0) {
      console.log(ansiC.green(ansiC.inverse(`eslint > ${src}`)));
    } else {
      console.log(
        ansiC.red(ansiC.inverse(`eslint > ${src} failed ${failures} times`))
      );
    }

    this.summary[src].staticErrors = failures;
    this.writeBigBoard();
    this.checkForShutdown();
  };

  bddTestIsRunning = (src: string) => {
    if (!this.summary[src]) {
      throw `this.summary[${src}] is undefined`;
    }
    this.summary[src].runTimeErrors = "?";
    this.writeBigBoard();
  };

  bddTestIsNowDone = (src: string, failures: number) => {
    if (!this.summary[src]) {
      throw `this.summary[${src}] is undefined`;
    }
    this.summary[src].runTimeErrors = failures;
    this.writeBigBoard();
    this.checkForShutdown();
  };

  writeBigBoard = () => {
    const summaryPath = `./testeranto/reports/${this.name}/summary.json`;
    const summaryData = JSON.stringify(this.summary, null, 2);
    fs.writeFileSync(summaryPath, summaryData);
    
    // Broadcast the update
    this.broadcast({
      type: 'summaryUpdate',
      data: this.summary
    });
  };

  abstract checkForShutdown();
}
