#!/usr/bin/env node
/**
 * API Route Generator — Next.js App Router
 *
 * Generates two files:
 *   src/app/api/<name>/route.ts       — GET (list) + POST (create)
 *   src/app/api/<name>/[id]/route.ts  — GET (detail) + PATCH (update) + DELETE
 *
 * Follows the pattern used across ByteLyst product dashboards:
 *   - Named exports (export const GET, POST, PATCH, DELETE)
 *   - withErrorHandler HOF wrapper
 *   - Auth via getCurrentUser / getAccessToken
 *   - Zod validation on POST/PATCH bodies
 *   - NextRequest + NextResponse
 *
 * Usage:
 *   npx tsx src/generators/api-routes.ts --name tasks --fields "title:string,status:enum(pending,active,done),priority:number?" --target ../some-web/src
 *   npx tsx src/generators/api-routes.ts --name tasks --fields "title:string" --mode proxy --target ../some-web/src
 *
 * Modes:
 *   --mode direct   (default) Direct Cosmos DB access via repository functions
 *   --mode proxy    Proxy to product backend via fetch (for thin web clients)
 *
 * @module @bytelyst/create-app/generators/api-routes
 */

/* eslint-disable no-console -- This generator is a CLI; console output is its user interface. */

import { promises as fs } from 'node:fs';
import path from 'node:path';

// ── CLI ──────────────────────────────────────────────────────────────────────

interface Options {
  name: string;
  fields: string;
  target: string;
  mode: 'direct' | 'proxy';
  methods: string[];
  withHandler: boolean;
  dryRun: boolean;
}

function parseArgs(): Options {
  const args = process.argv.slice(2);
  const options: Options = {
    name: '',
    fields: '',
    target: './src',
    mode: 'direct',
    methods: ['GET', 'POST', 'PATCH', 'DELETE'],
    withHandler: true,
    dryRun: false,
  };

  for (let i = 0; i < args.length; i++) {
    const arg = args[i];
    if (arg === '--name' || arg === '-n') options.name = args[++i];
    else if (arg === '--fields' || arg === '-f') options.fields = args[++i];
    else if (arg === '--target' || arg === '-t') options.target = args[++i];
    else if (arg === '--mode' || arg === '-m') options.mode = args[++i] as 'direct' | 'proxy';
    else if (arg === '--methods')
      options.methods = args[++i].split(',').map(m => m.trim().toUpperCase());
    else if (arg === '--no-handler') options.withHandler = false;
    else if (arg === '--dry-run' || arg === '-d') options.dryRun = true;
    else if (arg === '--help' || arg === '-h') {
      showHelp();
      process.exit(0);
    }
  }

  if (!options.name) {
    console.error('Error: --name is required');
    showHelp();
    process.exit(1);
  }

  if (!/^[a-z][a-z0-9-]*$/.test(options.name)) {
    console.error('Error: --name must be lowercase alphanumeric with optional hyphens');
    process.exit(1);
  }

  if (!['direct', 'proxy'].includes(options.mode)) {
    console.error('Error: --mode must be "direct" or "proxy"');
    process.exit(1);
  }

  return options;
}

function showHelp(): void {
  console.log(`
API Route Generator — Next.js App Router

Generates CRUD API route files for a Next.js App Router project.

Usage:
  npx tsx api-routes.ts --name <name> [--fields "<field:type,...>"] [options]

Options:
  --name, -n         Entity name (e.g., "tasks", "sessions")     [required]
  --fields, -f       Comma-separated field definitions            [optional for proxy mode]
  --target, -t       Target src/ directory (default: "./src")
  --mode, -m         "direct" (Cosmos DB) or "proxy" (backend fetch)  [default: direct]
  --methods          HTTP methods to generate (default: GET,POST,PATCH,DELETE)
  --no-handler       Skip withErrorHandler wrapper
  --dry-run, -d      Preview without writing files
  --help, -h         Show this help

Field Types (same as gen-module):
  string             z.string()
  number             z.number()
  boolean            z.boolean()
  date               z.string().datetime()
  enum(a,b,c)        z.enum(["a","b","c"])
  Append ? for optional fields

Modes:
  direct   — Generates route files that call repository functions (direct Cosmos DB).
             Also generates a lib/repositories/<name>.ts and lib/schemas/<name>.ts.
  proxy    — Generates route files that proxy to a product backend via fetch.
             Requires lib/api-helpers.ts to exist (createApiClient pattern).

Examples:
  # Direct mode (full CRUD with Cosmos)
  npx tsx api-routes.ts --name tasks \\
    --fields "title:string,status:enum(pending,active,done),priority:number?" \\
    --target ./src

  # Proxy mode (thin web client forwarding to backend)
  npx tsx api-routes.ts --name tasks --mode proxy --target ./src

  # Preview only
  npx tsx api-routes.ts --name tasks --fields "title:string" --dry-run
`);
}

