/**
 * scaffold-vitrine — pure-layer tests (generate() output + helpers).
 *
 * Everything asserted here is deterministic: we drive the real `generate()` and
 * inspect the emitted files, so the page<->i18n key contract and the wiring are
 * checked against the actual output a developer would get.
 */
import { describe, it, expect } from 'vitest';
import {
  generate,
  injectVitrineExtensions,
  VITRINE_IMPORT_LINE,
  buildNavLinks,
  countSections,
} from '../generate.js';
import { cmpSemver } from '../validate.js';
import { ScaffoldVitrineInputSchema, isReservedPath } from '../types.js';
import type { GeneratedFile } from '../types.js';

const LAYOUT = { webDir: 'web/demo-web' };
const SRC = 'web/demo-web/src';

function parse(raw: unknown) {
  return ScaffoldVitrineInputSchema.parse(raw);
}

function gen(raw: unknown): GeneratedFile[] {
  return generate(parse(raw), LAYOUT);
}

function byPath(files: GeneratedFile[], suffix: string): GeneratedFile {
  const f = files.find((x) => x.path === `${SRC}/${suffix}` || x.path.endsWith(suffix));
  if (!f) throw new Error(`file not found: ${suffix}\nhave:\n${files.map((x) => x.path).join('\n')}`);
  return f;
}

const HOME_SPEC = {
  projectPath: '/tmp/app',
  appCode: 'demo',
  home: {
    sections: [
      {
        type: 'hero',
        badge: 'Nouveau',
        headline: 'Bienvenue',
        highlight: 'chez Acme',
        subhead: 'La plateforme métier.',
        primaryCta: { label: 'Commencer', href: '/register' },
        secondaryCta: { label: 'En savoir plus', href: '#features' },
        bullets: ['Rapide', 'Sécurisé'],
      },
      {
        type: 'features',
        title: 'Fonctionnalités',
        items: [
          { icon: 'Users', title: 'Équipes', description: 'Gérez vos équipes.' },
          { icon: 'Zap', title: 'Vitesse', description: 'Ultra rapide.' },
        ],
      },
    ],
  },
};

describe('home override', () => {
  it('emits HomePage.tsx that composes the sections and reads the vitrine namespace', () => {
    const files = gen(HOME_SPEC);
    const home = byPath(files, 'vitrine/pages/HomePage.tsx');
    expect(home.strategy).toBe('skip-if-exists');
    expect(home.content).toContain('export function HomePage(_props?: PageProps): ReactElement');
    expect(home.content).toContain("useTranslation('vitrine')");
    expect(home.content).toContain('<Hero');
    expect(home.content).toContain('<FeatureGrid');
    expect(home.content).toContain("import { Hero } from '../sections/Hero';");
    expect(home.content).toContain("import { FeatureGrid } from '../sections/FeatureGrid';");
  });

  it('wires the home override via extensions.pages[PAGE_KEYS.HOME]', () => {
    const wiring = byPath(gen(HOME_SPEC), 'extensions/vitrine.generated.ts');
    expect(wiring.strategy).toBe('overwrite');
    expect(wiring.content).toContain("import { HomePage } from '../vitrine/pages/HomePage';");
    expect(wiring.content).toContain('export const vitrineExtensions: ExtensionConfig = { pages: { [PAGE_KEYS.HOME]: HomePage } };');
    expect(wiring.content).toContain("addClientResources('fr', { vitrine: frVitrine });");
    expect(wiring.content).toContain("addClientResources('en', { vitrine: enVitrine });");
  });

  it('emits only the section components actually used', () => {
    const files = gen(HOME_SPEC);
    expect(() => byPath(files, 'vitrine/sections/Hero.tsx')).not.toThrow();
    expect(() => byPath(files, 'vitrine/sections/FeatureGrid.tsx')).not.toThrow();
    // no split/cta/faq/logos in this spec
    expect(files.find((f) => f.path.endsWith('SplitFeature.tsx'))).toBeUndefined();
    expect(files.find((f) => f.path.endsWith('CtaBanner.tsx'))).toBeUndefined();
    // no white-label chrome
    expect(files.find((f) => f.path.endsWith('MarketingHeader.tsx'))).toBeUndefined();
  });
});

