/**
 * scaffold-vitrine/generate.ts — PURE spec -> GeneratedFile[] (zero I/O).
 *
 * Emits, into web/{appCode}-web/src/:
 *   - vitrine/sections/*.tsx   reusable presentational blocks (SDK theme tokens + lucide)
 *   - vitrine/pages/*.tsx       composed pages (home override + presentation pages)
 *   - vitrine/locales/{lang}/vitrine.json   i18n copy (namespace `vitrine`)
 *   - extensions/vitrine.generated.ts        wiring (PublicRouteRegistry + addClientResources + vitrineExtensions)
 *
 * The page<->i18n key contract is generated in a SINGLE pass (renderSection emits
 * both the JSX `t('scope.sN.field')` calls and the matching JSON), so the two can
 * never drift. Components use the SDK theme.css tokens (--text-secondary,
 * --bg-card, --border-color, primary/accent color utilities, gradient-text,
 * hover-card, animated-gradient) — the vitrine renders inside the SDK public shell.
 */
import type {
  GeneratedFile,
  PageSpec,
  ProjectLayout,
  ScaffoldVitrineInput,
  Section,
} from './types.js';

// ── lucide-react icon allowlist (all present in ^0.562) ──────────────────────
const LUCIDE_ALLOWLIST = new Set<string>([
  'Sparkles', 'Zap', 'Shield', 'ShieldCheck', 'Lock', 'Rocket', 'Users', 'UserPlus',
  'Bell', 'Star', 'Heart', 'Check', 'CheckCircle', 'BadgeCheck', 'Globe', 'Layers',
  'Settings', 'Cpu', 'Cloud', 'Database', 'Code', 'Gauge', 'LineChart', 'BarChart3',
  'PieChart', 'TrendingUp', 'Activity', 'Mail', 'MessageSquare', 'Phone', 'MapPin',
  'Map', 'Calendar', 'Clock', 'Award', 'Briefcase', 'Building', 'Building2', 'Factory',
  'Store', 'ShoppingCart', 'CreditCard', 'Wallet', 'Coins', 'Receipt', 'Package',
  'Truck', 'Wrench', 'Palette', 'Smartphone', 'Monitor', 'Server', 'Wifi', 'Key',
  'Eye', 'Search', 'Filter', 'Target', 'Lightbulb', 'Puzzle', 'Workflow', 'Boxes',
  'Component', 'Blocks', 'Layout', 'PenTool', 'Image', 'FileText', 'Folder', 'Bookmark',
  'Tag', 'Gift', 'Flag', 'Compass', 'Headphones', 'LifeBuoy', 'Megaphone', 'Share2',
  'ThumbsUp',
]);

function normalizeIcon(name?: string): string {
  if (name && LUCIDE_ALLOWLIST.has(name)) return name;
  return 'Sparkles';
}

const SECTION_COMPONENT: Record<Section['type'], string> = {
  hero: 'Hero',
  features: 'FeatureGrid',
  split: 'SplitFeature',
  cta: 'CtaBanner',
  faq: 'Faq',
  logos: 'LogoStrip',
};

/** JSON string literal for embedding in generated source. */
const s = (v: string): string => JSON.stringify(v);

// ============================================================================
// Static section components (reusable, prop-driven, theme-token styled)
// ============================================================================
const BANNER =
  '/* Scaffolded by /site-vitrine — editable. Re-running keeps your edits (use force to overwrite). */\n';

