/**
 * React Components Generator — Pattern-Aware
 *
 * Generates React components following app-demo's design patterns:
 * - Field classification (business, lifecycle, relationship, metadata)
 * - Relationship resolution (FK → display name)
 * - Lifecycle badges with status colors
 * - Smart field ordering and grouping
 *
 * Components import shared utilities from src/lib/ (generated by shared-utils-generator).
 */

import type { TemplateContext } from '@specverse/types';

// ============================================================================
// Entry point
// ============================================================================

export default function generateReactComponent(context: TemplateContext): string {
  const { view, model, spec } = context;
  if (!view) throw new Error('View is required in template context');

  const componentName = view.name || `${model?.name || 'Unknown'}View`;
  let modelName: string;
  if (model?.name) modelName = model.name;
  else if (Array.isArray(view.model)) modelName = view.model[0];
  else if (view.model) modelName = view.model;
  else modelName = view.modelReference || 'Unknown';

  const viewType = view.type || 'list';
  const lower = modelName.charAt(0).toLowerCase() + modelName.slice(1);
  const plural = `${lower}s`;
  const api = `/api/${plural}`;
  const attrs = getModelAttributes(model);
  const belongsTo = getBelongsToRelationships(model);
  const hasMany = getHasManyRelationships(model);
  const lifecycle = getLifecycle(model);
  const classified = classifyAttrs(attrs, lifecycle);

  let body: string;
  switch (viewType) {
    case 'list':
      body = generateListView(componentName, modelName, lower, plural, api, classified, belongsTo, lifecycle, view);
      break;
    case 'detail':
      body = generateDetailView(componentName, modelName, lower, plural, api, classified, belongsTo, hasMany, lifecycle, view);
      break;
    case 'form':
      body = generateFormView(componentName, modelName, lower, plural, api, classified, belongsTo, lifecycle, view);
      break;
    case 'dashboard':
      body = generateDashboardView(componentName, modelName, lower, plural, api, classified, view, model);
      break;
    case 'board':
    case 'workflow':
      body = generateBoardView(componentName, modelName, lower, plural, api, lifecycle, view);
      break;
    case 'timeline':
      body = generateTimelineView(componentName, modelName, lower, plural, api, view);
      break;
    case 'calendar':
      body = generateCalendarView(componentName, modelName, lower, plural, api, view, model);
      break;
    case 'analytics':
      body = generateAnalyticsView(componentName, modelName, lower, plural, api, classified, lifecycle, view, model);
      break;
    default:
      body = generateListView(componentName, modelName, lower, plural, api, classified, belongsTo, lifecycle, view);
  }
  return stripUnusedImports(body);
}

/**
 * Rewrite the top-of-file named imports to drop symbols the body doesn't
 * reference. The view generators import every helper unconditionally; this
 * pass keeps them clean under tsc's `noUnusedLocals`.
 */