describe('i18n contract (page keys ↔ JSON)', () => {
  it('JSON holds the copy under indexed section keys; the page references the same keys', () => {
    const files = gen(HOME_SPEC);
    const fr = byPath(files, 'vitrine/locales/fr/vitrine.json');
    expect(fr.strategy).toBe('deep-merge-json');
    const json = JSON.parse(fr.content) as Record<string, any>;
    expect(json.home.s0.headline).toBe('Bienvenue');
    expect(json.home.s0.badge).toBe('Nouveau');
    expect(json.home.s0.bullets['0']).toBe('Rapide');
    expect(json.home.s0.primaryCta).toBe('Commencer'); // label only; href is structural
    expect(json.home.s1.items['0'].title).toBe('Équipes');

    const home = byPath(files, 'vitrine/pages/HomePage.tsx');
    expect(home.content).toContain('t("home.s0.headline")');
    expect(home.content).toContain('t("home.s0.bullets.0")');
    expect(home.content).toContain('t("home.s1.items.0.title")');
    // hrefs stay in the page, not i18n
    expect(home.content).toContain('href: "/register"');
  });

  it('seeds every requested language with the same copy', () => {
    const files = gen({ ...HOME_SPEC, languages: ['fr', 'en'] });
    const fr = byPath(files, 'vitrine/locales/fr/vitrine.json');
    const en = byPath(files, 'vitrine/locales/en/vitrine.json');
    expect(fr.content).toBe(en.content);
  });
});

describe('icons', () => {
  it('keeps allowlisted icons and falls back to Sparkles for unknown ones', () => {
    const files = gen({
      ...HOME_SPEC,
      home: {
        sections: [
          {
            type: 'features',
            items: [
              { icon: 'Users', title: 'A', description: 'a' },
              { icon: 'TotallyNotAnIcon', title: 'B', description: 'b' },
            ],
          },
        ],
      },
    });
    const home = byPath(files, 'vitrine/pages/HomePage.tsx');
    expect(home.content).toContain('Users');
    expect(home.content).toContain('Sparkles');
    expect(home.content).toContain("from 'lucide-react'");
    expect(home.content).not.toContain('TotallyNotAnIcon');
  });

  it('does not import lucide on a page without a features section', () => {
    const files = gen({
      projectPath: '/tmp/app',
      appCode: 'demo',
      home: { sections: [{ type: 'cta', headline: 'Go', primaryCta: { label: 'X', href: '/register' } }] },
    });
    const home = byPath(files, 'vitrine/pages/HomePage.tsx');
    expect(home.content).not.toContain("from 'lucide-react'");
  });
});

describe('public pages', () => {
  const SPEC = {
    projectPath: '/tmp/app',
    appCode: 'demo',
    pages: [
      {
        name: 'About',
        path: '/about',
        layout: 'public',
        sections: [{ type: 'split', title: 'À propos', body: 'Notre histoire.' }],
      },
    ],
  };

  it('registers a public route with lazyWithRetry and the correct path/layout', () => {
    const wiring = byPath(gen(SPEC), 'extensions/vitrine.generated.ts');
    expect(wiring.content).toContain('const AboutPage = lazyWithRetry(() =>');
    expect(wiring.content).toContain("import('../vitrine/pages/AboutPage')");
    expect(wiring.content).toContain('PublicRouteRegistry.register({ path: "/about", component: AboutPage, layout: "public" });');
    // no home → empty pages map
    expect(wiring.content).toContain('export const vitrineExtensions: ExtensionConfig = { pages: {} };');
  });

  it('a public (non-white-label) page does not pull in the marketing chrome', () => {
    const files = gen(SPEC);
    const about = byPath(files, 'vitrine/pages/AboutPage.tsx');
    expect(about.content).not.toContain('MarketingHeader');
    expect(about.content).toContain('<div className="flex flex-col">');
    expect(files.find((f) => f.path.endsWith('MarketingHeader.tsx'))).toBeUndefined();
  });
});

