#!/usr/bin/env node

/**
 * Component Registry Generator
 *
 * src/components/ 하위 .tsx 파일을 스캔하여
 * - src/generated/component-registry.json (페이지용 데이터)
 * - claudedocs/components/_registry.md (에디터/git 이력용)
 * 두 가지 출력을 생성합니다.
 *
 * 사용: node scripts/gen-component-registry.mjs
 */

import { readdir, readFile, writeFile, mkdir, stat } from 'node:fs/promises';
import { join, relative, basename, dirname, extname } from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = join(__dirname, '..');
const SRC_COMPONENTS = join(ROOT, 'src', 'components');
const OUT_JSON = join(ROOT, 'src', 'generated', 'component-registry.json');
const OUT_MD = join(ROOT, 'claudedocs', 'components', '_registry.md');

// Tier mapping by directory name
const TIER_MAP = {
  ui: 'ui',
  atoms: 'atoms',
  molecules: 'molecules',
  organisms: 'organisms',
  common: 'common',
  layout: 'layout',
  dev: 'dev',
};

// Skip patterns
const SKIP_FILES = new Set(['index.ts', 'index.tsx', 'actions.ts', 'actions.tsx', 'types.ts', 'types.tsx', 'utils.ts', 'utils.tsx', 'constants.ts', 'constants.tsx', 'schema.ts', 'schema.tsx']);
const SKIP_PATTERNS = [
  /\.test\./,
  /\.spec\./,
  /\.stories\./,
  /^use[A-Z].*\.ts$/,  // hooks files (useXxx.ts only, not .tsx)
];

function shouldSkip(fileName) {
  if (SKIP_FILES.has(fileName)) return true;
  return SKIP_PATTERNS.some(p => p.test(fileName));
}

function getTier(relPath) {
  const firstDir = relPath.split('/')[0];
  return TIER_MAP[firstDir] || 'domain';
}

function getCategory(relPath) {
  const parts = relPath.split('/');
  // category = first directory under components/
  return parts[0];
}

function getSubcategory(relPath) {
  const parts = relPath.split('/');
  // subcategory = second directory if exists
  return parts.length > 2 ? parts[1] : null;
}

