#!/usr/bin/env node
// jetbrains-fetch-worker.mjs — Sub-process worker that fetches a JetBrains
// plugin URL and extracts the downloaded ZIP into a writable directory.
// Mirrors `vsix-fetch-worker.mjs` IPC exactly.
//
// Contract:
//   stdin:  none
//   argv:   --url <url> --tmpdir <writable-dir>
//   stdout: single JSON line {ok:true, sha256, size, finalUrl, source, extRoot}
//           on success, or {ok:false, error:"<msg>", code?:"<ZIP_CODE>"} on failure
//   stderr: never (silent — all errors via JSON on stdout)
//   exit:   0 on success, 1 on any failure (caller still parses stdout)
//
// Key JetBrains-specific difference from the VSIX worker: plugin archives are
// NOT nested under `extension/`. The top-level entry is usually the plugin
// directory itself — identified by the presence of a `lib/` subdir containing
// at least one `*.jar`. If no such directory is found, fall back to `<tmpdir>`
// itself and let the parser surface a warning.

import { existsSync, readdirSync, statSync } from 'node:fs';
import { join } from 'node:path';
import { fetchJetBrainsPlugin, detectUrlType } from './vsix-fetch.mjs';
import { extractToDir, ZipError } from './zip-extract.mjs';

function emit(obj) {
  process.stdout.write(JSON.stringify(obj) + '\n');
}

function parseArgs(argv) {
  const out = { url: null, tmpdir: null };
  for (let i = 0; i < argv.length; i++) {
    if (argv[i] === '--url' && i + 1 < argv.length) out.url = argv[++i];
    else if (argv[i] === '--tmpdir' && i + 1 < argv.length) out.tmpdir = argv[++i];
  }
  return out;
}

// Find the top-level plugin dir inside an extracted JetBrains archive.
// JetBrains plugin zips contain `<plugin-name>/lib/*.jar`. We walk immediate
// children of <tmpdir>; the first child with a `lib/` subdir containing a
// `.jar` file is the plugin root. Fallback: return <tmpdir>.
function findPluginRoot(tmpdir) {
  let entries;
  try { entries = readdirSync(tmpdir); } catch { return tmpdir; }
  for (const name of entries) {
    const candidate = join(tmpdir, name);
    let s;
    try { s = statSync(candidate); } catch { continue; }
    if (!s.isDirectory()) continue;
    const libDir = join(candidate, 'lib');
    let libStat;
    try { libStat = statSync(libDir); } catch { continue; }
    if (!libStat.isDirectory()) continue;
    let libEntries;
    try { libEntries = readdirSync(libDir); } catch { continue; }
    if (libEntries.some((n) => n.toLowerCase().endsWith('.jar'))) {
      return candidate;
    }
  }
  return tmpdir;
}

async function main() {
  const { url, tmpdir: dir } = parseArgs(process.argv.slice(2));
  if (!url || !dir) {
    emit({ ok: false, error: 'missing --url or --tmpdir' });
    process.exit(1);
  }
  const detected = detectUrlType(url);
  if (detected.type !== 'jetbrains') {
    emit({ ok: false, error: `worker expected JetBrains URL, got type=${detected.type}` });
    process.exit(1);
  }

  let fetched;
  try {
    fetched = await fetchJetBrainsPlugin({
      numericId: detected.numericId,
      xmlId: detected.xmlId,
      version: detected.version,
    });
  } catch (err) {
    emit({ ok: false, error: `fetch failed: ${err.message}` });
    process.exit(1);
  }

  try {
    await extractToDir(fetched.buffer, dir);
  } catch (err) {
    if (err instanceof ZipError) {
      emit({ ok: false, error: `malformed JetBrains plugin (${err.code}): ${err.message}`, code: err.code });
    } else {
      emit({ ok: false, error: `extract failed: ${err.message}` });
    }
    process.exit(1);
  }

  const extRoot = existsSync(dir) ? findPluginRoot(dir) : dir;

  emit({
    ok: true,
    sha256: fetched.sha256,
    size: fetched.size,
    finalUrl: fetched.finalUrl,
    source: fetched.source,
    extRoot,
  });
  process.exit(0);
}

main().catch((err) => {
  emit({ ok: false, error: `worker crashed: ${err.message || String(err)}` });
  process.exit(1);
});
