#!/usr/bin/env node

import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import vm from "node:vm";
import { execFile } from "node:child_process";
import { promisify } from "node:util";

const execFileAsync = promisify(execFile);

const VERSION = process.argv[2] ?? "12.17.0";
const VERSION_TAG = `v${VERSION}`;
const VSIX_URL = `https://open-vsx.org/api/vscode-icons-team/vscode-icons/${VERSION}/file/vscode-icons-team.vscode-icons-${VERSION}.vsix`;
const LANGUAGES_URL = `https://raw.githubusercontent.com/vscode-icons/vscode-icons/${VERSION_TAG}/src/iconsManifest/languages.ts`;

const REPO_ROOT = process.cwd();
const MANIFEST_PATH = path.join(REPO_ROOT, "apps/web/src/vscode-icons-manifest.json");
const ASSOCIATIONS_PATH = path.join(
  REPO_ROOT,
  "apps/web/src/vscode-icons-language-associations.json",
);

function normalizeExtension(value) {
  return value.trim().toLowerCase().replace(/^\./, "");
}

function normalizeFileName(value) {
  return value.trim().toLowerCase();
}

function putIfAbsent(target, key, value) {
  if (!(key in target)) {
    target[key] = value;
  }
}

async function downloadVsix(tmpDir) {
  const vsixPath = path.join(tmpDir, `vscode-icons-${VERSION}.vsix`);
  const response = await fetch(VSIX_URL);
  if (!response.ok) {
    throw new Error(`Failed to download VSIX: ${response.status} ${response.statusText}`);
  }
  const bytes = Buffer.from(await response.arrayBuffer());
  await fs.writeFile(vsixPath, bytes);
  return vsixPath;
}

async function extractManifestFromVsix(vsixPath) {
  const { stdout } = await execFileAsync("unzip", [
    "-p",
    vsixPath,
    "extension/dist/src/vsicons-icon-theme.json",
  ]);
  if (!stdout || stdout.trim().length === 0) {
    throw new Error("Could not extract vsicons-icon-theme.json from VSIX");
  }
  return JSON.parse(stdout);
}

async function loadLanguagesCollection() {
  const response = await fetch(LANGUAGES_URL);
  if (!response.ok) {
    throw new Error(`Failed to fetch languages.ts: ${response.status} ${response.statusText}`);
  }
  const rawSource = await response.text();
  const source = rawSource
    .replace(/^import[^;]+;\s*/gm, "")
    .replace(/export const languages(?:\s*:\s*[^=]+)?\s*=/, "const languages =")
    .replace(/\}\s*satisfies\s*Record<[^;]+>;/, "};");

  const context = {};
  vm.createContext(context);
  vm.runInContext(`${source}\n;globalThis.__languages = languages;`, context);
  const languages = context.__languages;
  if (!languages || typeof languages !== "object") {
    throw new Error("Failed to parse languages.ts into a collection");
  }
  return languages;
}

function buildLanguageAssociations(manifest, languages) {
  const availableLanguageIds = new Set(Object.keys(manifest.languageIds ?? {}));
  const extensionToLanguageId = {};
  const fileNameToLanguageId = {};

  for (const entry of Object.values(languages)) {
    if (!entry || typeof entry !== "object") continue;
    const idsRaw = entry.ids;
    const ids = Array.isArray(idsRaw) ? idsRaw : [idsRaw];
    const knownExtensions = Array.isArray(entry.knownExtensions) ? entry.knownExtensions : [];
    const knownFilenames = Array.isArray(entry.knownFilenames) ? entry.knownFilenames : [];

    for (const idValue of ids) {
      if (typeof idValue !== "string") continue;
      const languageId = idValue.trim();
      if (languageId.length === 0) continue;
      if (!availableLanguageIds.has(languageId)) continue;

      for (const extensionValue of knownExtensions) {
        if (typeof extensionValue !== "string") continue;
        const extension = normalizeExtension(extensionValue);
        if (extension.length === 0) continue;
        putIfAbsent(extensionToLanguageId, extension, languageId);
      }

      for (const fileNameValue of knownFilenames) {
        if (typeof fileNameValue !== "string") continue;
        const fileName = normalizeFileName(fileNameValue);
        if (fileName.length === 0) continue;
        putIfAbsent(fileNameToLanguageId, fileName, languageId);
      }
    }
  }

  return {
    version: VERSION,
    source: LANGUAGES_URL,
    generatedAt: new Date().toISOString(),
    extensionToLanguageId,
    fileNameToLanguageId,
  };
}

async function main() {
  const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "belweave-vscode-icons-sync-"));
  try {
    const vsixPath = await downloadVsix(tmpDir);
    const manifest = await extractManifestFromVsix(vsixPath);
    const languages = await loadLanguagesCollection();
    const associations = buildLanguageAssociations(manifest, languages);

    await fs.writeFile(MANIFEST_PATH, `${JSON.stringify(manifest)}\n`, "utf8");
    await fs.writeFile(ASSOCIATIONS_PATH, `${JSON.stringify(associations)}\n`, "utf8");

    process.stdout.write(
      [
        `Synced vscode-icons ${VERSION}`,
        `manifest: ${MANIFEST_PATH}`,
        `language associations: ${ASSOCIATIONS_PATH}`,
        `extension mappings: ${Object.keys(associations.extensionToLanguageId).length}`,
        `filename mappings: ${Object.keys(associations.fileNameToLanguageId).length}`,
      ].join("\n") + "\n",
    );
  } finally {
    await fs.rm(tmpDir, { recursive: true, force: true });
  }
}

main().catch((error) => {
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
  process.exit(1);
});
