#!/usr/bin/env node
/**
 * CLI Scaffolder — generates a fully wired ByteLyst product repo.
 *
 * Usage:
 *   npx tsx scaffolder.ts                              # Interactive prompts
 *   npx tsx scaffolder.ts --from product.json           # From existing manifest
 *   npx tsx scaffolder.ts --from product.json --dry-run # Preview
 *
 * @module @bytelyst/create-app/scaffolder
 */

import { promises as fs } from 'node:fs';
import path from 'node:path';
import readline from 'node:readline';
import { renderTemplate, type TemplateVars } from './lib/template-engine.js';
import * as T from './lib/templates.js';

// ── Types ────────────────────────────────────────────────────────────────────

interface ProductManifest {
  productId: string;
  displayName: string;
  tagline: string;
  domain: string;
  backendPort: number;
  primarySurface: string;
  platforms: ('web' | 'mobile' | 'ios' | 'android')[];
  features: string[];
}

interface CliOptions {
  from: string | null;
  outDir: string | null;
  dryRun: boolean;
}

// ── CLI Arg Parsing ──────────────────────────────────────────────────────────

function parseCliArgs(): CliOptions {
  const args = process.argv.slice(2);
  const options: CliOptions = { from: null, outDir: null, dryRun: false };

  for (let i = 0; i < args.length; i++) {
    const arg = args[i];
    if (arg === '--from' || arg === '-f') options.from = args[++i];
    else if (arg === '--out' || arg === '-o') options.outDir = args[++i];
    else if (arg === '--dry-run' || arg === '-d') options.dryRun = true;
    else if (arg === '--help' || arg === '-h') {
      showHelp();
      process.exit(0);
    }
  }

  return options;
}

function showHelp(): void {
  // eslint-disable-next-line no-console
  console.log(`
@bytelyst/create-app — Product Repo Scaffolder

Generates a fully wired ByteLyst product repo with backend, web, and/or mobile.

Usage:
  npx tsx scaffolder.ts                          # Interactive prompts
  npx tsx scaffolder.ts --from product.json      # From existing manifest
  npx tsx scaffolder.ts --from product.json -d   # Dry run

Options:
  --from, -f       Path to existing product.json (skip prompts)
  --out, -o        Output directory (default: ./<productId>)
  --dry-run, -d    Preview files without writing
  --help, -h       Show this help

Interactive Prompts:
  1. Product name + ID + tagline + domain
  2. Backend port
  3. Platform selection: web, mobile (Expo), iOS, Android
  4. Feature selection: auth, billing, telemetry, flags, sync, push

Output:
  <productId>/
  ├── shared/product.json
  ├── backend/           (if selected)
  ├── web/               (if selected)
  ├── mobile/            (if selected)
  ├── .gitignore
  ├── .env.example
  ├── README.md
  └── AGENTS.md
`);
}

// ── Interactive Prompts ──────────────────────────────────────────────────────

function createPrompt(): (question: string) => Promise<string> {
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });

  return (question: string) =>
    new Promise(resolve => {
      rl.question(question, answer => {
        resolve(answer.trim());
      });
    });
}

async function gatherManifestInteractively(): Promise<ProductManifest> {
  const ask = createPrompt();

  // eslint-disable-next-line no-console
  console.log('\n📦 ByteLyst Product Scaffolder\n');

  const displayName = (await ask('Product name (e.g., FlowMonk): ')) || 'MyApp';
  const defaultId = displayName.toLowerCase().replace(/[^a-z0-9]/g, '');
  const productId = (await ask(`Product ID [${defaultId}]: `)) || defaultId;
  const tagline = (await ask('Tagline: ')) || `${displayName} — a ByteLyst product`;
  const defaultDomain = `${productId}.app`;
  const domain = (await ask(`Domain [${defaultDomain}]: `)) || defaultDomain;
  const backendPort = parseInt(await ask('Backend port [4020]: ')) || 4020;

  // eslint-disable-next-line no-console
  console.log('\nPlatforms (comma-separated: web, mobile, ios, android)');
  const platformStr = (await ask('Platforms [web]: ')) || 'web';
  const platforms = platformStr
    .split(',')
    .map(p => p.trim().toLowerCase()) as ProductManifest['platforms'];

  const primarySurface = platforms.includes('web') ? 'web' : platforms[0];

  // eslint-disable-next-line no-console
  console.log('\nFeatures (comma-separated: auth, billing, telemetry, flags, sync, push)');
  const featureStr = (await ask('Features [auth,telemetry,flags]: ')) || 'auth,telemetry,flags';
  const features = featureStr.split(',').map(f => f.trim().toLowerCase());

  // Close readline
  process.stdin.unref();

  return {
    productId,
    displayName,
    tagline,
    domain,
    backendPort,
    primarySurface,
    platforms,
    features,
  };
}