const HERO_TSX = `${BANNER}import type { ReactElement } from 'react';
import { Sparkles, ArrowRight, Check } from 'lucide-react';

export interface HeroCta { label: string; href: string; }
export interface HeroProps {
  badge?: string;
  headline: string;
  highlight?: string;
  subhead?: string;
  primaryCta?: HeroCta;
  secondaryCta?: HeroCta;
  bullets?: string[];
}

export function Hero({ badge, headline, highlight, subhead, primaryCta, secondaryCta, bullets }: HeroProps): ReactElement {
  return (
    <section className="relative pt-32 pb-20 md:pt-40 md:pb-32 overflow-hidden">
      <div className="absolute inset-0 overflow-hidden pointer-events-none">
        <div className="absolute -top-40 -right-40 w-80 h-80 rounded-full bg-primary-500/10 blur-3xl" />
        <div className="absolute -bottom-40 -left-40 w-80 h-80 rounded-full bg-accent-500/10 blur-3xl" />
      </div>
      <div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
        <div className="text-center">
          {badge ? (
            <div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-primary-500/10 border border-primary-500/20 mb-8">
              <Sparkles className="w-4 h-4 text-primary-500" />
              <span className="text-sm font-medium text-primary-500">{badge}</span>
            </div>
          ) : null}
          <h1 className="text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-bold tracking-tight mb-6">
            {headline}
            {highlight ? (
              <>
                <br />
                <span className="gradient-text">{highlight}</span>
              </>
            ) : null}
          </h1>
          {subhead ? (
            <p className="max-w-2xl mx-auto text-lg md:text-xl text-[var(--text-secondary)] mb-10">{subhead}</p>
          ) : null}
          {primaryCta || secondaryCta ? (
            <div className="flex flex-col sm:flex-row items-center justify-center gap-4 mb-16">
              {primaryCta ? (
                <a href={primaryCta.href} className="w-full sm:w-auto inline-flex items-center justify-center gap-2 px-8 py-4 rounded-xl bg-primary-600 hover:bg-primary-700 text-white font-semibold transition-all shadow-lg shadow-primary-500/25 hover:shadow-primary-500/40">
                  {primaryCta.label}
                  <ArrowRight className="w-5 h-5" />
                </a>
              ) : null}
              {secondaryCta ? (
                <a href={secondaryCta.href} className="w-full sm:w-auto inline-flex items-center justify-center gap-2 px-8 py-4 rounded-xl bg-[var(--bg-tertiary)] hover:bg-[var(--bg-secondary)] border border-[var(--border-color)] font-semibold transition-all">
                  {secondaryCta.label}
                </a>
              ) : null}
            </div>
          ) : null}
          {bullets && bullets.length > 0 ? (
            <div className="flex flex-wrap items-center justify-center gap-6">
              {bullets.map((b) => (
                <div key={b} className="flex items-center gap-2 text-[var(--text-secondary)]">
                  <Check className="w-5 h-5 text-primary-500" />
                  <span>{b}</span>
                </div>
              ))}
            </div>
          ) : null}
        </div>
      </div>
    </section>
  );
}
`;

const FEATUREGRID_TSX = `${BANNER}import type { ComponentType, ReactElement } from 'react';

export interface FeatureItem {
  icon: ComponentType<{ className?: string }>;
  title: string;
  description: string;
}
export interface FeatureGridProps {
  title?: string;
  subtitle?: string;
  items: FeatureItem[];
}

export function FeatureGrid({ title, subtitle, items }: FeatureGridProps): ReactElement {
  return (
    <section id="features" className="py-20 md:py-32">
      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
        {title || subtitle ? (
          <div className="text-center mb-16">
            {title ? <h2 className="text-3xl md:text-4xl font-bold mb-4">{title}</h2> : null}
            {subtitle ? <p className="max-w-2xl mx-auto text-[var(--text-secondary)]">{subtitle}</p> : null}
          </div>
        ) : null}
        <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
          {items.map((item) => {
            const Icon = item.icon;
            return (
              <div key={item.title} className="group p-6 rounded-2xl bg-[var(--bg-card)] border border-[var(--border-color)] hover-card">
                <div className="w-12 h-12 rounded-xl bg-primary-500/10 flex items-center justify-center mb-4">
                  <Icon className="w-6 h-6 text-primary-500" />
                </div>
                <h3 className="text-xl font-semibold mb-2">{item.title}</h3>
                <p className="text-[var(--text-secondary)]">{item.description}</p>
              </div>
            );
          })}
        </div>
      </div>
    </section>
  );
}
`;