function stripUnusedImports(source: string): string {
  // Process only the leading contiguous run of named imports.
  const lines = source.split('\n');
  const out: string[] = [];
  let i = 0;
  // Find the end of the import block.
  while (i < lines.length) {
    const line = lines[i];
    const match = line.match(/^import\s+\{\s*([^}]+?)\s*\}\s+from\s+(['"][^'"]+['"]);?\s*$/);
    if (!match) break;
    const names = match[1].split(',').map(s => s.trim()).filter(Boolean);
    const from = match[2];
    // Join all following lines as the usage body so we can scan for references.
    const rest = lines.slice(i + 1).join('\n');
    const used = names.filter(n => new RegExp(`\\b${n.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(rest));
    if (used.length === 0) {
      // drop the import entirely
    } else {
      out.push(`import { ${used.join(', ')} } from ${from};`);
    }
    i++;
  }
  return [...out, ...lines.slice(i)].join('\n');
}

// ============================================================================
// Model introspection helpers
// ============================================================================

interface Attr { name: string; type: string; required?: boolean; auto?: string; values?: string[] }
interface Rel { name: string; target: string; type: string; cascade?: boolean }
interface Lifecycle { name: string; states: string[]; statusField: string }
interface ClassifiedAttrs { business: Attr[]; lifecycle: Attr[]; metadata: Attr[]; all: Attr[] }

function getModelAttributes(model: any): Attr[] {
  if (!model?.attributes) return [];
  const attrs = Array.isArray(model.attributes) ? model.attributes
    : Object.entries(model.attributes).map(([name, def]: [string, any]) => {
        if (typeof def === 'string') {
          const parts = def.split(' ');
          return { name, type: parts[0], required: parts.includes('required'), auto: parts.find((p: string) => p.startsWith('auto='))?.split('=')[1], values: extractValues(def) };
        }
        return { name, ...def };
      });
  return attrs;
}

function extractValues(def: string): string[] | undefined {
  const match = def.match(/values=\[([^\]]+)\]/);
  if (match) return match[1].split(',').map((s: string) => s.trim());
  return undefined;
}

function getBelongsToRelationships(model: any): Rel[] {
  if (!model?.relationships) return [];
  const rels = Array.isArray(model.relationships) ? model.relationships
    : Object.entries(model.relationships).map(([name, def]: [string, any]) => {
        if (typeof def === 'string') {
          const parts = def.split(' ');
          return { name, type: parts[0], target: parts[1], cascade: parts.includes('cascade') };
        }
        return { name, ...def };
      });
  return rels.filter((r: any) => r.type === 'belongsTo');
}

function getHasManyRelationships(model: any): Rel[] {
  if (!model?.relationships) return [];
  const rels = Array.isArray(model.relationships) ? model.relationships
    : Object.entries(model.relationships).map(([name, def]: [string, any]) => {
        if (typeof def === 'string') {
          const parts = def.split(' ');
          return { name, type: parts[0], target: parts[1] };
        }
        return { name, ...def };
      });
  return rels.filter((r: any) => r.type === 'hasMany');
}

function getLifecycle(model: any): Lifecycle | null {
  if (!model?.lifecycles) return null;
  const entries = Array.isArray(model.lifecycles) ? model.lifecycles
    : Object.entries(model.lifecycles).map(([name, lc]: [string, any]) => ({ name, ...lc }));
  if (entries.length === 0) return null;
  const lc = entries[0];
  const states = lc.states || lc.flow?.split(/\s*->\s*/) || [];
  return { name: lc.name || 'status', states, statusField: lc.name || 'status' };
}

const METADATA_FIELDS = new Set(['id', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy', 'deletedAt', 'version']);

function classifyAttrs(attrs: Attr[], lifecycle: Lifecycle | null): ClassifiedAttrs {
  const business: Attr[] = [];
  const lifecycleAttrs: Attr[] = [];
  const metadata: Attr[] = [];
  for (const a of attrs) {
    if (a.auto || METADATA_FIELDS.has(a.name)) metadata.push(a);
    else if (lifecycle && a.name === lifecycle.statusField) lifecycleAttrs.push(a);
    else if (['status', 'state', 'phase'].includes(a.name)) lifecycleAttrs.push(a);
    else business.push(a);
  }
  return { business, lifecycle: lifecycleAttrs, metadata, all: attrs };
}

function pluralize(s: string): string {
  if (s.endsWith('s')) return s;
  if (s.endsWith('y')) return s.slice(0, -1) + 'ies';
  return s + 's';
}

// ============================================================================
// LIST VIEW
// ============================================================================

function generateListView(name: string, model: string, lower: string, plural: string, api: string, classified: ClassifiedAttrs, belongsTo: Rel[], lifecycle: Lifecycle | null, view: any): string {
  const displayCols = classified.business.slice(0, 5);
  const statusField = lifecycle?.statusField || classified.lifecycle[0]?.name;
  const apiPath = api.replace(/^\/api/, ''); // strip /api prefix for apiRequest

  // Build relationship lookup fetches
  const relFetches = belongsTo.map(r => {
    const tLower = r.target.charAt(0).toLowerCase() + r.target.slice(1);
    return `  const [${tLower}Map, set${r.target}Map] = useState<Record<string, any>>({});`;
  }).join('\n');

  const relEffects = belongsTo.map(r => {
    const tLower = r.target.charAt(0).toLowerCase() + r.target.slice(1);
    return `    apiRequest('GET', '/${tLower}s').then(data => {
      if (Array.isArray(data)) {
        const m: Record<string, any> = {};
        data.forEach(e => { m[e.id] = e; });
        set${r.target}Map(m);
      }
    }).catch(() => {});`;
  }).join('\n');

  const relColumns = belongsTo.map(r => {
    const tLower = r.target.charAt(0).toLowerCase() + r.target.slice(1);
    const fk = `${r.name}_id`;
    return { header: r.target, cell: `{${tLower}Map[item.${fk}] ? getEntityDisplayName(${tLower}Map[item.${fk}]) : (item.${fk} ? item.${fk}.slice(0,8)+'...' : '—')}` };
  });

  return `import { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { apiRequest } from '../lib/apiClient';
import { getEntityDisplayName } from '../lib/field-helpers';
import { formatValue, formatDate, StatusBadge } from '../lib/view-helpers';

function ${name}() {
  const [items, setItems] = useState<any[]>([]);
  const [loading, setLoading] = useState(true);
${relFetches}

  useEffect(() => {
    apiRequest('GET', '${apiPath}').then(data => {
      setItems(Array.isArray(data) ? data : []);
      setLoading(false);
    }).catch(() => setLoading(false));
${relEffects}
  }, []);

  if (loading) return <div className="flex items-center justify-center h-64 text-gray-400">Loading...</div>;

  return (
    <div className="p-6">
      <div className="flex justify-between items-center mb-6">
        <div>
          <h1 className="text-2xl font-bold text-gray-900">${pluralize(model)}</h1>
          <p className="text-sm text-gray-500 mt-1">{items.length} ${lower}{items.length !== 1 ? 's' : ''}</p>
        </div>
        <Link to="/${lower}form" className="px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 transition-colors">
          + New ${model}
        </Link>
      </div>

      {items.length === 0 ? (
        <div className="text-center py-16 bg-white rounded-lg border border-dashed border-gray-300">
          <p className="text-gray-500">No ${plural} yet</p>
          <Link to="/${lower}form" className="mt-3 inline-block text-blue-600 hover:text-blue-700 text-sm font-medium">Create your first ${lower} →</Link>
        </div>
      ) : (
        <div className="bg-white rounded-lg border border-gray-200 shadow-sm overflow-hidden">
          <table className="min-w-full divide-y divide-gray-200">
            <thead className="bg-gray-50 sticky top-0">
              <tr>
${displayCols.map(c => `                <th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">${c.name}</th>`).join('\n')}
${relColumns.map(r => `                <th className="px-4 py-3 text-left text-xs font-medium text-blue-600 uppercase tracking-wider">${r.header}</th>`).join('\n')}
${statusField ? `                <th className="px-4 py-3 text-left text-xs font-medium text-purple-600 uppercase tracking-wider">Status</th>` : ''}
                <th className="px-4 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider italic">Created</th>
                <th className="px-4 py-3"></th>
              </tr>
            </thead>
            <tbody className="bg-white divide-y divide-gray-200">
              {items.map((item) => (
                <tr key={item.id} className="hover:bg-gray-50 cursor-pointer transition-colors" onClick={() => window.location.href = \`/${lower}detail?id=\${item.id}\`}>
${displayCols.map(c => `                  <td className="px-4 py-3 text-sm text-gray-900">{formatValue(item.${c.name}, '${c.type}')}</td>`).join('\n')}
${relColumns.map(r => `                  <td className="px-4 py-3 text-sm text-blue-700">${r.cell}</td>`).join('\n')}
${statusField ? `                  <td className="px-4 py-3"><StatusBadge status={item.${statusField}} variant="lifecycle" /></td>` : ''}
                  <td className="px-4 py-3 text-xs text-gray-400 font-mono">{formatDate(item.createdAt)}</td>
                  <td className="px-4 py-3 text-right">
                    <Link to={\`/${lower}form?id=\${item.id}\`} className="text-blue-600 hover:text-blue-800 text-sm font-medium" onClick={e => e.stopPropagation()}>Edit</Link>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}
    </div>
  );
}

export default ${name};
`;
}

// ============================================================================
// DETAIL VIEW
// ============================================================================

function generateDetailView(name: string, model: string, lower: string, plural: string, api: string, classified: ClassifiedAttrs, belongsTo: Rel[], hasMany: Rel[], lifecycle: Lifecycle | null, view: any): string {
  const statusField = lifecycle?.statusField || classified.lifecycle[0]?.name;
  const apiPath = api.replace(/^\/api/, '');

  const relFetches = belongsTo.map(r => {
    const tLower = r.target.charAt(0).toLowerCase() + r.target.slice(1);
    return `  const [${tLower}Ref, set${r.target}Ref] = useState<any>(null);`;
  }).join('\n');

  const relEffects = belongsTo.map(r => {
    const tLower = r.target.charAt(0).toLowerCase() + r.target.slice(1);
    const fk = `${r.name}_id`;
    return `      if (data.${fk}) apiRequest('GET', \`/${tLower}s/\${data.${fk}}\`).then(d => set${r.target}Ref(d)).catch(() => {});`;
  }).join('\n');

  const hasManyFetches = hasMany.map(r => {
    const tLower = r.target.charAt(0).toLowerCase() + r.target.slice(1);
    return `  const [${r.name}Items, set${capitalize(r.name)}Items] = useState<any[]>([]);`;
  }).join('\n');

  const hasManyEffects = hasMany.map(r => {
    const tLower = r.target.charAt(0).toLowerCase() + r.target.slice(1);
    return `      apiRequest('GET', \`/${tLower}s?${lower}Id=\${data.id}\`).then(d => set${capitalize(r.name)}Items(Array.isArray(d) ? d : [])).catch(() => {});`;
  }).join('\n');

  return `import { useState, useEffect } from 'react';
import { Link, useSearchParams, useNavigate } from 'react-router-dom';
import { apiRequest } from '../lib/apiClient';
import { getEntityDisplayName } from '../lib/field-helpers';
import { formatValue, formatDate, StatusBadge } from '../lib/view-helpers';

function ${name}() {
  const [searchParams] = useSearchParams();
  const navigate = useNavigate();
  const id = searchParams.get('id');
  const [item, setItem] = useState<any>(null);
  const [loading, setLoading] = useState(true);
${relFetches}
${hasManyFetches}

  useEffect(() => {
    if (!id) { setLoading(false); return; }
    apiRequest('GET', \`${apiPath}/\${id}\`).then(data => {
      setItem(data);
      setLoading(false);
      if (data) {
${relEffects}
${hasManyEffects}
      }
    }).catch(() => setLoading(false));
  }, [id]);

  if (loading) return <div className="flex items-center justify-center h-64 text-gray-400">Loading...</div>;
  if (!item) return <div className="p-6 text-gray-500">Not found</div>;

  const handleDelete = async () => {
    if (!confirm('Delete this ${lower}?')) return;
    await apiRequest('DELETE', \`${apiPath}/\${id}\`);
    navigate('/${lower}list');
  };

  return (
    <div className="p-6 max-w-3xl">
      {/* Header */}
      <div className="flex items-start justify-between mb-6">
        <div>
          <h1 className="text-2xl font-bold text-gray-900">{getEntityDisplayName(item)}</h1>
${statusField ? `          <div className="mt-2"><StatusBadge status={item.${statusField}} /></div>` : ''}
        </div>
        <div className="flex gap-2">
          <Link to={\`/${lower}form?id=\${id}\`} className="px-3 py-1.5 text-sm font-medium text-blue-600 border border-blue-200 rounded-lg hover:bg-blue-50">Edit</Link>
          <button onClick={handleDelete} className="px-3 py-1.5 text-sm font-medium text-red-600 border border-red-200 rounded-lg hover:bg-red-50">Delete</button>
        </div>
      </div>

      {/* Business Fields */}
      <div className="bg-white rounded-lg border border-gray-200 shadow-sm mb-6">
        <div className="divide-y divide-gray-100">
${classified.business.map(a => `          <div className="px-5 py-3 flex justify-between">
            <span className="text-sm text-gray-500">${a.name.charAt(0).toUpperCase() + a.name.slice(1)}</span>
            <span className="text-sm text-gray-900">{formatValue(item.${a.name}, '${a.type}')}</span>
          </div>`).join('\n')}
        </div>
      </div>

${belongsTo.length > 0 ? `      {/* Relationships */}
      <div className="bg-white rounded-lg border border-gray-200 shadow-sm mb-6">
        <div className="px-5 py-3 border-b border-gray-100">
          <h3 className="text-sm font-medium text-gray-700">Related</h3>
        </div>
        <div className="divide-y divide-gray-100">
${belongsTo.map(r => {
  const tLower = r.target.charAt(0).toLowerCase() + r.target.slice(1);
  return `          <div className="px-5 py-3 flex justify-between items-center">
            <span className="text-sm text-gray-500">${r.target}</span>
            {${tLower}Ref ? (
              <Link to={\`/${tLower}detail?id=\${${tLower}Ref.id}\`} className="text-sm text-blue-600 hover:text-blue-800">{getEntityDisplayName(${tLower}Ref)}</Link>
            ) : <span className="text-sm text-gray-400">—</span>}
          </div>`;
}).join('\n')}
        </div>
      </div>` : ''}

${hasMany.map(r => {
  const tLower = r.target.charAt(0).toLowerCase() + r.target.slice(1);
  return `      {/* ${r.target} */}
      <div className="bg-white rounded-lg border border-gray-200 shadow-sm mb-6">
        <div className="px-5 py-3 border-b border-gray-100 flex justify-between items-center">
          <h3 className="text-sm font-medium text-gray-700">${pluralize(r.target)} ({${r.name}Items.length})</h3>
          <Link to={\`/${tLower}form?${lower}Id=\${id}\`} className="text-xs text-blue-600 hover:text-blue-800">+ Add</Link>
        </div>
        <ul className="divide-y divide-gray-100">
          {${r.name}Items.slice(0, 10).map((child: any, i: number) => (
            <li key={i} className="px-5 py-2.5 flex justify-between items-center hover:bg-gray-50">
              <Link to={\`/${tLower}detail?id=\${child.id}\`} className="text-sm text-gray-900 hover:text-blue-600">{getEntityDisplayName(child)}</Link>
              {child.status && <StatusBadge status={child.status} />}
            </li>
          ))}
          {${r.name}Items.length === 0 && <li className="px-5 py-3 text-sm text-gray-400">None yet</li>}
        </ul>
      </div>`;
}).join('\n')}

      {/* Metadata */}
      <div className="text-xs text-gray-400 space-y-1">
        <div>ID: {item.id}</div>
        {item.createdAt && <div>Created: {formatDate(item.createdAt)}</div>}
        {item.updatedAt && <div>Updated: {formatDate(item.updatedAt)}</div>}
      </div>
    </div>
  );
}

export default ${name};
`;
}

// ============================================================================
// FORM VIEW
// ============================================================================

function generateFormView(name: string, model: string, lower: string, plural: string, api: string, classified: ClassifiedAttrs, belongsTo: Rel[], lifecycle: Lifecycle | null, view: any): string {
  // Only show editable fields (business + lifecycle, not metadata/auto)
  const editableFields = [...classified.business, ...classified.lifecycle];
  const statusField = lifecycle?.statusField;
  const apiPath = api.replace(/^\/api/, '');

  const relStates = belongsTo.map(r => {
    return `  const [${r.target.charAt(0).toLowerCase() + r.target.slice(1)}Options, set${r.target}Options] = useState<any[]>([]);`;
  }).join('\n');

  const relEffects = belongsTo.map(r => {
    const tLower = r.target.charAt(0).toLowerCase() + r.target.slice(1);
    return `    apiRequest('GET', '/${tLower}s').then(d => set${r.target}Options(Array.isArray(d) ? d : [])).catch(() => {});`;
  }).join('\n');

  function inputForField(attr: Attr): string {
    const { name: n, type, required, values } = attr;
    const req = required ? ' required' : '';

    // Enum dropdown
    if (values && values.length > 0) {
      return `        <select name="${n}" value={form.${n} || ''} onChange={handleChange} className="w-full px-3 py-2 border border-gray-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"${req}>
          <option value="">Select...</option>
${values.map(v => `          <option value="${v}">${v}</option>`).join('\n')}
        </select>`;
    }

    // Lifecycle status dropdown
    if (lifecycle && n === statusField) {
      return `        <select name="${n}" value={form.${n} || ''} onChange={handleChange} className="w-full px-3 py-2 border border-gray-300 border-l-4 border-l-purple-600 rounded text-sm focus:outline-none focus:ring-2 focus:ring-purple-500"${req}>
          <option value="">Select status...</option>
${lifecycle.states.map(s => `          <option value="${s}">${s.replace(/[_-]/g, ' ')}</option>`).join('\n')}
        </select>`;
    }

    const t = type.toLowerCase();
    if (t === 'boolean') return `        <input type="checkbox" name="${n}" checked={!!form.${n}} onChange={e => setForm((f: any) => ({...f, ${n}: e.target.checked}))} className="h-4 w-4 text-blue-600 rounded" />`;
    if (t.includes('date') || t.includes('timestamp')) return `        <input type="datetime-local" name="${n}" value={form.${n} || ''} onChange={handleChange} className="w-full px-3 py-2 border border-gray-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"${req} />`;
    if (t === 'integer' || t === 'number' || t === 'money' || t === 'decimal' || t === 'float') return `        <input type="number" name="${n}" value={form.${n} || ''} onChange={handleChange} className="w-full px-3 py-2 border border-gray-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"${req} />`;
    if (n.toLowerCase().includes('description') || n.toLowerCase().includes('content') || n.toLowerCase().includes('body')) return `        <textarea name="${n}" value={form.${n} || ''} onChange={handleChange} rows={4} className="w-full px-3 py-2 border border-gray-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"${req} />`;
    if (t === 'email') return `        <input type="email" name="${n}" value={form.${n} || ''} onChange={handleChange} className="w-full px-3 py-2 border border-gray-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"${req} />`;
    return `        <input type="text" name="${n}" value={form.${n} || ''} onChange={handleChange} className="w-full px-3 py-2 border border-gray-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"${req} />`;
  }

  return `import { useState, useEffect } from 'react';
import { useSearchParams, useNavigate } from 'react-router-dom';
import { apiRequest } from '../lib/apiClient';
import { getEntityDisplayName } from '../lib/field-helpers';

function ${name}() {
  const [searchParams] = useSearchParams();
  const navigate = useNavigate();
  const editId = searchParams.get('id');
  const [form, setForm] = useState<any>({});
  const [error, setError] = useState('');
  const [saving, setSaving] = useState(false);
${relStates}

  useEffect(() => {
    if (editId) {
      apiRequest('GET', \`${apiPath}/\${editId}\`).then(data => setForm(data || {})).catch(() => {});
    }
${relEffects}
  }, [editId]);

  const handleChange = (e: any) => {
    const { name, value, type } = e.target;
    setForm((f: any) => ({ ...f, [name]: type === 'number' ? Number(value) : value }));
  };

  const handleSubmit = async (e: any) => {
    e.preventDefault();
    setSaving(true);
    setError('');
    try {
      if (editId) {
        await apiRequest('PUT', \`${apiPath}/\${editId}\`, form);
      } else {
        await apiRequest('POST', '${apiPath}', form);
      }
      // apiRequest throws on non-2xx so reaching here means success.
      navigate('/${lower}list');
    } catch (err: any) {
      setError(err.message);
    } finally {
      setSaving(false);
    }
  };

  return (
    <div className="p-6 max-w-2xl">
      <h1 className="text-2xl font-bold text-gray-900 mb-6">{editId ? 'Edit' : 'New'} ${model}</h1>

      {error && (
        <div className="mb-4 p-3 bg-red-50 border border-red-200 rounded-lg text-sm text-red-700">{error}</div>
      )}

      <form onSubmit={handleSubmit} className="bg-white rounded-lg border border-gray-200 shadow-sm">
        <div className="p-6 space-y-5">
${editableFields.map(a => `          {/* ${a.name} */}
          <div>
            <label className="block text-sm font-medium text-gray-700 mb-1">
              ${a.name.charAt(0).toUpperCase() + a.name.slice(1)}${a.required ? ' *' : ''}
            </label>
${inputForField(a)}
          </div>`).join('\n\n')}

${belongsTo.map(r => {
  const tLower = r.target.charAt(0).toLowerCase() + r.target.slice(1);
  const fk = `${r.name}_id`;
  return `          {/* ${r.target} (relationship) */}
          <div>
            <label className="block text-sm font-medium text-gray-700 mb-1">${r.target}</label>
            <select name="${fk}" value={form.${fk} || ''} onChange={handleChange} className="w-full px-3 py-2 border border-gray-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
              <option value="">Select ${r.target}...</option>
              {${tLower}Options.map((opt: any) => (
                <option key={opt.id} value={opt.id}>{getEntityDisplayName(opt)}</option>
              ))}
            </select>
          </div>`;
}).join('\n\n')}
        </div>

        <div className="px-6 py-4 bg-gray-50 border-t border-gray-200 rounded-b-lg flex justify-end gap-3">
          <button type="button" onClick={() => navigate(-1)} className="px-4 py-2 text-sm font-medium text-gray-700 border border-gray-300 rounded-lg hover:bg-gray-100">
            Cancel
          </button>
          <button type="submit" disabled={saving} className="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 disabled:opacity-50">
            {saving ? 'Saving...' : (editId ? 'Update' : 'Create')}
          </button>
        </div>
      </form>
    </div>
  );
}

export default ${name};
`;
}

// ============================================================================
// DASHBOARD VIEW
// ============================================================================

function generateDashboardView(name: string, model: string, lower: string, plural: string, api: string, classified: ClassifiedAttrs, view: any, modelDef: any): string {
  const numericAttrs = classified.business.filter(a => ['integer', 'number', 'money', 'decimal', 'float'].includes(a.type.toLowerCase()));
  const metricNames = numericAttrs.map(a => a.name).slice(0, 4);
  const apiPath = api.replace(/^\/api/, '');

  return `import { useState, useEffect } from 'react';
import { apiRequest } from '../lib/apiClient';
import { getEntityDisplayName } from '../lib/field-helpers';
import { formatDate, StatusBadge } from '../lib/view-helpers';

function ${name}() {
  const [items, setItems] = useState<any[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    apiRequest('GET', '${apiPath}').then(data => {
      setItems(Array.isArray(data) ? data : []);
      setLoading(false);
    }).catch(() => setLoading(false));
  }, []);

  if (loading) return <div className="flex items-center justify-center h-64 text-gray-400">Loading...</div>;

  const total = items.length;
${metricNames.map(m => `  const ${m}Total = items.reduce((sum, item) => sum + (Number(item.${m}) || 0), 0);`).join('\n')}

  return (
    <div className="p-6 space-y-6">
      <h1 className="text-2xl font-bold text-gray-900">${model} Dashboard</h1>

      <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
        <div className="bg-white rounded-lg border border-gray-200 shadow-sm p-5">
          <p className="text-sm text-gray-500">Total</p>
          <p className="text-3xl font-bold text-blue-600">{total}</p>
        </div>
${metricNames.map(m => `        <div className="bg-white rounded-lg border border-gray-200 shadow-sm p-5">
          <p className="text-sm text-gray-500">${m.charAt(0).toUpperCase() + m.slice(1)}</p>
          <p className="text-3xl font-bold text-blue-600">{${m}Total}</p>
        </div>`).join('\n')}
      </div>

      <div className="bg-white rounded-lg border border-gray-200 shadow-sm">
        <div className="px-5 py-4 border-b border-gray-200">
          <h2 className="text-lg font-medium text-gray-900">Recent</h2>
        </div>
        <ul className="divide-y divide-gray-100">
          {items.slice(0, 8).map((item, i) => (
            <li key={i} className="px-5 py-3 flex justify-between items-center">
              <span className="text-sm text-gray-900">{getEntityDisplayName(item)}</span>
              <div className="flex items-center gap-3">
                {item.status && <StatusBadge status={item.status} />}
                <span className="text-xs text-gray-400">{formatDate(item.createdAt)}</span>
              </div>
            </li>
          ))}
          {items.length === 0 && <li className="px-5 py-3 text-sm text-gray-400">No ${plural} yet</li>}
        </ul>
      </div>
    </div>
  );
}

export default ${name};
`;
}

// ============================================================================
// BOARD VIEW (lifecycle kanban)
// ============================================================================

function generateBoardView(name: string, model: string, lower: string, plural: string, api: string, lifecycle: Lifecycle | null, view: any): string {
  const states = lifecycle?.states || ['todo', 'in_progress', 'done'];
  const statusField = lifecycle?.statusField || 'status';
  const apiPath = api.replace(/^\/api/, '');

  return `import { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { apiRequest } from '../lib/apiClient';
import { getEntityDisplayName } from '../lib/field-helpers';
import { statusColor } from '../lib/view-helpers';

function ${name}() {
  const [items, setItems] = useState<any[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    apiRequest('GET', '${apiPath}').then(data => {
      setItems(Array.isArray(data) ? data : []);
      setLoading(false);
    }).catch(() => setLoading(false));
  }, []);

  if (loading) return <div className="flex items-center justify-center h-64 text-gray-400">Loading...</div>;

  const columns = ${JSON.stringify(states)};
  const grouped: Record<string, any[]> = {};
  columns.forEach(col => { grouped[col] = []; });
  items.forEach(item => {
    const col = item.${statusField} || columns[0];
    if (grouped[col]) grouped[col].push(item);
    else grouped[columns[columns.length - 1]]?.push(item);
  });

  return (
    <div className="p-6">
      <h1 className="text-2xl font-bold text-gray-900 mb-6">${model} Board</h1>
      <div className="flex gap-4 overflow-x-auto pb-4">
        {columns.map(col => {
          const colors = statusColor(col);
          return (
            <div key={col} className="flex-shrink-0 w-72 bg-gray-50 rounded-lg border border-gray-200">
              <div className="p-3 border-b border-gray-200 flex items-center justify-between">
                <div className="flex items-center gap-2">
                  <span className={\`w-2 h-2 rounded-full \${colors.dot}\`}></span>
                  <h3 className="text-sm font-semibold text-gray-700">{col.replace(/[_-]/g, ' ')}</h3>
                </div>
                <span className="text-xs text-gray-400 font-medium">{(grouped[col] || []).length}</span>
              </div>
              <div className="p-2 space-y-2 min-h-[200px]">
                {(grouped[col] || []).map((item, i) => (
                  <Link key={i} to={\`/${lower}detail?id=\${item.id}\`} className="block bg-white rounded-md border border-gray-200 p-3 hover:shadow-md transition-shadow">
                    <p className="text-sm font-medium text-gray-900">{getEntityDisplayName(item)}</p>
                    {item.description && <p className="text-xs text-gray-500 mt-1 line-clamp-2">{item.description}</p>}
                  </Link>
                ))}
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

export default ${name};
`;
}

// ============================================================================
// TIMELINE VIEW
// ============================================================================

function generateTimelineView(name: string, model: string, lower: string, plural: string, api: string, view: any): string {
  const apiPath = api.replace(/^\/api/, '');
  return `import { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { apiRequest } from '../lib/apiClient';
import { getEntityDisplayName } from '../lib/field-helpers';
import { formatDate, StatusBadge } from '../lib/view-helpers';

function ${name}() {
  const [items, setItems] = useState<any[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    apiRequest('GET', '${apiPath}').then(data => {
      const sorted = (Array.isArray(data) ? data : []).sort((a, b) =>
        new Date(b.createdAt || 0).getTime() - new Date(a.createdAt || 0).getTime()
      );
      setItems(sorted);
      setLoading(false);
    }).catch(() => setLoading(false));
  }, []);

  if (loading) return <div className="flex items-center justify-center h-64 text-gray-400">Loading...</div>;

  return (
    <div className="p-6 max-w-3xl">
      <h1 className="text-2xl font-bold text-gray-900 mb-6">${model} Timeline</h1>
      <div className="relative">
        <div className="absolute left-4 top-0 bottom-0 w-0.5 bg-gray-200"></div>
        <div className="space-y-4">
          {items.map((item, i) => (
            <div key={i} className="relative flex items-start ml-4 pl-6">
              <div className="absolute -left-1.5 mt-2 w-3 h-3 rounded-full bg-blue-500 border-2 border-white shadow"></div>
              <Link to={\`/${lower}detail?id=\${item.id}\`} className="bg-white rounded-lg border border-gray-200 shadow-sm p-4 flex-1 hover:shadow-md transition-shadow">
                <div className="flex justify-between items-start">
                  <h3 className="text-sm font-medium text-gray-900">{getEntityDisplayName(item)}</h3>
                  <time className="text-xs text-gray-400 ml-4">{formatDate(item.createdAt)}</time>
                </div>
                {item.description && <p className="text-sm text-gray-500 mt-1 line-clamp-2">{item.description}</p>}
                {item.status && <div className="mt-2"><StatusBadge status={item.status} /></div>}
              </Link>
            </div>
          ))}
          {items.length === 0 && <p className="ml-10 text-sm text-gray-400">No ${plural} yet</p>}
        </div>
      </div>
    </div>
  );
}

export default ${name};
`;
}

// ============================================================================
// CALENDAR VIEW
// ============================================================================

function generateCalendarView(name: string, model: string, lower: string, plural: string, api: string, view: any, modelDef: any): string {
  const attrs = getModelAttributes(modelDef);
  const dateField = attrs.find(a =>
    ['startdate', 'duedate', 'scheduledat', 'eventdate', 'date'].includes(a.name.toLowerCase())
  )?.name || 'createdAt';
  const apiPath = api.replace(/^\/api/, '');

  return `import { useState, useEffect } from 'react';
import { apiRequest } from '../lib/apiClient';
import { getEntityDisplayName } from '../lib/field-helpers';

function ${name}() {
  const [items, setItems] = useState<any[]>([]);
  const [loading, setLoading] = useState(true);
  const [currentMonth, setCurrentMonth] = useState(new Date());

  useEffect(() => {
    apiRequest('GET', '${apiPath}').then(data => {
      setItems(Array.isArray(data) ? data : []);
      setLoading(false);
    }).catch(() => setLoading(false));
  }, []);

  if (loading) return <div className="flex items-center justify-center h-64 text-gray-400">Loading...</div>;

  const year = currentMonth.getFullYear();
  const month = currentMonth.getMonth();
  const firstDay = new Date(year, month, 1).getDay();
  const daysInMonth = new Date(year, month + 1, 0).getDate();
  const days = Array.from({ length: daysInMonth }, (_, i) => i + 1);
  const blanks = Array.from({ length: firstDay }, (_, i) => i);

  const getItemsForDay = (day: number) => items.filter(item => {
    const d = new Date(item.${dateField} || item.createdAt);
    return d.getFullYear() === year && d.getMonth() === month && d.getDate() === day;
  });

  return (
    <div className="p-6">
      <div className="flex items-center justify-between mb-6">
        <h1 className="text-2xl font-bold text-gray-900">${model} Calendar</h1>
        <div className="flex items-center gap-3">
          <button onClick={() => setCurrentMonth(new Date(year, month - 1, 1))} className="p-2 hover:bg-gray-100 rounded-lg text-gray-600">&larr;</button>
          <span className="text-lg font-medium text-gray-900 w-48 text-center">{currentMonth.toLocaleString('default', { month: 'long', year: 'numeric' })}</span>
          <button onClick={() => setCurrentMonth(new Date(year, month + 1, 1))} className="p-2 hover:bg-gray-100 rounded-lg text-gray-600">&rarr;</button>
        </div>
      </div>
      <div className="grid grid-cols-7 gap-px bg-gray-200 rounded-lg overflow-hidden border border-gray-200">
        {['Sun','Mon','Tue','Wed','Thu','Fri','Sat'].map(d => (
          <div key={d} className="bg-gray-50 p-2 text-xs font-medium text-gray-500 text-center">{d}</div>
        ))}
        {blanks.map(i => <div key={\`b\${i}\`} className="bg-white p-2 min-h-[80px]"></div>)}
        {days.map(day => {
          const dayItems = getItemsForDay(day);
          const isToday = day === new Date().getDate() && month === new Date().getMonth() && year === new Date().getFullYear();
          return (
            <div key={day} className="bg-white p-2 min-h-[80px]">
              <span className={\`text-sm \${isToday ? 'bg-blue-600 text-white w-6 h-6 rounded-full inline-flex items-center justify-center' : dayItems.length > 0 ? 'font-bold text-blue-600' : 'text-gray-700'}\`}>{day}</span>
              {dayItems.slice(0, 2).map((item, i) => (
                <div key={i} className="mt-1 text-xs bg-blue-50 text-blue-700 rounded px-1.5 py-0.5 truncate">{getEntityDisplayName(item)}</div>
              ))}
              {dayItems.length > 2 && <div className="mt-1 text-xs text-gray-400">+{dayItems.length - 2} more</div>}
            </div>
          );
        })}
      </div>
    </div>
  );
}

export default ${name};
`;
}

// ============================================================================
// ANALYTICS VIEW
// ============================================================================

function generateAnalyticsView(name: string, model: string, lower: string, plural: string, api: string, classified: ClassifiedAttrs, lifecycle: Lifecycle | null, view: any, modelDef: any): string {
  const numericAttrs = classified.business.filter(a => ['integer', 'number', 'money', 'decimal', 'float'].includes(a.type.toLowerCase()));
  const statusField = lifecycle?.statusField || 'status';
  const apiPath = api.replace(/^\/api/, '');

  return `import { useState, useEffect } from 'react';
import { apiRequest } from '../lib/apiClient';

function ${name}() {
  const [items, setItems] = useState<any[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    apiRequest('GET', '${apiPath}').then(data => {
      setItems(Array.isArray(data) ? data : []);
      setLoading(false);
    }).catch(() => setLoading(false));
  }, []);

  if (loading) return <div className="flex items-center justify-center h-64 text-gray-400">Loading...</div>;

  const statusCounts: Record<string, number> = {};
  items.forEach(item => {
    const s = item.${statusField} || 'unknown';
    statusCounts[s] = (statusCounts[s] || 0) + 1;
  });
  const maxCount = Math.max(...Object.values(statusCounts), 1);

  return (
    <div className="p-6 space-y-6">
      <h1 className="text-2xl font-bold text-gray-900">${model} Analytics</h1>

      <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
        <div className="bg-white rounded-lg border border-gray-200 shadow-sm p-5">
          <h2 className="text-lg font-medium text-gray-900 mb-4">Status Distribution</h2>
          <div className="space-y-3">
            {Object.entries(statusCounts).map(([status, count]) => (
              <div key={status}>
                <div className="flex justify-between text-sm mb-1">
                  <span className="text-gray-600">{status.replace(/[_-]/g, ' ')}</span>
                  <span className="font-medium">{count}</span>
                </div>
                <div className="w-full bg-gray-100 rounded-full h-2">
                  <div className="bg-blue-500 h-2 rounded-full transition-all" style={{ width: \`\${(count / maxCount) * 100}%\` }}></div>
                </div>
              </div>
            ))}
          </div>
        </div>

${numericAttrs.length > 0 ? `        <div className="bg-white rounded-lg border border-gray-200 shadow-sm p-5">
          <h2 className="text-lg font-medium text-gray-900 mb-4">Metrics</h2>
          <div className="space-y-4">
${numericAttrs.slice(0, 4).map(a => `            <div className="flex justify-between items-baseline">
              <span className="text-sm text-gray-600">${a.name.charAt(0).toUpperCase() + a.name.slice(1)}</span>
              <div className="text-right">
                <p className="text-lg font-bold text-gray-900">{items.reduce((s, i) => s + (Number(i.${a.name}) || 0), 0)}</p>
                <p className="text-xs text-gray-400">avg: {items.length ? (items.reduce((s, i) => s + (Number(i.${a.name}) || 0), 0) / items.length).toFixed(1) : 0}</p>
              </div>
            </div>`).join('\n')}
          </div>
        </div>` : ''}
      </div>
    </div>
  );
}

export default ${name};
`;
}

// ============================================================================
// Helpers
// ============================================================================

function capitalize(s: string): string {
  return s.charAt(0).toUpperCase() + s.slice(1);
}