async function loadManifestFromFile(filePath: string): Promise<ProductManifest> {
  const raw = await fs.readFile(filePath, 'utf-8');
  const data = JSON.parse(raw);

  return {
    productId: data.productId || 'myapp',
    displayName: data.displayName || data.productId || 'MyApp',
    tagline: data.tagline || `${data.displayName} — a ByteLyst product`,
    domain: data.domain || `${data.productId}.app`,
    backendPort: data.backendPort || 4020,
    primarySurface: data.primarySurface || 'web',
    platforms: data.platforms || ['web'],
    features: data.features || ['auth', 'telemetry', 'flags'],
  };
}

// ── File Generator ───────────────────────────────────────────────────────────

interface GeneratedFile {
  path: string;
  content: string;
}

function generateFiles(manifest: ProductManifest): GeneratedFile[] {
  const vars: TemplateVars = {
    PRODUCT_ID: manifest.productId,
    DISPLAY_NAME: manifest.displayName,
    TAGLINE: manifest.tagline,
    DOMAIN: manifest.domain,
    BACKEND_PORT: manifest.backendPort,
    PRIMARY_SURFACE: manifest.primarySurface,
    HAS_BACKEND: true, // Always generate backend
    HAS_WEB: manifest.platforms.includes('web'),
    HAS_MOBILE: manifest.platforms.includes('mobile'),
    HAS_IOS: manifest.platforms.includes('ios'),
    HAS_ANDROID: manifest.platforms.includes('android'),
    HAS_AUTH: manifest.features.includes('auth'),
    HAS_BILLING: manifest.features.includes('billing'),
    HAS_TELEMETRY: manifest.features.includes('telemetry'),
    HAS_FLAGS: manifest.features.includes('flags'),
    HAS_SYNC: manifest.features.includes('sync'),
    HAS_PUSH: manifest.features.includes('push'),
  };

  const render = (tmpl: string) => renderTemplate(tmpl, vars);
  const files: GeneratedFile[] = [];

  // ── Root files
  files.push({ path: 'shared/product.json', content: render(T.PRODUCT_JSON) });
  files.push({ path: '.gitignore', content: render(T.GITIGNORE) });
  files.push({ path: '.env.example', content: render(T.ENV_EXAMPLE) });
  files.push({ path: 'README.md', content: render(T.README) });

  // ── Backend (always generated)
  files.push({ path: 'backend/package.json', content: render(T.BACKEND_PACKAGE_JSON) });
  files.push({ path: 'backend/tsconfig.json', content: render(T.BACKEND_TSCONFIG) });
  files.push({ path: 'backend/src/lib/config.ts', content: render(T.BACKEND_CONFIG) });
  files.push({
    path: 'backend/src/lib/product-config.ts',
    content: render(T.BACKEND_PRODUCT_CONFIG),
  });
  files.push({ path: 'backend/src/lib/auth.ts', content: render(T.BACKEND_AUTH) });
  files.push({
    path: 'backend/src/lib/request-context.ts',
    content: render(T.BACKEND_REQUEST_CONTEXT),
  });
  files.push({ path: 'backend/src/lib/errors.ts', content: render(T.BACKEND_ERRORS) });
  files.push({ path: 'backend/src/lib/datastore.ts', content: render(T.BACKEND_DATASTORE) });
  files.push({ path: 'backend/src/server.ts', content: render(T.BACKEND_SERVER) });

  // ── Web (if selected)
  if (manifest.platforms.includes('web')) {
    files.push({ path: 'web/package.json', content: render(T.WEB_PACKAGE_JSON) });
    files.push({ path: 'web/tsconfig.json', content: render(T.WEB_TSCONFIG) });
    files.push({ path: 'web/next.config.ts', content: render(T.WEB_NEXT_CONFIG) });
    files.push({ path: 'web/src/app/layout.tsx', content: render(T.WEB_LAYOUT) });
    files.push({ path: 'web/src/app/page.tsx', content: render(T.WEB_PAGE) });
    files.push({ path: 'web/src/lib/product-config.ts', content: render(T.WEB_PRODUCT_CONFIG) });
  }

  // ── Mobile (if selected)
  if (manifest.platforms.includes('mobile')) {
    files.push({ path: 'mobile/package.json', content: render(T.MOBILE_PACKAGE_JSON) });
    files.push({ path: 'mobile/app.json', content: render(T.MOBILE_APP_JSON) });
    files.push({ path: 'mobile/src/app/index.tsx', content: render(T.MOBILE_INDEX) });
  }

  return files;
}