const SPLITFEATURE_TSX = `${BANNER}import type { ReactElement } from 'react';
import { Check } from 'lucide-react';

export interface SplitCta { label: string; href: string; }
export interface SplitFeatureProps {
  title: string;
  highlight?: string;
  body?: string;
  bullets?: string[];
  side?: 'left' | 'right';
  cta?: SplitCta;
}

export function SplitFeature({ title, highlight, body, bullets, side = 'left', cta }: SplitFeatureProps): ReactElement {
  const visual = (
    <div className="relative">
      <div className="absolute inset-0 bg-gradient-to-r from-primary-500/20 to-accent-500/20 rounded-3xl blur-2xl" />
      <div className="relative min-h-[280px] rounded-3xl bg-[var(--bg-card)] border border-[var(--border-color)]" />
    </div>
  );
  const content = (
    <div>
      <h2 className="text-3xl md:text-4xl font-bold mb-6">
        {title}
        {highlight ? (
          <>
            <br />
            <span className="gradient-text">{highlight}</span>
          </>
        ) : null}
      </h2>
      {body ? <p className="text-[var(--text-secondary)] mb-8">{body}</p> : null}
      {bullets && bullets.length > 0 ? (
        <ul className="space-y-4 mb-8">
          {bullets.map((b) => (
            <li key={b} className="flex items-start gap-3">
              <div className="flex-shrink-0 w-6 h-6 rounded-full bg-primary-500/10 flex items-center justify-center mt-0.5">
                <Check className="w-4 h-4 text-primary-500" />
              </div>
              <span className="text-[var(--text-secondary)]">{b}</span>
            </li>
          ))}
        </ul>
      ) : null}
      {cta ? (
        <a href={cta.href} className="inline-flex items-center gap-2 px-6 py-3 rounded-xl bg-primary-600 hover:bg-primary-700 text-white font-semibold transition-all">
          {cta.label}
        </a>
      ) : null}
    </div>
  );
  return (
    <section className="py-20 md:py-32 bg-[var(--bg-secondary)]">
      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
        <div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
          {side === 'right' ? (
            <>
              {content}
              {visual}
            </>
          ) : (
            <>
              {visual}
              {content}
            </>
          )}
        </div>
      </div>
    </section>
  );
}
`;

const CTABANNER_TSX = `${BANNER}import type { ReactElement } from 'react';
import { ArrowRight } from 'lucide-react';

export interface BannerCta { label: string; href: string; }
export interface CtaBannerProps {
  headline: string;
  subhead?: string;
  primaryCta: BannerCta;
  secondaryCta?: BannerCta;
}

export function CtaBanner({ headline, subhead, primaryCta, secondaryCta }: CtaBannerProps): ReactElement {
  return (
    <section id="contact" className="py-20 md:py-32">
      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
        <div className="relative overflow-hidden rounded-3xl animated-gradient p-px">
          <div className="relative rounded-3xl bg-[var(--bg-primary)] p-8 md:p-16 text-center">
            <h2 className="text-3xl md:text-4xl font-bold mb-4">{headline}</h2>
            {subhead ? <p className="max-w-2xl mx-auto text-[var(--text-secondary)] mb-8">{subhead}</p> : null}
            <div className="flex flex-col sm:flex-row items-center justify-center gap-4">
              <a href={primaryCta.href} className="w-full sm:w-auto inline-flex items-center justify-center gap-2 px-8 py-4 rounded-xl bg-primary-600 hover:bg-primary-700 text-white font-semibold transition-all shadow-lg shadow-primary-500/25 hover:shadow-primary-500/40">
                {primaryCta.label}
                <ArrowRight className="w-5 h-5" />
              </a>
              {secondaryCta ? (
                <a href={secondaryCta.href} className="w-full sm:w-auto inline-flex items-center justify-center gap-2 px-8 py-4 rounded-xl bg-[var(--bg-tertiary)] hover:bg-[var(--bg-secondary)] border border-[var(--border-color)] font-semibold transition-all">
                  {secondaryCta.label}
                </a>
              ) : null}
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}
`;

const FAQ_TSX = `${BANNER}import type { ReactElement } from 'react';

export interface FaqItem { question: string; answer: string; }
export interface FaqProps { title?: string; items: FaqItem[]; }

export function Faq({ title, items }: FaqProps): ReactElement {
  return (
    <section className="py-20 md:py-32">
      <div className="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8">
        {title ? <h2 className="text-3xl md:text-4xl font-bold mb-12 text-center">{title}</h2> : null}
        <div className="space-y-4">
          {items.map((item) => (
            <details key={item.question} className="group p-6 rounded-2xl bg-[var(--bg-card)] border border-[var(--border-color)]">
              <summary className="flex items-center justify-between cursor-pointer font-semibold list-none">
                {item.question}
                <span className="ml-4 text-primary-500 transition-transform group-open:rotate-45">+</span>
              </summary>
              <p className="mt-4 text-[var(--text-secondary)]">{item.answer}</p>
            </details>
          ))}
        </div>
      </div>
    </section>
  );
}
`;