describe('white-label pages (layout: none)', () => {
  const SPEC = {
    projectPath: '/tmp/app',
    appCode: 'demo',
    branding: { appName: 'Acme', tagline: 'On vous simplifie la vie' },
    pages: [
      {
        name: 'Landing',
        path: '/landing',
        layout: 'none',
        sections: [{ type: 'hero', headline: 'Acme' }],
      },
    ],
  };

  it('wraps the page in MarketingHeader/MarketingFooter and emits the chrome components', () => {
    const files = gen(SPEC);
    const page = byPath(files, 'vitrine/pages/LandingPage.tsx');
    expect(page.content).toContain("import { MarketingHeader } from '../sections/MarketingHeader';");
    expect(page.content).toContain("import { MarketingFooter } from '../sections/MarketingFooter';");
    expect(page.content).toContain('<MarketingHeader brand="Acme"');
    expect(page.content).toContain('tagline="On vous simplifie la vie"');
    expect(page.content).toContain('min-h-screen flex flex-col');
    expect(() => byPath(files, 'vitrine/sections/MarketingHeader.tsx')).not.toThrow();
    expect(() => byPath(files, 'vitrine/sections/MarketingFooter.tsx')).not.toThrow();
  });

  it('registers the route with layout "none"', () => {
    const wiring = byPath(gen(SPEC), 'extensions/vitrine.generated.ts');
    expect(wiring.content).toContain('layout: "none"');
  });
});

describe('schema validation', () => {
  it('rejects when neither home nor pages is provided', () => {
    const r = ScaffoldVitrineInputSchema.safeParse({ projectPath: '/x', appCode: 'd' });
    expect(r.success).toBe(false);
  });

  it('rejects duplicate page names', () => {
    const r = ScaffoldVitrineInputSchema.safeParse({
      projectPath: '/x',
      appCode: 'd',
      pages: [
        { name: 'About', path: '/about', sections: [{ type: 'hero', headline: 'a' }] },
        { name: 'About', path: '/about-2', sections: [{ type: 'hero', headline: 'b' }] },
      ],
    });
    expect(r.success).toBe(false);
  });

  it('rejects a non-PascalCase page name and a non-absolute path', () => {
    expect(
      ScaffoldVitrineInputSchema.safeParse({
        projectPath: '/x',
        appCode: 'd',
        pages: [{ name: 'about', path: '/about', sections: [{ type: 'hero', headline: 'a' }] }],
      }).success,
    ).toBe(false);
    expect(
      ScaffoldVitrineInputSchema.safeParse({
        projectPath: '/x',
        appCode: 'd',
        pages: [{ name: 'About', path: 'about', sections: [{ type: 'hero', headline: 'a' }] }],
      }).success,
    ).toBe(false);
  });

  it('defaults page layout to public and languages to fr+en', () => {
    const spec = parse({
      projectPath: '/x',
      appCode: 'd',
      pages: [{ name: 'About', path: '/about', sections: [{ type: 'hero', headline: 'a' }] }],
    });
    expect(spec.pages?.[0].layout).toBe('public');
    expect(spec.languages).toEqual(['fr', 'en']);
  });
});

describe('isReservedPath (mirrors PublicRouteRegistry)', () => {
  it('flags reserved exact paths and prefixes', () => {
    expect(isReservedPath('/')).toBe(true);
    expect(isReservedPath('/login')).toBe(true);
    expect(isReservedPath('/register')).toBe(true);
    expect(isReservedPath('/docs')).toBe(true);
    expect(isReservedPath('/docs/getting-started')).toBe(true);
    expect(isReservedPath('/t')).toBe(true);
    expect(isReservedPath('/t/acme')).toBe(true);
  });

  it('allows ordinary marketing paths', () => {
    expect(isReservedPath('/about')).toBe(false);
    expect(isReservedPath('/pricing')).toBe(false);
    expect(isReservedPath('/contact')).toBe(false);
    // a path that merely starts with the same letters as a reserved one is fine
    expect(isReservedPath('/documents')).toBe(false);
    expect(isReservedPath('/team')).toBe(false);
  });
});

