#!/usr/bin/env node
// vsix-fetch-worker.mjs — Sub-process worker that fetches a VSIX URL and extracts
// it to a writable directory. Designed to be spawned under sandbox-exec (macOS),
// bwrap (Linux), or directly (Windows fallback).
//
// 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)
//
// Why a worker: the parent process can wrap this command in sandbox-exec / bwrap
// so any filesystem write the ZIP extractor performs is restricted to <tmpdir>.
// Defense-in-depth — even if our own zip-slip / symlink validation has a bug,
// the OS sandbox cannot let bytes land outside <tmpdir>.

import { writeFileSync, existsSync } from 'node:fs';
import { join } from 'node:path';
import { fetchVsixFromUrl } 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;
}

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);
  }
  let fetched;
  try {
    fetched = await fetchVsixFromUrl(url);
  } 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 VSIX (${err.code}): ${err.message}`, code: err.code });
    } else {
      emit({ ok: false, error: `extract failed: ${err.message}` });
    }
    process.exit(1);
  }
  const nested = join(dir, 'extension');
  const extRoot = existsSync(nested) ? nested : 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);
});
