/**
 * Stage 3 (F4): extract Page Objects + a fixtures entry point from flows shared
 * across saved specs.
 *
 * Reads detectSharedFlows (>= 3 specs sharing an entry prefix — the scaffold's
 * 3-use threshold), generates a `pages/<Name>.ts` per flow, and (re)writes a
 * single `fixtures.ts` that registers each Page Object via `base.extend`. New
 * specs `import { test, expect } from './fixtures'` and consume e.g.
 * `async ({ page, loginPage }) => …`.
 *
 * Manual trigger (Stage 3b): invoked by a CLI command, not on every save, and
 * it never rewrites already-committed specs (D6) — it only emits the shared
 * pages/ + fixtures.ts going forward.
 */
import { mkdir, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { detectSharedFlows } from './detectSharedFlows.js';
import { generatePageObject } from './generatePageObject.js';
import { writePageObjectManifest, type PageObjectEntry } from './pageObjectManifest.js';

export interface ExtractedPage {
  className: string;
  methodName: string;
  fileName: string;
  /** Absolute path written. */
  path: string;
  /** Slugs of the specs that share this flow. */
  specs: string[];
}

export interface ExtractResult {
  pages: ExtractedPage[];
  /** Absolute path of the written fixtures.ts, or null when nothing extracted. */
  fixturesPath: string | null;
}

export async function extractPageObjects(
  devRoot: string,
  opts: { minSpecs?: number } = {},
): Promise<ExtractResult> {
  // 3-use threshold for extraction; lower thresholds only *report* (Stage 2).
  const flows = await detectSharedFlows(devRoot, { minSpecs: opts.minSpecs ?? 3 });
  if (flows.length === 0) return { pages: [], fixturesPath: null };

  const testsDir = join(devRoot, '__vibe_tests__');
  const pagesDir = join(testsDir, 'pages');
  await mkdir(pagesDir, { recursive: true });

  const pages: ExtractedPage[] = [];
  const entries: PageObjectEntry[] = [];
  const usedNames = new Set<string>();
  for (const flow of flows) {
    const probe = generatePageObject(flow.prefixSteps);
    const className = uniqueName(probe.className, usedNames);
    const po =
      className === probe.className
        ? probe
        : generatePageObject(flow.prefixSteps, { className });
    const path = join(pagesDir, po.fileName);
    await writeFile(path, po.source, 'utf-8');
    pages.push({
      className: po.className,
      methodName: po.methodName,
      fileName: po.fileName,
      path,
      specs: flow.specs,
    });
    entries.push({
      className: po.className,
      methodName: po.methodName,
      fixtureName: fixtureName(po.className),
      fileName: po.fileName,
      signatures: flow.signatures,
      specs: flow.specs,
    });
  }

  const fixturesPath = join(testsDir, 'fixtures.ts');
  await writeFile(fixturesPath, renderFixtures(pages), 'utf-8');
  // Manifest lets writeSpec match a new spec's prefix to a Page Object and
  // consume it (Stage 3c) without re-running detection.
  await writePageObjectManifest(devRoot, entries);
  return { pages, fixturesPath };
}

function renderFixtures(pages: ExtractedPage[]): string {
  const lines: string[] = [];
  lines.push(`import { test as base } from '@playwright/test';`);
  for (const p of pages) {
    lines.push(`import { ${p.className} } from './pages/${p.className}';`);
  }
  lines.push('');
  lines.push('/**');
  lines.push(' * Generated by Hover — Page Object fixtures lifted from flows shared');
  lines.push(" * across specs. In a new spec: `import { test, expect } from './fixtures';`");
  lines.push(' * then consume e.g. `async ({ page, loginPage }) => …`.');
  lines.push(' */');
  const typeMembers = pages.map(p => `${fixtureName(p.className)}: ${p.className}`).join('; ');
  lines.push(`export const test = base.extend<{ ${typeMembers} }>({`);
  for (const p of pages) {
    lines.push(`  ${fixtureName(p.className)}: async ({ page }, use) => {`);
    lines.push(`    await use(new ${p.className}(page));`);
    lines.push(`  },`);
  }
  lines.push(`});`);
  lines.push('');
  lines.push(`export { expect } from '@playwright/test';`);
  lines.push('');
  return lines.join('\n');
}

/** Class name -> fixture key: LoginPage -> loginPage. */
function fixtureName(className: string): string {
  return className.charAt(0).toLowerCase() + className.slice(1);
}

function uniqueName(base: string, used: Set<string>): string {
  let name = base;
  let n = 2;
  while (used.has(name)) name = `${base}${n++}`;
  used.add(name);
  return name;
}