describe('main.tsx wiring (idempotent)', () => {
  const MAIN = `import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import { SmartStackProvider } from '@atlashub/smartstack';
import './extensions/componentRegistry.generated';
import App from './App';
import './index.css';
import './i18n';

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <BrowserRouter>
      <SmartStackProvider
        config={{
          apiUrl: import.meta.env.VITE_API_URL || 'http://localhost:5142',
          extensions: {}
        }}
      >
        <App />
      </SmartStackProvider>
    </BrowserRouter>
  </StrictMode>,
);
`;

  it('adds the import after componentRegistry and spreads vitrineExtensions', () => {
    const { content, status } = injectVitrineExtensions(MAIN);
    expect(status).toBe('applied');
    expect(content).toContain(VITRINE_IMPORT_LINE);
    expect(content.indexOf("componentRegistry.generated")).toBeLessThan(content.indexOf('vitrine.generated'));
    expect(content).toContain('extensions: { ...vitrineExtensions }');
    expect(content).not.toContain('extensions: {}');
  });

  it('is idempotent (second pass is unchanged, no duplicate import)', () => {
    const once = injectVitrineExtensions(MAIN).content;
    const twice = injectVitrineExtensions(once);
    expect(twice.status).toBe('unchanged');
    expect(twice.content).toBe(once);
    expect(once.match(/vitrineExtensions } from/g)?.length).toBe(1);
  });

  it('bails atomically when the empty extensions object is absent (customized main.tsx)', () => {
    const customized = MAIN.replace('extensions: {}', 'extensions: { pages: { home: MyHome } }');
    const { content, status } = injectVitrineExtensions(customized);
    expect(status).toBe('not-found');
    expect(content).toBe(customized); // unchanged — no dangling import
  });

  it('skips a main.tsx marked // @customised', () => {
    const optedOut = `// @customised\n${MAIN}`;
    expect(injectVitrineExtensions(optedOut).status).toBe('skipped-customised');
  });
});

describe('determinism + helpers', () => {
  it('generate() is deterministic (same spec → identical output)', () => {
    const a = gen(HOME_SPEC);
    const b = gen(HOME_SPEC);
    expect(a.map((f) => `${f.path}\n${f.content}`)).toEqual(b.map((f) => `${f.path}\n${f.content}`));
  });

  it('buildNavLinks lists home plus every page', () => {
    const spec = parse({
      projectPath: '/x',
      appCode: 'd',
      pages: [
        { name: 'About', path: '/about', title: 'À propos', sections: [{ type: 'hero', headline: 'a' }] },
        { name: 'Pricing', path: '/pricing', sections: [{ type: 'hero', headline: 'b' }] },
      ],
    });
    expect(buildNavLinks(spec)).toEqual([
      { label: 'Home', href: '/' },
      { label: 'À propos', href: '/about' },
      { label: 'Pricing', href: '/pricing' },
    ]);
  });

  it('countSections sums home + pages', () => {
    expect(countSections(parse(HOME_SPEC))).toBe(2);
  });

  it('cmpSemver orders versions correctly', () => {
    expect(cmpSemver('3.54.0', '3.55.0')).toBe(-1);
    expect(cmpSemver('3.55.0', '3.55.0')).toBe(0);
    expect(cmpSemver('3.56.0', '3.55.0')).toBe(1);
    expect(cmpSemver('3.55.1', '3.55.0')).toBe(1);
    expect(cmpSemver('3.55.0-beta.1', '3.55.0')).toBe(0); // prerelease stripped
  });
});