const LOGOSTRIP_TSX = `${BANNER}import type { ReactElement } from 'react';

export interface LogoStripProps { label?: string; items: string[]; }

export function LogoStrip({ label, items }: LogoStripProps): ReactElement {
  return (
    <section className="py-8 bg-[var(--bg-secondary)] border-y border-[var(--border-color)]">
      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
        <div className="flex flex-wrap items-center justify-center gap-8 md:gap-12">
          {label ? <span className="text-[var(--text-tertiary)] text-sm uppercase tracking-wider">{label}</span> : null}
          {items.map((it) => (
            <span key={it} className="text-[var(--text-secondary)] font-medium">{it}</span>
          ))}
        </div>
      </div>
    </section>
  );
}
`;

const MARKETING_HEADER_TSX = `${BANNER}import type { ReactElement } from 'react';

export interface MarketingNavLink { label: string; href: string; }
export interface MarketingHeaderProps {
  brand: string;
  logoUrl?: string;
  links?: MarketingNavLink[];
  ctaLabel?: string;
  ctaHref?: string;
}

export function MarketingHeader({ brand, logoUrl, links = [], ctaLabel, ctaHref }: MarketingHeaderProps): ReactElement {
  return (
    <header className="sticky top-0 z-50 backdrop-blur bg-[var(--bg-secondary)] border-b border-[var(--border-color)]">
      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between">
        <a href="/" className="flex items-center gap-2 font-bold text-lg">
          {logoUrl ? <img src={logoUrl} alt={brand} className="h-8 w-auto" /> : null}
          <span>{brand}</span>
        </a>
        <nav className="hidden md:flex items-center gap-6">
          {links.map((l) => (
            <a key={l.href} href={l.href} className="text-[var(--text-secondary)] hover:text-primary-500 transition-colors">{l.label}</a>
          ))}
          {ctaLabel && ctaHref ? (
            <a href={ctaHref} className="inline-flex items-center px-4 py-2 rounded-lg bg-primary-600 hover:bg-primary-700 text-white font-semibold transition-all">{ctaLabel}</a>
          ) : null}
        </nav>
      </div>
    </header>
  );
}
`;

const MARKETING_FOOTER_TSX = `${BANNER}import type { ReactElement } from 'react';

export interface MarketingFooterLink { label: string; href: string; }
export interface MarketingFooterProps {
  brand: string;
  tagline?: string;
  links?: MarketingFooterLink[];
}

export function MarketingFooter({ brand, tagline, links = [] }: MarketingFooterProps): ReactElement {
  return (
    <footer className="border-t border-[var(--border-color)] bg-[var(--bg-secondary)]">
      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
        <div className="flex flex-col md:flex-row items-center justify-between gap-6">
          <div className="text-center md:text-left">
            <div className="font-bold text-lg">{brand}</div>
            {tagline ? <p className="text-[var(--text-secondary)] text-sm mt-1">{tagline}</p> : null}
          </div>
          <nav className="flex flex-wrap items-center justify-center gap-6">
            {links.map((l) => (
              <a key={l.href} href={l.href} className="text-[var(--text-secondary)] hover:text-primary-500 transition-colors text-sm">{l.label}</a>
            ))}
          </nav>
        </div>
        <div className="mt-8 pt-6 border-t border-[var(--border-color)] text-center text-[var(--text-tertiary)] text-sm">
          © {new Date().getFullYear()} {brand}
        </div>
      </div>
    </footer>
  );
}
`;

const SECTION_SOURCES: Record<string, { file: string; src: string }> = {
  hero: { file: 'Hero.tsx', src: HERO_TSX },
  features: { file: 'FeatureGrid.tsx', src: FEATUREGRID_TSX },
  split: { file: 'SplitFeature.tsx', src: SPLITFEATURE_TSX },
  cta: { file: 'CtaBanner.tsx', src: CTABANNER_TSX },
  faq: { file: 'Faq.tsx', src: FAQ_TSX },
  logos: { file: 'LogoStrip.tsx', src: LOGOSTRIP_TSX },
};

// ============================================================================
// Per-section render: emits matching JSX + i18n in one pass (no drift possible)
// ============================================================================
interface RenderedSection {
  jsx: string;
  i18n: Record<string, unknown>;
  icons: string[];
}