// ── Field Parser ─────────────────────────────────────────────────────────────

interface ParsedField {
  name: string;
  type: string;
  optional: boolean;
  zodType: string;
  tsType: string;
  enumValues: string[] | null;
}

function splitFields(str: string): string[] {
  const parts: string[] = [];
  let depth = 0;
  let current = '';
  for (const ch of str) {
    if (ch === '(') depth++;
    if (ch === ')') depth--;
    if (ch === ',' && depth === 0) {
      parts.push(current.trim());
      current = '';
    } else {
      current += ch;
    }
  }
  if (current.trim()) parts.push(current.trim());
  return parts;
}

function parseFields(fieldsStr: string): ParsedField[] {
  if (!fieldsStr) return [];
  const fields: ParsedField[] = [];
  const fieldDefs = splitFields(fieldsStr);

  for (const raw of fieldDefs) {
    const def = raw.trim();
    if (!def) continue;

    const optional = def.endsWith('?');
    const cleaned = optional ? def.slice(0, -1) : def;
    const colonIdx = cleaned.indexOf(':');
    if (colonIdx === -1) throw new Error(`Invalid field (missing type): ${def}`);

    const name = cleaned.slice(0, colonIdx).trim();
    const type = cleaned.slice(colonIdx + 1).trim();

    if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
      throw new Error(`Invalid field name: ${name}`);
    }

    let zodType: string;
    let tsType: string;
    let enumValues: string[] | null = null;

    if (type.startsWith('enum(') && type.endsWith(')')) {
      enumValues = type
        .slice(5, -1)
        .split(',')
        .map(v => v.trim())
        .filter(Boolean);
      if (enumValues.length === 0) throw new Error(`Empty enum: ${def}`);
      zodType = `z.enum([${enumValues.map(v => `'${v}'`).join(', ')}])`;
      tsType = enumValues.map(v => `'${v}'`).join(' | ');
    } else {
      const MAP: Record<string, { zod: string; ts: string }> = {
        string: { zod: 'z.string().min(1)', ts: 'string' },
        number: { zod: 'z.number()', ts: 'number' },
        boolean: { zod: 'z.boolean()', ts: 'boolean' },
        date: { zod: 'z.string().datetime()', ts: 'string' },
        datetime: { zod: 'z.string().datetime()', ts: 'string' },
        'string[]': { zod: 'z.array(z.string())', ts: 'string[]' },
        'number[]': { zod: 'z.array(z.number())', ts: 'number[]' },
      };
      const mapping = MAP[type];
      if (!mapping) throw new Error(`Unknown field type "${type}" in: ${def}`);
      zodType = mapping.zod;
      tsType = mapping.ts;
    }

    fields.push({ name, type, optional, zodType, tsType, enumValues });
  }
  return fields;
}

// ── Helpers ──────────────────────────────────────────────────────────────────

function pascal(s: string): string {
  return s.replace(/(^|-)([a-z])/g, (_, __, c: string) => c.toUpperCase());
}
// ── Proxy Mode Templates ────────────────────────────────────────────────────

