#!/usr/bin/env node
/**
 * cli:scaffold-dashboard-primitives — Scaffold theme-compliant dashboard
 * primitives (KpiCard, ChartCard, ListWidget, DashboardGrid, WidgetRenderer +
 * types + useDatavizPalette) into src/components/dashboard/.
 *
 * Idempotent: skips writes if existing content matches; honors `// @customised`
 * (or block form) at the top of a file. For the locale JSON, deep-merges the
 * `dashboard.*` namespace so siblings (auth.*, nav.*, entityLookup.*, …) survive.
 *
 * Non-blocking guard: ChartCard imports `recharts`; if the target app doesn't
 * have it installed, we warn + add a nextStep (never fail).
 */
import { parseArgs } from 'node:util';
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { dirname, join, resolve } from 'node:path';
import { validate } from './validate.js';
import { generate } from './generate.js';
import { generateEnvelope, failGenerate, printEnvelope } from '../../../../../lib/output.js';

const COMMAND = 'scaffold-dashboard-primitives';

function isCustomised(source: string): boolean {
  const head = source.trimStart().slice(0, 100);
  return head.startsWith('/* @customised') || head.startsWith('// @customised');
}

function deepMerge(base: unknown, patch: unknown): unknown {
  if (
    base !== null && typeof base === 'object' && !Array.isArray(base) &&
    patch !== null && typeof patch === 'object' && !Array.isArray(patch)
  ) {
    const out: Record<string, unknown> = { ...(base as Record<string, unknown>) };
    for (const [k, v] of Object.entries(patch as Record<string, unknown>)) {
      out[k] = deepMerge(out[k], v);
    }
    return out;
  }
  return patch;
}

function main(): void {
  const { values } = parseArgs({
    options: {
      spec: { type: 'string' },
      dry_run: { type: 'boolean', default: false },
    },
    strict: true,
  });

  if (!values.spec) {
    printEnvelope(failGenerate(COMMAND, ['--spec is required']));
    process.exit(1);
  }

  let raw: unknown;
  try {
    raw = JSON.parse(values.spec);
  } catch {
    printEnvelope(failGenerate(COMMAND, ['Invalid JSON in --spec']));
    process.exit(1);
  }

  const validation = validate(raw);
  if (!validation.valid || !validation.spec) {
    printEnvelope(failGenerate(COMMAND, validation.errors));
    process.exit(1);
  }

  const spec = validation.spec;
  const files = generate(spec);

  const warnings = [...validation.warnings];
  const nextSteps: string[] = [];

  // ChartCard needs recharts in the consuming app (it's a transitive dep of the
  // package but not guaranteed importable — esp. under pnpm). Warn, never fail.
  const rechartsPresent = existsSync(resolve(join(spec.projectPath, 'node_modules', 'recharts')));
  if (!rechartsPresent) {
    warnings.push('recharts is not installed in the target app — ChartCard will fail Vite resolution at build. Run `npm i recharts` in the web app.');
  }

  if (values.dry_run) {
    printEnvelope(generateEnvelope(COMMAND, {
      data: { dryRun: true, files: files.map((f) => f.path) },
      warnings,
    }));
    process.exit(0);
  }

  const filesCreated: string[] = [];
  const filesModified: string[] = [];
  const skipped: string[] = [];

  for (const file of files) {
    const abs = resolve(join(spec.projectPath, file.path));
    const exists = existsSync(abs);

    if (exists && file.strategy === 'overwrite') {
      const existing = readFileSync(abs, 'utf-8');
      if (isCustomised(existing)) { skipped.push(file.path); continue; }
      if (!spec.force && existing === file.content) continue;
      mkdirSync(dirname(abs), { recursive: true });
      writeFileSync(abs, file.content, 'utf-8');
      filesModified.push(abs);
    } else if (exists && file.strategy === 'deep-merge-json') {
      let existingJson: unknown = {};
      try {
        existingJson = JSON.parse(readFileSync(abs, 'utf-8'));
      } catch {
        existingJson = {};
      }
      const patch = JSON.parse(file.content);
      const merged = deepMerge(existingJson, patch);
      const out = JSON.stringify(merged, null, 2) + '\n';
      if (!spec.force && readFileSync(abs, 'utf-8') === out) continue;
      writeFileSync(abs, out, 'utf-8');
      filesModified.push(abs);
    } else {
      mkdirSync(dirname(abs), { recursive: true });
      writeFileSync(abs, file.content, 'utf-8');
      filesCreated.push(abs);
    }
  }

  if (skipped.length > 0) {
    nextSteps.push(`Skipped ${skipped.length} customised file(s): ${skipped.join(', ')}. Remove the @customised marker to allow overwrites.`);
  }
  if (filesCreated.length === 0 && filesModified.length === 0 && skipped.length === 0) {
    nextSteps.push('Dashboard primitives already up-to-date — no changes needed.');
  }
  if (filesCreated.length > 0 || filesModified.length > 0) {
    nextSteps.push('scaffold-component now resolves @/components/dashboard/WidgetRenderer for the dashboard view. Re-run audit-dev-frontend to clear DASH-002.');
  }

  printEnvelope(generateEnvelope(COMMAND, {
    data: { fileCount: filesCreated.length + filesModified.length, skipped: skipped.length, rechartsPresent },
    filesCreated,
    filesModified,
    warnings,
    nextSteps,
  }));
}

main();
