// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

// @ts-check
const child_process = require("child_process");
const fs = require("fs");
const path = require("path");
const { collapseCompletedDirectories } = require("./collapse-completed-directories");
const config = require("./config");
const { getUncheckedLeafFiles } = require("./eligible-file-finder");
const { writeTsconfigSync } = require("./write-tsconfig");

const repoRoot = config.repoRoot;
const tscPath = path.join(repoRoot, "node_modules", "typescript", "bin", "tsc");
const tsconfigPath = path.join(repoRoot, config.targetTsconfig);

async function main() {
  console.log("## Initializing tsc --watch process...");
  const tscWatchProcess = child_process.spawn("node", [tscPath, "-p", tsconfigPath, "--watch"]);
  await waitForBuildComplete(tscWatchProcess);

  const alreadyAttempted = new Set();

  for (let pass = 1; ; pass += 1) {
    let successesThisPass = 0;
    const uncheckedLeafFiles = await getUncheckedLeafFiles();
    const candidateFiles = uncheckedLeafFiles.filter((f) => !alreadyAttempted.has(f));
    const candidateCount = candidateFiles.length;
    console.log(`## Starting pass ${pass} with ${candidateCount} candidate files`);

    for (const file of candidateFiles) {
      alreadyAttempted.add(file);
      if (await tryAutoAddStrictNulls(tscWatchProcess, tsconfigPath, file)) {
        successesThisPass += 1;
      }
    }

    console.log(`### Finished pass ${pass} (added ${successesThisPass}/${candidateCount})`);
    if (successesThisPass === 0) {
      break;
    }
  }

  console.log("## Stopping tsc --watch process...");
  tscWatchProcess.kill();

  console.log('## Collapsing fully null-checked directories into "include" patterns...');
  collapseCompletedDirectories(tsconfigPath);
}

async function tryAutoAddStrictNulls(child, tsconfigPath, file) {
  const relativeFilePath = path.relative(repoRoot, file).replace(/\\/g, "/");
  const originalConfig = JSON.parse(fs.readFileSync(tsconfigPath).toString());
  originalConfig.files = Array.from(new Set(originalConfig.files.sort()));

  // Config on accept
  const newConfig = Object.assign({}, originalConfig);
  newConfig.files = Array.from(new Set(originalConfig.files.concat("./" + relativeFilePath).sort()));

  const buildCompetePromise = waitForBuildComplete(child);

  writeTsconfigSync(tsconfigPath, newConfig);

  const errorCount = await buildCompetePromise;
  const success = errorCount === 0;
  if (success) {
    console.log(`${relativeFilePath}: added`);
  } else {
    console.log(`${relativeFilePath}: ${errorCount} error(s), skipped`);
    writeTsconfigSync(tsconfigPath, originalConfig);
  }

  return success;
}

const buildCompletePattern = /Found (\d+) errors?\. Watching for file changes\./gi;
async function waitForBuildComplete(tscWatchProcess) {
  const match = await waitForStdoutMatching(tscWatchProcess, buildCompletePattern);
  const errorCount = +match[1];
  return errorCount;
}

async function waitForStdoutMatching(child, pattern) {
  return new Promise((resolve) => {
    const listener = (data) => {
      const textOut = data.toString();
      const match = pattern.exec(textOut);
      if (match) {
        child.stdout.removeListener("data", listener);
        resolve(match);
      }
    };
    child.stdout.on("data", listener);
  });
}

main().catch((error) => {
  console.error(error.stack);
  process.exit(1);
});