const key = (scope: string, i: number, field: string): string => `${scope}.s${i}.${field}`;

function indexed(values: string[]): Record<string, string> {
  return Object.fromEntries(values.map((v, j) => [String(j), v]));
}

function tag(name: string, props: string[]): string {
  return `      <${name}\n        ${props.join('\n        ')}\n      />`;
}

function renderSection(scope: string, i: number, sec: Section): RenderedSection {
  switch (sec.type) {
    case 'hero': {
      const i18n: Record<string, unknown> = { headline: sec.headline };
      const props = [`headline={t(${s(key(scope, i, 'headline'))})}`];
      if (sec.badge) {
        i18n.badge = sec.badge;
        props.push(`badge={t(${s(key(scope, i, 'badge'))})}`);
      }
      if (sec.highlight) {
        i18n.highlight = sec.highlight;
        props.push(`highlight={t(${s(key(scope, i, 'highlight'))})}`);
      }
      if (sec.subhead) {
        i18n.subhead = sec.subhead;
        props.push(`subhead={t(${s(key(scope, i, 'subhead'))})}`);
      }
      if (sec.primaryCta) {
        i18n.primaryCta = sec.primaryCta.label;
        props.push(`primaryCta={{ label: t(${s(key(scope, i, 'primaryCta'))}), href: ${s(sec.primaryCta.href)} }}`);
      }
      if (sec.secondaryCta) {
        i18n.secondaryCta = sec.secondaryCta.label;
        props.push(`secondaryCta={{ label: t(${s(key(scope, i, 'secondaryCta'))}), href: ${s(sec.secondaryCta.href)} }}`);
      }
      if (sec.bullets && sec.bullets.length > 0) {
        i18n.bullets = indexed(sec.bullets);
        props.push(`bullets={[${sec.bullets.map((_, j) => `t(${s(key(scope, i, `bullets.${j}`))})`).join(', ')}]}`);
      }
      return { jsx: tag('Hero', props), i18n, icons: [] };
    }
    case 'features': {
      const itemsI18n: Record<string, unknown> = {};
      const icons: string[] = [];
      const lits = sec.items.map((it, j) => {
        itemsI18n[String(j)] = { title: it.title, description: it.description };
        const icon = normalizeIcon(it.icon);
        icons.push(icon);
        return `          { icon: ${icon}, title: t(${s(key(scope, i, `items.${j}.title`))}), description: t(${s(key(scope, i, `items.${j}.description`))}) }`;
      });
      const i18n: Record<string, unknown> = { items: itemsI18n };
      const props: string[] = [];
      if (sec.title) {
        i18n.title = sec.title;
        props.push(`title={t(${s(key(scope, i, 'title'))})}`);
      }
      if (sec.subtitle) {
        i18n.subtitle = sec.subtitle;
        props.push(`subtitle={t(${s(key(scope, i, 'subtitle'))})}`);
      }
      props.push(`items={[\n${lits.join(',\n')}\n        ]}`);
      return { jsx: tag('FeatureGrid', props), i18n, icons };
    }
    case 'split': {
      const i18n: Record<string, unknown> = { title: sec.title };
      const props = [`title={t(${s(key(scope, i, 'title'))})}`];
      if (sec.highlight) {
        i18n.highlight = sec.highlight;
        props.push(`highlight={t(${s(key(scope, i, 'highlight'))})}`);
      }
      if (sec.body) {
        i18n.body = sec.body;
        props.push(`body={t(${s(key(scope, i, 'body'))})}`);
      }
      if (sec.bullets && sec.bullets.length > 0) {
        i18n.bullets = indexed(sec.bullets);
        props.push(`bullets={[${sec.bullets.map((_, j) => `t(${s(key(scope, i, `bullets.${j}`))})`).join(', ')}]}`);
      }
      props.push(`side=${s(sec.side)}`);
      if (sec.cta) {
        i18n.cta = sec.cta.label;
        props.push(`cta={{ label: t(${s(key(scope, i, 'cta'))}), href: ${s(sec.cta.href)} }}`);
      }
      return { jsx: tag('SplitFeature', props), i18n, icons: [] };
    }
    case 'cta': {
      const i18n: Record<string, unknown> = { headline: sec.headline, primaryCta: sec.primaryCta.label };
      const props = [
        `headline={t(${s(key(scope, i, 'headline'))})}`,
        `primaryCta={{ label: t(${s(key(scope, i, 'primaryCta'))}), href: ${s(sec.primaryCta.href)} }}`,
      ];
      if (sec.subhead) {
        i18n.subhead = sec.subhead;
        props.push(`subhead={t(${s(key(scope, i, 'subhead'))})}`);
      }
      if (sec.secondaryCta) {
        i18n.secondaryCta = sec.secondaryCta.label;
        props.push(`secondaryCta={{ label: t(${s(key(scope, i, 'secondaryCta'))}), href: ${s(sec.secondaryCta.href)} }}`);
      }
      return { jsx: tag('CtaBanner', props), i18n, icons: [] };
    }
    case 'faq': {
      const itemsI18n: Record<string, unknown> = {};
      const lits = sec.items.map((it, j) => {
        itemsI18n[String(j)] = { question: it.question, answer: it.answer };
        return `          { question: t(${s(key(scope, i, `items.${j}.question`))}), answer: t(${s(key(scope, i, `items.${j}.answer`))}) }`;
      });
      const i18n: Record<string, unknown> = { items: itemsI18n };
      const props: string[] = [];
      if (sec.title) {
        i18n.title = sec.title;
        props.push(`title={t(${s(key(scope, i, 'title'))})}`);
      }
      props.push(`items={[\n${lits.join(',\n')}\n        ]}`);
      return { jsx: tag('Faq', props), i18n, icons: [] };
    }
    case 'logos': {
      const i18n: Record<string, unknown> = { items: indexed(sec.items) };
      const props: string[] = [];
      if (sec.label) {
        i18n.label = sec.label;
        props.push(`label={t(${s(key(scope, i, 'label'))})}`);
      }
      props.push(`items={[${sec.items.map((_, j) => `t(${s(key(scope, i, `items.${j}`))})`).join(', ')}]}`);
      return { jsx: tag('LogoStrip', props), i18n, icons: [] };
    }
  }
}

