#!/usr/bin/env node
/**
 * sync-design-system.mjs
 *
 * Vendors shared/playground-design-system/ into a plugin's
 * playground/vendor/playground-design-system/ tree.
 *
 * Usage:
 *   node scripts/sync-design-system.mjs <plugin-name> [--force]
 *
 * Each plugin keeps its own pinned copy so it stays standalone.
 * MANIFEST.json records SHA-256 per file + source commit + sync date.
 * Drift detection refuses overwrite if a vendored file was modified
 * locally after sync; pass --force to overwrite anyway.
 *
 * No npm dependencies. Node 16.7+ for fs.cp().
 */

import { createHash } from 'node:crypto';
import { promises as fs } from 'node:fs';
import path from 'node:path';
import { execSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const MARKETPLACE_ROOT = path.resolve(__dirname, '..');
const SOURCE_DIR = path.join(MARKETPLACE_ROOT, 'shared', 'playground-design-system');
const GENERATED_HEADER = '/* Code generated by sync-design-system.mjs; DO NOT EDIT. */\n';

function parseArgs(argv) {
  const args = { plugin: null, force: false };
  for (const a of argv.slice(2)) {
    if (a === '--force') args.force = true;
    else if (a.startsWith('--')) {
      throw new Error(`Unknown flag: ${a}`);
    } else if (!args.plugin) {
      args.plugin = a;
    } else {
      throw new Error(`Unexpected positional arg: ${a}`);
    }
  }
  if (!args.plugin) {
    throw new Error('Missing plugin name. Usage: node scripts/sync-design-system.mjs <plugin-name> [--force]');
  }
  return args;
}

async function sha256(filePath) {
  const buf = await fs.readFile(filePath);
  return createHash('sha256').update(buf).digest('hex');
}

async function walk(dir, base = dir) {
  const entries = await fs.readdir(dir, { withFileTypes: true });
  const out = [];
  for (const e of entries) {
    const full = path.join(dir, e.name);
    if (e.isDirectory()) {
      out.push(...(await walk(full, base)));
    } else if (e.isFile()) {
      out.push(path.relative(base, full));
    }
  }
  return out;
}

async function readJsonIfExists(p) {
  try {
    return JSON.parse(await fs.readFile(p, 'utf8'));
  } catch (e) {
    if (e.code === 'ENOENT') return null;
    throw e;
  }
}

async function detectDrift(targetDir, prevManifest) {
  if (!prevManifest || !prevManifest.files) return [];
  const drifted = [];
  for (const [rel, prevHash] of Object.entries(prevManifest.files)) {
    const tgt = path.join(targetDir, rel);
    try {
      const cur = await sha256(tgt);
      if (cur !== prevHash) drifted.push(rel);
    } catch (e) {
      if (e.code === 'ENOENT') drifted.push(`${rel} (missing)`);
      else throw e;
    }
  }
  return drifted;
}

async function injectGeneratedHeader(targetDir, files) {
  for (const rel of files) {
    if (!rel.endsWith('.css')) continue;
    const p = path.join(targetDir, rel);
    const content = await fs.readFile(p, 'utf8');
    if (content.startsWith(GENERATED_HEADER)) continue;
    await fs.writeFile(p, GENERATED_HEADER + content, 'utf8');
  }
}

async function buildManifest(targetDir, files, sourceCommit) {
  const fileHashes = {};
  for (const rel of files.sort()) {
    fileHashes[rel] = await sha256(path.join(targetDir, rel));
  }
  return {
    generated_by: 'scripts/sync-design-system.mjs',
    do_not_edit: true,
    source: 'shared/playground-design-system/',
    source_commit: sourceCommit,
    sync_date: new Date().toISOString(),
    file_count: files.length,
    files: fileHashes,
  };
}

function getCurrentCommit() {
  try {
    return execSync('git rev-parse HEAD', {
      cwd: MARKETPLACE_ROOT,
      encoding: 'utf8',
    }).trim();
  } catch {
    return 'unknown';
  }
}

async function main() {
  const args = parseArgs(process.argv);
  const pluginDir = path.join(MARKETPLACE_ROOT, 'plugins', args.plugin);

  try {
    const stat = await fs.stat(pluginDir);
    if (!stat.isDirectory()) throw new Error('not a directory');
  } catch {
    throw new Error(`Plugin directory not found: ${pluginDir}`);
  }

  try {
    await fs.access(SOURCE_DIR);
  } catch {
    throw new Error(`Source directory missing: ${SOURCE_DIR}`);
  }

  const targetDir = path.join(pluginDir, 'playground', 'vendor', 'playground-design-system');
  const manifestPath = path.join(targetDir, 'MANIFEST.json');

  const prevManifest = await readJsonIfExists(manifestPath);
  const drifted = await detectDrift(targetDir, prevManifest);
  if (drifted.length && !args.force) {
    console.error(`Refusing sync: ${drifted.length} vendored file(s) drifted from previous MANIFEST:`);
    for (const f of drifted) console.error(`  - ${f}`);
    console.error('Pass --force to overwrite local changes.');
    process.exit(2);
  }
  if (drifted.length && args.force) {
    console.warn(`--force: overwriting ${drifted.length} drifted file(s).`);
  }

  await fs.mkdir(path.dirname(targetDir), { recursive: true });
  await fs.rm(targetDir, { recursive: true, force: true });
  await fs.cp(SOURCE_DIR, targetDir, { recursive: true, force: true });

  const files = await walk(targetDir);
  await injectGeneratedHeader(targetDir, files);

  const sourceCommit = getCurrentCommit();
  const finalFiles = await walk(targetDir);
  const manifest = await buildManifest(targetDir, finalFiles, sourceCommit);
  await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf8');

  console.log(`Synced shared/playground-design-system/ → plugins/${args.plugin}/playground/vendor/playground-design-system/`);
  console.log(`  Files: ${manifest.file_count + 1} (incl. MANIFEST.json)`);
  console.log(`  Source commit: ${sourceCommit}`);
  console.log(`  Sync date: ${manifest.sync_date}`);
}

main().catch(err => {
  console.error(`Error: ${err.message}`);
  process.exit(1);
});