function genProxyListRoute(name: string, methods: string[], withHandler: boolean): string {
  const hasGet = methods.includes('GET');
  const hasPost = methods.includes('POST');
  const handlerImport = withHandler
    ? `import { withErrorHandler } from '@/lib/api-handler';\n`
    : '';
  const wrap = (code: string) => (withHandler ? `withErrorHandler(${code})` : code);

  let out = `import { NextRequest, NextResponse } from 'next/server';
import { getAccessToken } from '@/lib/api-helpers';
${handlerImport}
const BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL ?? 'http://localhost:4000';
`;

  if (hasGet) {
    out += `
export const GET = ${wrap(`async (req: NextRequest) => {
  const token = await getAccessToken(req);
  if (!token) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });

  const url = new URL(req.url);
  const params = url.searchParams.toString();
  const res = await fetch(\`\${BACKEND_URL}/api/${name}\${params ? '?' + params : ''}\`, {
    headers: { Authorization: \`Bearer \${token}\` },
  });
  const data = await res.json();
  return NextResponse.json(data, { status: res.status });
}`)};
`;
  }

  if (hasPost) {
    out += `
export const POST = ${wrap(`async (req: NextRequest) => {
  const token = await getAccessToken(req);
  if (!token) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });

  const body = await req.json();
  const res = await fetch(\`\${BACKEND_URL}/api/${name}\`, {
    method: 'POST',
    headers: { Authorization: \`Bearer \${token}\`, 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  });
  const data = await res.json();
  return NextResponse.json(data, { status: res.status });
}`)};
`;
  }

  return out;
}

function genProxyDetailRoute(name: string, methods: string[], withHandler: boolean): string {
  const hasGet = methods.includes('GET');
  const hasPatch = methods.includes('PATCH');
  const hasDelete = methods.includes('DELETE');
  const handlerImport = withHandler
    ? `import { withErrorHandler } from '@/lib/api-handler';\n`
    : '';
  const wrap = (code: string) => (withHandler ? `withErrorHandler(${code})` : code);

  let out = `import { NextRequest, NextResponse } from 'next/server';
import { getAccessToken } from '@/lib/api-helpers';
${handlerImport}
const BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL ?? 'http://localhost:4000';

type RouteContext = { params: Promise<{ id: string }> };
`;

  if (hasGet) {
    out += `
export const GET = ${wrap(`async (req: NextRequest, { params }: RouteContext) => {
  const token = await getAccessToken(req);
  if (!token) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });

  const { id } = await params;
  const res = await fetch(\`\${BACKEND_URL}/api/${name}/\${id}\`, {
    headers: { Authorization: \`Bearer \${token}\` },
  });
  const data = await res.json();
  return NextResponse.json(data, { status: res.status });
}`)};
`;
  }

  if (hasPatch) {
    out += `
export const PATCH = ${wrap(`async (req: NextRequest, { params }: RouteContext) => {
  const token = await getAccessToken(req);
  if (!token) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });

  const { id } = await params;
  const body = await req.json();
  const res = await fetch(\`\${BACKEND_URL}/api/${name}/\${id}\`, {
    method: 'PATCH',
    headers: { Authorization: \`Bearer \${token}\`, 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  });
  const data = await res.json();
  return NextResponse.json(data, { status: res.status });
}`)};
`;
  }

  if (hasDelete) {
    out += `
export const DELETE = ${wrap(`async (req: NextRequest, { params }: RouteContext) => {
  const token = await getAccessToken(req);
  if (!token) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });

  const { id } = await params;
  const res = await fetch(\`\${BACKEND_URL}/api/${name}/\${id}\`, {
    method: 'DELETE',
    headers: { Authorization: \`Bearer \${token}\` },
  });
  if (res.status === 204) return new NextResponse(null, { status: 204 });
  const data = await res.json();
  return NextResponse.json(data, { status: res.status });
}`)};
`;
  }

  return out;
}

// ── Direct Mode Templates ───────────────────────────────────────────────────

function genSchemaFile(name: string, fields: ParsedField[]): string {
  const P = pascal(name);
  const createFields = fields
    .map(f => `  ${f.name}: ${f.zodType}${f.optional ? '.optional()' : ''},`)
    .join('\n');
  const updateFields = fields.map(f => `  ${f.name}: ${f.zodType}.optional(),`).join('\n');

  return `import { z } from 'zod';

