import { resolve, basename, relative } from "path";
import * as fs from "fs";
import * as util from "util";
import { execSync } from "child_process";
const exec = util.promisify(execSync);

const root = resolve(import.meta.dirname, "..");
const dry = process.argv.includes("--dry");

const shellQuote = (value) => `'${String(value).replace(/'/g, `'\\''`)}'`;

const gitOutput = (command) => {
  return execSync(command, { cwd: root, encoding: "utf-8" }).trim();
};

const gitHeadHash = gitOutput("git rev-parse HEAD");
let gitHeadBranch = "";
try {
  gitHeadBranch = gitOutput("git symbolic-ref --short HEAD");
} catch {
  gitHeadBranch = "";
}
const gitHead = gitHeadBranch ? `ref: refs/heads/${gitHeadBranch}` : gitHeadHash;
const gitInputFlags = [
  ["tinymist-git-head", gitHead],
  ["tinymist-git-head-branch", gitHeadBranch],
  ["tinymist-git-head-hash", gitHeadHash],
]
  .map(([key, value]) => `--input ${shellQuote(`${key}=${value}`)}`)
  .join(" ");

const yarn = (cmd, stdio = "inherit") => {
  const script = `yarn run ${cmd}`;
  if (dry) {
    return script;
  }
  return execSync(script, { stdio });
};
const typlite = (input, output) => {
  const assets_flag = dry
    ? ""
    : `--assets-path ${relative(root, resolve(output, "../assets/images/", basename(input.slice(0, -4))))}`;

  // return stdout
  const res = yarn(
    `--silent typlite ${gitInputFlags} ${assets_flag} --root ${root} ${input} -`,
    "pipe",
  );
  return res.toString();
};

const isCheck = process.argv.includes("--check");

const generatedInputs = [
  {
    command:
      "cargo run --quiet --bin tinymist-docs-tool -- --output docs/tinymist/generated/compiler-settings-fonts.json",
  },
];

const prepareGeneratedInputs = () => {
  if (dry) {
    return;
  }

  for (const input of generatedInputs) {
    const command = `${input.command}${isCheck ? " --check" : ""}`;
    execSync(command, { cwd: root, stdio: "inherit" });
  }
};

const convert = async ({ input: inp, output: out }) => {
  const input = resolve(root, inp);
  const output = resolve(root, out);

  const outputContent = typlite(input, output).trim();
  if (dry) {
    console.log(outputContent);
    return;
  }

  await fs.promises.writeFile(output, outputContent.trim() + "\n");

  if (isCheck) {
    const gitStatus = (
      await exec(`git status --porcelain ${output}`, {
        encoding: "utf-8",
      })
    ).trim();
    if (gitStatus) {
      throw new Error(
        `The file ${out} is not up to date. Please run \`node scripts/link-docs.mjs\` locally to update it.`,
      );
    }
  }
};

// todo: generate me using typlite.
const maintainerMd = async () => {
  const maintainers = JSON.parse(yarn(`--silent maintainers --input=action=maintainers`, "pipe"));
  const features = JSON.parse(yarn(`--silent maintainers --input=action=features`, "pipe"));

  const output = [];

  output.push("<!-- This file is generated by scripts/link-docs.mjs. Do not edit manually. -->\n");
  output.push("# Tinymist Maintainers\n\n");
  output.push(
    "Tinymist [ˈtaɪni mɪst] is an integrated language service for [Typst](https://typst.app/) [taɪpst].",
  );

  output.push(
    "\nThis page is generated from [./MAINTAINERS.typ](./MAINTAINERS.typ) and renders information of [maintainers](#maintainers) and [features.](#features)\n",
  );

  output.push("## Maintainers\n");

  const italicIt = (it) => `*${it}*`;
  const featureLink = (it) => {
    const feature = features.find((f) => f.name === it);
    if (feature) {
      return `[${it}](#${it.replace(/\s+/g, "-").toLowerCase()})`;
    }
    return it;
  };
  const fsPath = (it) => {
    if (!fs.existsSync(it)) {
      throw new Error(`Path ${it} does not exist!`);
    }
    return `[\`${it}\`](./${it})`;
  };

  for (const maintainer of maintainers) {
    output.push(`- [**${maintainer["name"]}**](https://github.com/${maintainer["github-name"]})`);
    output.push(`  - Email: ${maintainer.email}`);
    if (maintainer.maintains.length > 0) {
      const rendered = maintainer.maintains.map(featureLink).map(italicIt);
      if (rendered.length > 1) {
        const last = rendered.pop();
        output.push(`  - Maintains: ${rendered.join(", ")}, and ${last}`);
      } else {
        output.push(`  - Maintains: ${rendered.join(", ")}`);
      }
    }
    output.push("");
  }

  output.push("## Features\n");
  for (const feature of features) {
    output.push(`### ${feature.name}`);
    output.push(`${feature.description}`);
    output.push(`- Scope: ${feature.scope.map(fsPath).join(", ")}`);
  }

  const outPath = resolve(root, "MAINTAINERS.md");
  const outputContent = output.join("\n");
  if (dry) {
    console.log(content);
    return;
  }

  await fs.promises.writeFile(outPath, outputContent);
};

const tasks = [
  {
    input: "docs/tinymist/introduction.typ",
    output: "README.md",
  },
  {
    input: "docs/tinymist/release-instruction.typ",
    output: "docs/release-instruction.md",
  },
  {
    input: "docs/tinymist/software-spec/vscode.typ",
    output: "docs/for-llm/vscode-ui.md",
  },
  {
    input: "docs/tinymist/software-spec/neovim.typ",
    output: "docs/for-llm/neovim-ui.md",
  },
  {
    input: "docs/tinymist/crates/typlite.typ",
    output: "crates/typlite/README.md",
  },
  {
    input: "docs/tinymist/frontend/emacs.typ",
    output: "editors/emacs/README.md",
  },
  {
    input: "docs/tinymist/frontend/helix.typ",
    output: "editors/helix/README.md",
  },
  {
    input: "docs/tinymist/frontend/neovim.typ",
    output: "editors/neovim/README.md",
  },
  {
    input: "docs/tinymist/frontend/sublime-text.typ",
    output: "editors/sublime-text/README.md",
  },
  {
    input: "docs/tinymist/frontend/vscode.typ",
    output: "editors/vscode/README.md",
  },
  {
    input: "docs/tinymist/frontend/zed.typ",
    output: "editors/zed/README.md",
  },
  {
    input: "docs/tinymist/config/vscode.typ",
    output: "editors/vscode/Configuration.md",
  },
  {
    input: "docs/tinymist/config/neovim.typ",
    output: "editors/neovim/Configuration.md",
  },
];

const main = async () => {
  prepareGeneratedInputs();
  await Promise.all([...tasks.map(convert), maintainerMd()]);
};

main();
