/**
 * Factory B top-level orchestrator
 *
 * Single entry point the realize engine calls. Returns a map of
 * `relativePath → content` for every file the factory decides to
 * write, after applying regeneration-safety triage. Realize handles
 * the actual filesystem writes via its normal multi-file generator
 * pipeline.
 *
 * Skipped (user-edited) files are NOT in the returned map — realize
 * never sees them, so user edits are safe. The hash manifest is
 * emitted as one of the returned files (`.specverse-gen/hashes.json`)
 * so it rolls through the same write pipeline.
 */

import { existsSync, mkdirSync, writeFileSync } from 'fs';
import { join, dirname } from 'path';
import { generate as generateViews } from './views-generator.js';
import { generate as generateAppTsx } from './app-tsx-generator.js';
import { generate as generatePackageJson } from './package-json-generator.js';
import {
  loadHashManifest,
  reconcileWrites,
  summarize,
  HASHES_DIR,
  HASHES_FILE,
} from './regen-safety.js';
import type { ExpandedSpec } from './views-generator.js';

/**
 * Context shape as it arrives from realize's code-generator. The
 * generator protocol is "function receives context, returns string
 * (the single file contents)". This orchestrator is special: instead
 * of one file, it produces many (views × models, App.tsx,
 * package.json, regen-safety manifest). It writes them directly and
 * returns '' so realize's write pipeline skips the no-op.
 *
 * `outputDir` and `frontendDir` come from the realize-time context.
 * `frontendDir` may be '.' for standalone layouts or 'frontend' for
 * monorepos — either way, the resolved write root is
 * `outputDir + frontendDir`.
 */
export interface OrchestratorContext {
  spec: ExpandedSpec & { metadata?: { name?: string }; name?: string };
  outputDir: string;
  frontendDir?: string;
  manifest?: unknown;
}

/**
 * Produce the complete file set for a Factory B run, writing each
 * file directly. Returns '' so the caller's single-file writeOutput
 * short-circuits.
 */
export async function generate(context: OrchestratorContext): Promise<string> {
  const { spec, outputDir } = context;
  const frontendDir = context.frontendDir || 'frontend';
  const projectRoot = frontendDir === '.' ? outputDir : join(outputDir, frontendDir);

  // 1. Produce all file contents (pure composition — no I/O yet).
  const proposed: Record<string, string> = {};

  const viewFiles = await generateViews({ spec });
  Object.assign(proposed, viewFiles);

  proposed['src/App.tsx'] = await generateAppTsx({ spec });
  proposed['package.json'] = await generatePackageJson({ spec });

  // 2. Content-hashing triage against the prior manifest. Read-only.
  const prevManifest = loadHashManifest(projectRoot);
  const result = reconcileWrites(projectRoot, proposed, prevManifest);

  // 3. Log the summary for operator visibility.
  console.log(summarize(result, projectRoot));

  // 4. Write the approved files + the updated hash manifest.
  const toWrite: Record<string, string> = {
    ...result.approvedWrites,
    [`${HASHES_DIR}/${HASHES_FILE}`]: JSON.stringify(result.manifest, null, 2) + '\n',
  };

  for (const [rel, content] of Object.entries(toWrite)) {
    const abs = join(projectRoot, rel);
    const dir = dirname(abs);
    if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
    writeFileSync(abs, content, 'utf-8');
  }

  // Realize's single-file writeOutput skips empty strings (see
  // realize/index.ts writeOutput). The orchestrator already wrote
  // everything it needed.
  return '';
}

export default generate;