export const Create${P}Schema = z.object({
${createFields}
});

export const Update${P}Schema = z.object({
${updateFields}
});

export type Create${P}Input = z.infer<typeof Create${P}Schema>;
export type Update${P}Input = z.infer<typeof Update${P}Schema>;
`;
}

function genRepositoryFile(name: string, fields: ParsedField[]): string {
  const P = pascal(name);
  const docFields = fields.map(f => `  ${f.name}${f.optional ? '?' : ''}: ${f.tsType};`).join('\n');

  return `import { randomUUID } from 'node:crypto';
import { getCosmosContainer, PRODUCT_ID } from '@/lib/datastore';
import type { Create${P}Input, Update${P}Input } from '@/lib/schemas/${name}';

export interface ${P}Doc {
  id: string;
  productId: string;
  userId: string;
${docFields}
  createdAt: string;
  updatedAt: string;
}

function getContainer() {
  return getCosmosContainer('${name}');
}

export async function list${P}(userId: string, limit = 50, offset = 0): Promise<${P}Doc[]> {
  const { resources } = await getContainer()
    .items.query<${P}Doc>({
      query: 'SELECT * FROM c WHERE c.userId = @uid AND c.productId = @pid ORDER BY c.createdAt DESC OFFSET @off LIMIT @lim',
      parameters: [
        { name: '@uid', value: userId },
        { name: '@pid', value: PRODUCT_ID },
        { name: '@off', value: offset },
        { name: '@lim', value: limit },
      ],
    })
    .fetchAll();
  return resources;
}

export async function get${P}(id: string, userId: string): Promise<${P}Doc | null> {
  try {
    const { resource } = await getContainer().item(id, userId).read<${P}Doc>();
    return resource ?? null;
  } catch {
    return null;
  }
}

export async function create${P}(userId: string, input: Create${P}Input): Promise<${P}Doc> {
  const now = new Date().toISOString();
  const doc: ${P}Doc = {
    id: \`${name.slice(0, 3)}_\${randomUUID()}\`,
    productId: PRODUCT_ID,
    userId,
    ...input,
    createdAt: now,
    updatedAt: now,
  };
  await getContainer().items.create(doc);
  return doc;
}

export async function update${P}(id: string, userId: string, updates: Update${P}Input): Promise<${P}Doc | null> {
  const existing = await get${P}(id, userId);
  if (!existing) return null;

  const updated: ${P}Doc = {
    ...existing,
    ...updates,
    updatedAt: new Date().toISOString(),
  };
  await getContainer().item(id, userId).replace(updated);
  return updated;
}

export async function delete${P}(id: string, userId: string): Promise<boolean> {
  try {
    await getContainer().item(id, userId).delete();
    return true;
  } catch {
    return false;
  }
}
`;
}

function genDirectListRoute(
  name: string,
  fields: ParsedField[],
  methods: string[],
  withHandler: boolean
): string {
  const P = pascal(name);
  const hasGet = methods.includes('GET');
  const hasPost = methods.includes('POST');
  const handlerImport = withHandler
    ? `import { withErrorHandler } from '@/lib/api-handler';\n`
    : '';
  const wrap = (code: string) => (withHandler ? `withErrorHandler(${code})` : code);

  const imports: string[] = [];
  if (hasGet) imports.push(`list${P}`);
  if (hasPost) imports.push(`create${P}`);

  let out = `import { NextRequest, NextResponse } from 'next/server';
import { getCurrentUser } from '@/lib/auth-server';
${handlerImport}`;

  if (imports.length > 0) {
    out += `import { ${imports.join(', ')} } from '@/lib/repositories/${name}';\n`;
  }

  if (hasPost) {
    out += `import { Create${P}Schema } from '@/lib/schemas/${name}';\n`;
  }

  if (hasGet) {
    out += `
export const GET = ${wrap(`async (req: NextRequest) => {
  const user = await getCurrentUser(req.headers.get('authorization'));
  if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });

  const url = new URL(req.url);
  const limit = parseInt(url.searchParams.get('limit') ?? '50');
  const offset = parseInt(url.searchParams.get('offset') ?? '0');

  const items = await list${P}(user.id, limit, offset);
  return NextResponse.json({ items });
}`)};
`;
  }

  if (hasPost) {
    out += `
export const POST = ${wrap(`async (req: NextRequest) => {
  const user = await getCurrentUser(req.headers.get('authorization'));
  if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });

  const body = await req.json();
  const input = Create${P}Schema.parse(body);
  const item = await create${P}(user.id, input);
  return NextResponse.json(item, { status: 201 });
}`)};
`;
  }

  return out;
}