// ============================================================================
// Page composer
// ============================================================================
export interface NavLink {
  label: string;
  href: string;
}

interface BuildPageOptions {
  whiteLabel: boolean;
  brand?: string;
  tagline?: string;
  logoUrl?: string;
  navLinks?: NavLink[];
}

interface BuiltPage {
  content: string;
  i18nScope: Record<string, unknown>;
}

function buildPage(
  scope: string,
  compName: string,
  sections: Section[],
  opts: BuildPageOptions,
): BuiltPage {
  const rendered = sections.map((sec, i) => renderSection(scope, i, sec));
  const i18nScope: Record<string, unknown> = {};
  rendered.forEach((r, i) => {
    i18nScope[`s${i}`] = r.i18n;
  });

  const icons = Array.from(new Set(rendered.flatMap((r) => r.icons))).sort();
  const sectionComps = Array.from(new Set(sections.map((sec) => SECTION_COMPONENT[sec.type])));

  const imports = [
    `import type { ReactElement } from 'react';`,
    `import { useTranslation } from 'react-i18next';`,
    `import type { PageProps } from '@atlashub/smartstack';`,
  ];
  if (icons.length > 0) imports.push(`import { ${icons.join(', ')} } from 'lucide-react';`);
  for (const comp of sectionComps) imports.push(`import { ${comp} } from '../sections/${comp}';`);
  if (opts.whiteLabel) {
    imports.push(`import { MarketingHeader } from '../sections/MarketingHeader';`);
    imports.push(`import { MarketingFooter } from '../sections/MarketingFooter';`);
  }

  const body = rendered.map((r) => r.jsx).join('\n');

  let inner: string;
  if (opts.whiteLabel) {
    const navLit = `[${(opts.navLinks ?? [])
      .map((l) => `{ label: ${s(l.label)}, href: ${s(l.href)} }`)
      .join(', ')}]`;
    const headerProps = [`brand=${s(opts.brand ?? '')}`];
    if (opts.logoUrl) headerProps.push(`logoUrl=${s(opts.logoUrl)}`);
    headerProps.push(`links={${navLit}}`);
    const footerProps = [`brand=${s(opts.brand ?? '')}`];
    if (opts.tagline) footerProps.push(`tagline=${s(opts.tagline)}`);
    footerProps.push(`links={${navLit}}`);
    inner = `    <div className="min-h-screen flex flex-col bg-[var(--bg-primary)]">
      <MarketingHeader ${headerProps.join(' ')} />
      <main className="flex-1">
${body}
      </main>
      <MarketingFooter ${footerProps.join(' ')} />
    </div>`;
  } else {
    inner = `    <div className="flex flex-col">
${body}
    </div>`;
  }

  const content = `${imports.join('\n')}

export function ${compName}(_props?: PageProps): ReactElement {
  const { t } = useTranslation('vitrine');
  return (
${inner}
  );
}
`;
  return { content, i18nScope };
}