// ── Main ─────────────────────────────────────────────────────────────────────

async function main(): Promise<void> {
  const cliOpts = parseCliArgs();

  const manifest = cliOpts.from
    ? await loadManifestFromFile(cliOpts.from)
    : await gatherManifestInteractively();

  const outDir = cliOpts.outDir || manifest.productId;
  const outPath = path.resolve(outDir);

  // eslint-disable-next-line no-console
  console.log(`\n🚀 Scaffolding ${manifest.displayName}`);
  // eslint-disable-next-line no-console
  console.log(`   Product ID: ${manifest.productId}`);
  // eslint-disable-next-line no-console
  console.log(`   Output:     ${outPath}`);
  // eslint-disable-next-line no-console
  console.log(`   Platforms:  ${manifest.platforms.join(', ')}`);
  // eslint-disable-next-line no-console
  console.log(`   Features:   ${manifest.features.join(', ')}`);
  if (cliOpts.dryRun) {
    // eslint-disable-next-line no-console
    console.log('   ⚠️  DRY RUN\n');
  }

  const files = generateFiles(manifest);

  if (cliOpts.dryRun) {
    // eslint-disable-next-line no-console
    console.log(`📄 ${files.length} files would be generated:\n`);
    for (const file of files) {
      // eslint-disable-next-line no-console
      console.log(`── ${file.path} ──────────────────────────────────────`);
      // eslint-disable-next-line no-console
      console.log(file.content);
    }
    // eslint-disable-next-line no-console
    console.log(`\n✨ Dry run complete. Re-run without --dry-run to write files.`);
    return;
  }

  // Write files
  for (const file of files) {
    const fullPath = path.join(outPath, file.path);
    await fs.mkdir(path.dirname(fullPath), { recursive: true });
    await fs.writeFile(fullPath, file.content, 'utf-8');
    // eslint-disable-next-line no-console
    console.log(`   ✅ ${file.path}`);
  }

  // eslint-disable-next-line no-console
  console.log(`\n✨ ${manifest.displayName} scaffolded at ${outPath}`);
  // eslint-disable-next-line no-console
  console.log(`\nNext steps:`);
  // eslint-disable-next-line no-console
  console.log(`  cd ${outDir}/backend && npm install && npm run dev`);
  if (manifest.platforms.includes('web')) {
    // eslint-disable-next-line no-console
    console.log(`  cd ${outDir}/web && npm install && npm run dev`);
  }
}

// Export for testing
export { generateFiles, renderTemplate, type ProductManifest, type GeneratedFile };

main().catch(err => {
  // eslint-disable-next-line no-console
  console.error('❌ Error:', err instanceof Error ? err.message : String(err));
  process.exit(1);
});
