/**
 * P2 — Factory generators consume the pattern library, not forks
 *
 * Architectural invariant I5 from VIEW-RENDERING-ARCHITECTURE.md:
 *   "Factory generators are consumers of the pattern library, not
 *    reimplementations of it."
 *
 * Every composer / body-generator under react-starter must import
 * from `@specverse/runtime/views/core` or `@specverse/runtime/views/tailwind`.
 * A generator that doesn't is a reimplementation — it's drift-prone
 * and violates the "one pattern library, three consumers" contract.
 *
 * Scope: files matching *-composer.ts or view-emitter.ts / generator
 * files that actually produce view-layout code. Skeletons (.tsx.template)
 * are content-only and exempt. helpers-emitter / html-to-jsx are
 * transformation utilities, exempt — they don't consume pattern data.
 */

import { describe, it, expect } from 'vitest';
import { readdirSync, readFileSync } from 'fs';
import { join, resolve } from 'path';
import { fileURLToPath } from 'url';

const HERE = fileURLToPath(import.meta.url);
const STARTER_DIR = resolve(HERE, '..', '..');

const RUNTIME_IMPORT_PATTERN =
  /from\s+['"]@specverse\/runtime\/views\/(core|tailwind)['"]/;

/**
 * Files required to import from @specverse/runtime/views/core or
 * /views/tailwind. The filenames match the convention of any generator
 * that emits view layout or composes pattern data.
 */
const MUST_IMPORT_FROM_RUNTIME = (filename: string): boolean => {
  // Every body composer (list / detail / form / dashboard).
  if (/-body-composer\.ts$/.test(filename)) return true;
  return false;
};

/**
 * Files explicitly exempt from P2 — orchestrators, transformers, and
 * utilities that don't consume pattern data. Each exemption carries
 * a short justification as an inline comment near the import list.
 */
const EXEMPT_FILES = new Set([
  // Orchestrator: dispatches to composers (which do import runtime).
  'views-generator.ts',
  'view-emitter.ts',
  'orchestrator.ts',
  // Pure string transformer, no pattern data.
  'html-to-jsx.ts',
  // Emits inline source code; no adapter calls needed.
  'helpers-emitter.ts',
  'app-tsx-generator.ts',
  'package-json-generator.ts',
  // Pure utility for content-hashing, no pattern consumption.
  'regen-safety.ts',
]);

function collectTypeScriptFiles(dir: string, prefix = ''): string[] {
  const out: string[] = [];
  const entries = readdirSync(dir, { withFileTypes: true });
  for (const entry of entries) {
    if (entry.name === '__tests__' || entry.name === 'skeletons') continue;
    const full = join(dir, entry.name);
    const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
    if (entry.isDirectory()) {
      out.push(...collectTypeScriptFiles(full, rel));
    } else if (entry.isFile() && entry.name.endsWith('.ts') && !entry.name.endsWith('.test.ts')) {
      out.push(rel);
    }
  }
  return out;
}

describe('P2 — factory generators consume the pattern library', () => {
  const files = collectTypeScriptFiles(STARTER_DIR);

  it('produces a non-empty file list (sanity)', () => {
    expect(files.length).toBeGreaterThan(0);
  });

  for (const relPath of files) {
    const filename = relPath.split('/').pop()!;

    if (EXEMPT_FILES.has(filename)) continue;
    if (!MUST_IMPORT_FROM_RUNTIME(filename)) continue;

    it(`${relPath} imports from @specverse/runtime/views/{core|tailwind}`, () => {
      const content = readFileSync(join(STARTER_DIR, relPath), 'utf8');
      const imports = RUNTIME_IMPORT_PATTERN.test(content);
      if (!imports) {
        throw new Error(
          `${relPath} must import from @specverse/runtime/views/core or ` +
          `@specverse/runtime/views/tailwind to satisfy invariant I5. ` +
          `If it genuinely doesn't need pattern data, add it to the ` +
          `EXEMPT_FILES set with a justification. Generators that emit ` +
          `view-layout code inevitably use pattern constants (field ` +
          `classification, atomic components) — sourcing those from ` +
          `runtime is what keeps the three consumers in sync.`
        );
      }
    });
  }

  it('every composer is covered by an explicit rule (no silent skips)', () => {
    const composers = files.filter(f => f.endsWith('-body-composer.ts'));
    expect(composers.length).toBeGreaterThanOrEqual(4); // list/detail/form/dashboard at minimum
    for (const c of composers) {
      const filename = c.split('/').pop()!;
      expect(MUST_IMPORT_FROM_RUNTIME(filename)).toBe(true);
      expect(EXEMPT_FILES.has(filename)).toBe(false);
    }
  });
});