// ============================================================================
// Wiring file (regenerated each run)
// ============================================================================
function buildWiringFile(spec: ScaffoldVitrineInput): string {
  const hasHome = !!spec.home?.enabled;
  const lines: string[] = [
    '/* AUTO-GENERATED by /site-vitrine (scaffold-vitrine) — regenerated on each run. Do not edit. */',
    `import { PublicRouteRegistry, lazyWithRetry, addClientResources, PAGE_KEYS } from '@atlashub/smartstack';`,
    `import type { ExtensionConfig } from '@atlashub/smartstack';`,
  ];
  for (const lang of spec.languages) {
    lines.push(`import ${lang}Vitrine from '../vitrine/locales/${lang}/vitrine.json';`);
  }
  if (hasHome) lines.push(`import { HomePage } from '../vitrine/pages/HomePage';`);
  lines.push('');
  for (const lang of spec.languages) {
    lines.push(`addClientResources('${lang}', { vitrine: ${lang}Vitrine });`);
  }
  lines.push('');
  for (const page of spec.pages ?? []) {
    const comp = `${page.name}Page`;
    lines.push(
      `const ${comp} = lazyWithRetry(() =>\n` +
        `  import('../vitrine/pages/${comp}').then((m) => {\n` +
        `    const resolved = m.${comp} ?? m.default;\n` +
        `    if (!resolved) throw new Error('${comp}: missing export');\n` +
        `    return { default: resolved };\n` +
        `  }),\n` +
        `);`,
    );
    lines.push(
      `PublicRouteRegistry.register({ path: ${s(page.path)}, component: ${comp}, layout: ${s(page.layout)} });`,
    );
  }
  lines.push('');
  const pagesMap = hasHome ? '{ [PAGE_KEYS.HOME]: HomePage }' : '{}';
  lines.push(`export const vitrineExtensions: ExtensionConfig = { pages: ${pagesMap} };`);
  lines.push('');
  return lines.join('\n');
}

// ============================================================================
// main.tsx wiring (idempotent string transform — model: login-config)
// ============================================================================
export const VITRINE_IMPORT_LINE = `import { vitrineExtensions } from './extensions/vitrine.generated';`;
const COMPONENT_REGISTRY_IMPORT = `import './extensions/componentRegistry.generated';`;

export interface InjectResult {
  content: string;
  status: 'applied' | 'unchanged' | 'not-found' | 'skipped-customised';
}

/**
 * Ensure main.tsx (a) imports the wiring module (runs the i18n + route
 * registrations as side-effects, and brings in `vitrineExtensions`) and (b)
 * spreads `vitrineExtensions` into the provider's `extensions: {}`.
 *
 * Idempotent. Atomic: if the empty `extensions: {}` can't be found (main.tsx
 * customized), nothing is written and 'not-found' is returned so the caller can
 * print a paste-in snippet.
 */
export function injectVitrineExtensions(source: string): InjectResult {
  if (/\/\/\s*@customised/.test(source)) {
    return { content: source, status: 'skipped-customised' };
  }

  const hasImport = source.includes(VITRINE_IMPORT_LINE);
  const hasSpread = source.includes('...vitrineExtensions');
  if (hasImport && hasSpread) return { content: source, status: 'unchanged' };

  let content = source;

  if (!hasImport) {
    if (content.includes(COMPONENT_REGISTRY_IMPORT)) {
      content = content.replace(
        COMPONENT_REGISTRY_IMPORT,
        `${COMPONENT_REGISTRY_IMPORT}\n${VITRINE_IMPORT_LINE}`,
      );
    } else {
      content = `${VITRINE_IMPORT_LINE}\n${content}`;
    }
  }

  if (!hasSpread) {
    if (!/extensions:\s*\{\s*\}/.test(content)) {
      // Don't leave a dangling import without the spread — bail atomically.
      return { content: source, status: 'not-found' };
    }
    content = content.replace(/extensions:\s*\{\s*\}/, 'extensions: { ...vitrineExtensions }');
  }

  return { content, status: 'applied' };
}