function extractComponentInfo(content, fileName) {
  const isClient = /^['"]use client['"];?/m.test(content);

  // Extract component name from exports
  let name = null;
  let exportType = 'none';

  // Check default export: export default function Foo / export default Foo
  const defaultFuncMatch = content.match(/export\s+default\s+function\s+([A-Z]\w*)/);
  const defaultConstMatch = content.match(/export\s+default\s+([A-Z]\w*)/);
  // Check named exports: export function Foo / export const Foo
  const namedFuncMatches = [...content.matchAll(/export\s+(?:async\s+)?function\s+([A-Z]\w*)/g)];
  const namedConstMatches = [...content.matchAll(/export\s+(?:const|let)\s+([A-Z]\w*)/g)];

  const hasDefault = !!(defaultFuncMatch || defaultConstMatch);
  const namedExports = [
    ...namedFuncMatches.map(m => m[1]),
    ...namedConstMatches.map(m => m[1]),
  ].filter(n => n !== undefined);

  // Remove default export name from named list if it also appears
  if (defaultFuncMatch) {
    name = defaultFuncMatch[1];
  } else if (defaultConstMatch) {
    name = defaultConstMatch[1];
  }

  const hasNamed = namedExports.length > 0;

  if (hasDefault && hasNamed) {
    exportType = 'both';
  } else if (hasDefault) {
    exportType = 'default';
  } else if (hasNamed) {
    exportType = 'named';
    name = namedExports[0]; // Use first named export as component name
  }

  // Fallback: derive name from file name
  if (!name) {
    const base = basename(fileName, extname(fileName));
    name = base
      .split(/[-_]/)
      .map(s => s.charAt(0).toUpperCase() + s.slice(1))
      .join('');
  }

  // Props detection
  const hasProps = /(?:interface|type)\s+\w*Props/.test(content);
  const propsNameMatch = content.match(/(?:interface|type)\s+(\w*Props)/);
  const propsName = propsNameMatch ? propsNameMatch[1] : null;

  // Line count
  const lineCount = content.split('\n').length;

  return { name, exportType, hasProps, propsName, isClientComponent: isClient, lineCount };
}

async function scanDirectory(dir) {
  const entries = await readdir(dir, { withFileTypes: true });
  const results = [];

  for (const entry of entries) {
    const fullPath = join(dir, entry.name);

    if (entry.isDirectory()) {
      results.push(...await scanDirectory(fullPath));
    } else if (entry.isFile() && entry.name.endsWith('.tsx')) {
      if (shouldSkip(entry.name)) continue;

      const relPath = relative(SRC_COMPONENTS, fullPath);
      const content = await readFile(fullPath, 'utf-8');
      const info = extractComponentInfo(content, entry.name);

      results.push({
        name: info.name,
        fileName: entry.name,
        filePath: `src/components/${relPath}`,
        tier: getTier(relPath),
        category: getCategory(relPath),
        subcategory: getSubcategory(relPath),
        exportType: info.exportType,
        hasProps: info.hasProps,
        propsName: info.propsName,
        isClientComponent: info.isClientComponent,
        lineCount: info.lineCount,
      });
    }
  }

  return results;
}

function buildCategories(components) {
  const map = new Map();
  for (const comp of components) {
    const key = `${comp.tier}::${comp.category}`;
    if (!map.has(key)) {
      map.set(key, { tier: comp.tier, category: comp.category, count: 0 });
    }
    map.get(key).count++;
  }
  return [...map.values()].sort((a, b) => a.tier.localeCompare(b.tier) || a.category.localeCompare(b.category));
}

function generateMarkdown(registry) {
  const now = registry.generatedAt;
  const lines = [
    `# Component Registry`,
    ``,
    `> Auto-generated: ${now}  `,
    `> Total: **${registry.totalCount}** components`,
    ``,
  ];

  // Group by tier, then category
  const byTier = new Map();
  for (const comp of registry.components) {
    if (!byTier.has(comp.tier)) byTier.set(comp.tier, new Map());
    const tierMap = byTier.get(comp.tier);
    if (!tierMap.has(comp.category)) tierMap.set(comp.category, []);
    tierMap.get(comp.category).push(comp);
  }

  const tierOrder = ['ui', 'atoms', 'molecules', 'organisms', 'common', 'layout', 'dev', 'domain'];
  for (const tier of tierOrder) {
    const categories = byTier.get(tier);
    if (!categories) continue;

    const tierCount = [...categories.values()].reduce((s, arr) => s + arr.length, 0);
    lines.push(`## ${tier.toUpperCase()} (${tierCount})`);
    lines.push(``);

    for (const [category, comps] of [...categories.entries()].sort()) {
      lines.push(`### ${category} (${comps.length})`);
      lines.push(``);
      lines.push(`| Component | File | Export | Props | Client | Lines |`);
      lines.push(`|-----------|------|--------|-------|--------|-------|`);

      for (const c of comps.sort((a, b) => a.name.localeCompare(b.name))) {
        const client = c.isClientComponent ? 'Y' : '';
        const props = c.hasProps ? (c.propsName || 'Y') : '';
        lines.push(`| ${c.name} | ${c.fileName} | ${c.exportType} | ${props} | ${client} | ${c.lineCount} |`);
      }
      lines.push(``);
    }
  }

  return lines.join('\n');
}

async function main() {
  console.log('Scanning src/components/...');

  const components = await scanDirectory(SRC_COMPONENTS);
  components.sort((a, b) => a.tier.localeCompare(b.tier) || a.category.localeCompare(b.category) || a.name.localeCompare(b.name));

  const registry = {
    generatedAt: new Date().toISOString(),
    totalCount: components.length,
    categories: buildCategories(components),
    components,
  };

  // Ensure output directories
  await mkdir(dirname(OUT_JSON), { recursive: true });
  await mkdir(dirname(OUT_MD), { recursive: true });

  // Write JSON
  await writeFile(OUT_JSON, JSON.stringify(registry, null, 2), 'utf-8');
  console.log(`  JSON: ${relative(ROOT, OUT_JSON)} (${registry.totalCount} components)`);

  // Write Markdown
  const md = generateMarkdown(registry);
  await writeFile(OUT_MD, md, 'utf-8');
  console.log(`  MD:   ${relative(ROOT, OUT_MD)}`);

  // Summary
  console.log(`\nSummary by tier:`);
  const tierCounts = {};
  for (const c of components) {
    tierCounts[c.tier] = (tierCounts[c.tier] || 0) + 1;
  }
  for (const [tier, count] of Object.entries(tierCounts).sort()) {
    console.log(`  ${tier}: ${count}`);
  }
  console.log(`\nDone!`);
}

main().catch(err => {
  console.error('Error:', err);
  process.exit(1);
});