function genDirectDetailRoute(
  name: string,
  _fields: ParsedField[],
  methods: string[],
  withHandler: boolean
): string {
  const P = pascal(name);
  const hasGet = methods.includes('GET');
  const hasPatch = methods.includes('PATCH');
  const hasDelete = methods.includes('DELETE');
  const handlerImport = withHandler
    ? `import { withErrorHandler } from '@/lib/api-handler';\n`
    : '';
  const wrap = (code: string) => (withHandler ? `withErrorHandler(${code})` : code);

  const imports: string[] = [];
  if (hasGet) imports.push(`get${P}`);
  if (hasPatch) imports.push(`update${P}`);
  if (hasDelete) imports.push(`delete${P}`);

  let out = `import { NextRequest, NextResponse } from 'next/server';
import { getCurrentUser } from '@/lib/auth-server';
${handlerImport}`;

  if (imports.length > 0) {
    out += `import { ${imports.join(', ')} } from '@/lib/repositories/${name}';\n`;
  }

  if (hasPatch) {
    out += `import { Update${P}Schema } from '@/lib/schemas/${name}';\n`;
  }

  out += `
type RouteContext = { params: Promise<{ id: string }> };
`;

  if (hasGet) {
    out += `
export const GET = ${wrap(`async (
  req: NextRequest,
  { params }: RouteContext,
) => {
  const user = await getCurrentUser(req.headers.get('authorization'));
  if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });

  const { id } = await params;
  const item = await get${P}(id, user.id);
  if (!item) return NextResponse.json({ error: 'Not found' }, { status: 404 });

  return NextResponse.json(item);
}`)};
`;
  }

  if (hasPatch) {
    out += `
export const PATCH = ${wrap(`async (
  req: NextRequest,
  { params }: RouteContext,
) => {
  const user = await getCurrentUser(req.headers.get('authorization'));
  if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });

  const { id } = await params;
  const body = await req.json();
  const updates = Update${P}Schema.parse(body);
  const item = await update${P}(id, user.id, updates);
  if (!item) return NextResponse.json({ error: 'Not found' }, { status: 404 });

  return NextResponse.json(item);
}`)};
`;
  }

  if (hasDelete) {
    out += `
export const DELETE = ${wrap(`async (
  req: NextRequest,
  { params }: RouteContext,
) => {
  const user = await getCurrentUser(req.headers.get('authorization'));
  if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });

  const { id } = await params;
  const deleted = await delete${P}(id, user.id);
  if (!deleted) return NextResponse.json({ error: 'Not found' }, { status: 404 });

  return NextResponse.json({ success: true });
}`)};
`;
  }

  return out;
}

// ── Test Template ────────────────────────────────────────────────────────────

function genSchemaTest(name: string, fields: ParsedField[]): string {
  const P = pascal(name);
  const requiredFields = fields.filter(f => !f.optional);

  function sampleValue(f: ParsedField): string {
    if (f.enumValues) return `'${f.enumValues[0]}'`;
    if (f.tsType === 'string') return `'test'`;
    if (f.tsType === 'number') return '42';
    if (f.tsType === 'boolean') return 'true';
    return `'2026-01-01T00:00:00.000Z'`;
  }

  const validPayload = requiredFields.map(f => `      ${f.name}: ${sampleValue(f)},`).join('\n');

  return `import { describe, it, expect } from 'vitest';
import { Create${P}Schema, Update${P}Schema } from './schemas/${name}';

describe('Create${P}Schema', () => {
  it('accepts valid input', () => {
    const result = Create${P}Schema.parse({
${validPayload}
    });
    expect(result).toBeDefined();
  });

  it('rejects empty object', () => {
    expect(() => Create${P}Schema.parse({})).toThrow();
  });
});

describe('Update${P}Schema', () => {
  it('accepts empty object (all optional)', () => {
    const result = Update${P}Schema.parse({});
    expect(result).toEqual({});
  });

  it('accepts partial update', () => {
    const result = Update${P}Schema.parse({ ${requiredFields[0]?.name ?? 'id'}: ${sampleValue(requiredFields[0] ?? fields[0])} });
    expect(result).toBeDefined();
  });
});
`;
}

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