export function snippetForManualWiring(): string {
  return (
    'Wire the vitrine manually in web/<app>-web/src/main.tsx: add ' +
    `\`${VITRINE_IMPORT_LINE}\` near the other extension imports, and change ` +
    '`extensions: {}` to `extensions: { ...vitrineExtensions }` in the SmartStackProvider config.'
  );
}

// ============================================================================
// Top-level generate
// ============================================================================
export function buildNavLinks(spec: ScaffoldVitrineInput): NavLink[] {
  const links: NavLink[] = [{ label: 'Home', href: '/' }];
  for (const page of spec.pages ?? []) {
    links.push({ label: page.title ?? page.name, href: page.path });
  }
  return links;
}

export function countSections(spec: ScaffoldVitrineInput): number {
  const home = spec.home?.enabled ? spec.home.sections.length : 0;
  const pages = (spec.pages ?? []).reduce((n, p) => n + p.sections.length, 0);
  return home + pages;
}

export function generate(spec: ScaffoldVitrineInput, layout: ProjectLayout): GeneratedFile[] {
  const files: GeneratedFile[] = [];
  const srcPrefix = `${layout.webDir}/src`;
  const usesWhiteLabel = (spec.pages ?? []).some((p) => p.layout === 'none');

  // 1) Section components (only those used) + marketing chrome when white-label.
  const usedTypes = new Set<Section['type']>();
  if (spec.home?.enabled) spec.home.sections.forEach((sec) => usedTypes.add(sec.type));
  (spec.pages ?? []).forEach((p) => p.sections.forEach((sec) => usedTypes.add(sec.type)));
  for (const t of usedTypes) {
    const def = SECTION_SOURCES[t];
    files.push({ path: `${srcPrefix}/vitrine/sections/${def.file}`, content: def.src, strategy: 'skip-if-exists' });
  }
  if (usesWhiteLabel) {
    files.push({ path: `${srcPrefix}/vitrine/sections/MarketingHeader.tsx`, content: MARKETING_HEADER_TSX, strategy: 'skip-if-exists' });
    files.push({ path: `${srcPrefix}/vitrine/sections/MarketingFooter.tsx`, content: MARKETING_FOOTER_TSX, strategy: 'skip-if-exists' });
  }

  // 2) Pages + i18n accumulation.
  const i18nRoot: Record<string, unknown> = {};
  const navLinks = buildNavLinks(spec);

  if (spec.home?.enabled) {
    const built = buildPage('home', 'HomePage', spec.home.sections, { whiteLabel: false });
    files.push({ path: `${srcPrefix}/vitrine/pages/HomePage.tsx`, content: built.content, strategy: 'skip-if-exists' });
    i18nRoot.home = built.i18nScope;
  }

  for (const page of spec.pages ?? []) {
    const scope = page.name.toLowerCase();
    const built = buildPage(scope, `${page.name}Page`, page.sections, {
      whiteLabel: page.layout === 'none',
      brand: spec.branding?.appName ?? spec.appCode,
      tagline: spec.branding?.tagline,
      logoUrl: spec.branding?.logoUrl,
      navLinks,
    });
    files.push({ path: `${srcPrefix}/vitrine/pages/${page.name}Page.tsx`, content: built.content, strategy: 'skip-if-exists' });
    i18nRoot[scope] = built.i18nScope;
  }

  // 3) i18n locales — same copy per language (translate non-primary later). Deep-merge.
  for (const lang of spec.languages) {
    files.push({
      path: `${srcPrefix}/vitrine/locales/${lang}/vitrine.json`,
      content: `${JSON.stringify(i18nRoot, null, 2)}\n`,
      strategy: 'deep-merge-json',
    });
  }

  // 4) Wiring (overwrite).
  files.push({
    path: `${srcPrefix}/extensions/vitrine.generated.ts`,
    content: buildWiringFile(spec),
    strategy: 'overwrite',
  });

  return files;
}