async function main(): Promise<void> {
  const { name, fields, target, mode, methods, withHandler, dryRun } = parseArgs();

  console.log(`\n🚀 Generating API routes: ${name}`);
  console.log(`   Mode:      ${mode}`);
  console.log(`   Methods:   ${methods.join(', ')}`);
  console.log(`   Target:    ${target}`);
  if (fields) console.log(`   Fields:    ${fields}`);
  if (dryRun) console.log('   ⚠️  DRY RUN — no files will be written\n');

  const parsedFields = parseFields(fields);

  // Determine which files to generate
  const files: { path: string; content: string }[] = [];

  if (mode === 'proxy') {
    files.push({
      path: `app/api/${name}/route.ts`,
      content: genProxyListRoute(name, methods, withHandler),
    });
    if (methods.some(m => ['GET', 'PATCH', 'DELETE'].includes(m))) {
      files.push({
        path: `app/api/${name}/[id]/route.ts`,
        content: genProxyDetailRoute(name, methods, withHandler),
      });
    }
  } else {
    // Direct mode — also generate schema + repository
    if (parsedFields.length === 0) {
      console.error('Error: --fields is required in direct mode');
      process.exit(1);
    }

    files.push({
      path: `lib/schemas/${name}.ts`,
      content: genSchemaFile(name, parsedFields),
    });
    files.push({
      path: `lib/repositories/${name}.ts`,
      content: genRepositoryFile(name, parsedFields),
    });
    files.push({
      path: `app/api/${name}/route.ts`,
      content: genDirectListRoute(name, parsedFields, methods, withHandler),
    });
    if (methods.some(m => ['GET', 'PATCH', 'DELETE'].includes(m))) {
      files.push({
        path: `app/api/${name}/[id]/route.ts`,
        content: genDirectDetailRoute(name, parsedFields, methods, withHandler),
      });
    }
    files.push({
      path: `lib/__tests__/${name}.test.ts`,
      content: genSchemaTest(name, parsedFields),
    });
  }

  if (dryRun) {
    console.log('📄 Generated files:\n');
    for (const file of files) {
      console.log(`── ${file.path} ──────────────────────────────────────`);
      console.log(file.content);
    }
    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(target, file.path);
    await fs.mkdir(path.dirname(fullPath), { recursive: true });

    // Check if file already exists
    try {
      await fs.access(fullPath);
      console.log(`   ⚠️  SKIP ${file.path} (already exists)`);
      continue;
    } catch {
      // Good — doesn't exist
    }

    await fs.writeFile(fullPath, file.content, 'utf-8');
    console.log(`   ✅ ${file.path}`);
  }

  console.log(`\n✨ API routes generated for "${name}".`);
  if (mode === 'direct') {
    console.log(`\nPrerequisites (if not already present):`);
    console.log(`   - lib/auth-server.ts — getCurrentUser(authHeader) function`);
    console.log(`   - lib/api-handler.ts — withErrorHandler HOF`);
    console.log(`   - lib/datastore.ts   — getCosmosContainer + PRODUCT_ID`);
    console.log(`   - zod installed      — npm install zod`);
  } else {
    console.log(`\nPrerequisites (if not already present):`);
    console.log(`   - lib/api-helpers.ts — getAccessToken(req) function`);
    console.log(`   - lib/api-handler.ts — withErrorHandler HOF`);
    console.log(`   - NEXT_PUBLIC_BACKEND_URL env var (or defaults to localhost:4000)`);
  }
}

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