// pages-tools-schema-v2.jsx — Schema Generator v2 (Phase 1: client-only)
// Exports SchemaGeneratorV2 to window.
// Pure helpers exposed via window.__schemaV2Internals for unit testing.
// Phase 2 TODO: real /api/tools/schema/autofill endpoint.

// ─────────────────────────────── Pure helpers (testable) ────────────────────

function buildGraph(fields, enabledTypes) {
  const clean = (obj) =>
    Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined && v !== null && v !== ''));

  const graph = [];

  if (enabledTypes.has('Organization')) {
    graph.push(clean({
      '@type': 'Organization',
      '@id': '#org',
      name: fields.orgName || undefined,
      url: fields.orgUrl || undefined,
      logo: fields.orgLogo ? { '@type': 'ImageObject', url: fields.orgLogo } : undefined,
    }));
  }

  if (enabledTypes.has('Article')) {
    graph.push(clean({
      '@type': 'Article',
      '@id': '#article',
      headline: fields.headline || undefined,
      author: fields.author ? { '@type': 'Person', name: fields.author } : undefined,
      datePublished: fields.datePublished || undefined,
      image: fields.image ? { '@type': 'ImageObject', url: fields.image } : undefined,
      publisher: enabledTypes.has('Organization') ? { '@id': '#org' } : undefined,
      mainEntityOfPage: fields.url || undefined,
    }));
  }

  if (enabledTypes.has('FAQPage')) {
    const questions = (fields.faqs || '').split('\n')
      .filter((line) => line.includes('|'))
      .map((line) => {
        const [q, a] = line.split('|');
        return {
          '@type': 'Question',
          name: (q || '').trim(),
          acceptedAnswer: { '@type': 'Answer', text: (a || '').trim() },
        };
      });
    if (questions.length > 0) {
      graph.push({ '@type': 'FAQPage', '@id': '#faqpage', mainEntity: questions });
    }
  }

  if (enabledTypes.has('BreadcrumbList')) {
    const items = (fields.breadcrumbs || '').split('\n')
      .filter((line) => line.includes('|'))
      .map((line, i) => {
        const [name, url] = line.split('|');
        return { '@type': 'ListItem', position: i + 1, name: (name || '').trim(), item: (url || '').trim() };
      });
    if (items.length > 0) {
      graph.push({ '@type': 'BreadcrumbList', '@id': '#breadcrumb', itemListElement: items });
    }
  }

  if (enabledTypes.has('SpeakableSpecification')) {
    graph.push({
      '@type': 'WebPage',
      '@id': '#speakable',
      speakable: {
        '@type': 'SpeakableSpecification',
        cssSelector: fields.speakableCss ? [fields.speakableCss] : undefined,
        xpath: fields.speakableXpath ? [fields.speakableXpath] : undefined,
      },
    });
  }

  if (enabledTypes.has('ClaimReview')) {
    graph.push(clean({
      '@type': 'ClaimReview',
      '@id': '#claimreview',
      claimReviewed: fields.claimText || undefined,
      reviewRating: fields.claimRating
        ? { '@type': 'Rating', ratingValue: fields.claimRating }
        : undefined,
      author: fields.claimAuthor ? { '@type': 'Organization', name: fields.claimAuthor } : undefined,
      datePublished: fields.datePublished || undefined,
      url: fields.claimUrl || undefined,
    }));
  }

  return { '@context': 'https://schema.org', '@graph': graph };
}

function parseImport(jsonText) {
  let data;
  try {
    data = JSON.parse(jsonText);
  } catch (e) {
    throw new Error('Invalid JSON — check your input.');
  }

  const items = data['@graph']
    ? data['@graph']
    : Array.isArray(data)
    ? data
    : [data];

  const fields = {};
  const typeSet = new Set();

  items.forEach((item) => {
    const type = item['@type'];
    if (!type) return;
    typeSet.add(type);

    if (type === 'Article') {
      if (item.headline) fields.headline = item.headline;
      if (item.author) fields.author = typeof item.author === 'string' ? item.author : item.author.name;
      if (item.datePublished) fields.datePublished = item.datePublished;
      if (item.mainEntityOfPage) fields.url = item.mainEntityOfPage;
      if (item.image) {
        if (typeof item.image === 'string') fields.image = item.image;
        else if (Array.isArray(item.image))
          fields.image = typeof item.image[0] === 'string' ? item.image[0] : item.image[0].url;
        else fields.image = item.image.url;
      }
    } else if (type === 'FAQPage') {
      fields.faqs = (item.mainEntity || [])
        .map((q) => `${q.name}|${q.acceptedAnswer?.text || ''}`)
        .join('\n');
    } else if (type === 'Organization') {
      if (item.name) fields.orgName = item.name;
      if (item.url) fields.orgUrl = item.url;
      if (item.logo) fields.orgLogo = typeof item.logo === 'string' ? item.logo : item.logo.url;
    } else if (type === 'BreadcrumbList') {
      fields.breadcrumbs = (item.itemListElement || [])
        .map((li) => `${li.name}|${li.item || ''}`)
        .join('\n');
    } else if (type === 'ClaimReview') {
      if (item.claimReviewed) fields.claimText = item.claimReviewed;
      if (item.reviewRating) fields.claimRating = item.reviewRating.ratingValue;
      if (item.author) fields.claimAuthor = item.author.name;
      if (item.url) fields.claimUrl = item.url;
    } else if (type === 'WebPage' && item.speakable) {
      typeSet.add('SpeakableSpecification');
      if (item.speakable.cssSelector) fields.speakableCss = item.speakable.cssSelector[0];
      if (item.speakable.xpath) fields.speakableXpath = item.speakable.xpath[0];
    }
  });

  return { fields, types: [...typeSet] };
}

function wizardToBundle(answers) {
  const types = new Set(['BreadcrumbList', 'Organization']);
  const q1 = (answers.q1 || '').toLowerCase();
  const q3 = (answers.q3 || '').toLowerCase();

  if (['article', 'product', 'recipe'].includes(q1)) {
    types.add('Article');
  }
  if (q1 === 'faq') {
    types.add('FAQPage');
    types.add('Article');
  }
  if (q3 === 'faq') {
    types.add('FAQPage');
  }

  return types;
}

// Expose for unit tests
window.__schemaV2Internals = { buildGraph, parseImport, wizardToBundle };
if (typeof module !== 'undefined') {
  module.exports = { buildGraph, parseImport, wizardToBundle };
}

// ─────────────────────────────── Component ───────────────────────────────────

function SchemaGeneratorV2({ accent }) {
  const { t } = useT();
  const isMobile = useMobile(960);
  const resolvedAccent = accent || 'var(--accent)';

  // State
  const [urlInput, setUrlInput] = React.useState('');
  const [loading, setLoading] = React.useState(false);
  const [loadingStep, setLoadingStep] = React.useState(0);
  const [enabledTypes, setEnabledTypes] = React.useState(new Set());
  const [activeTab, setActiveTab] = React.useState('jsonld');
  const [showWizard, setShowWizard] = React.useState(true);
  const [wizardStep, setWizardStep] = React.useState(1);
  const [wizardAnswers, setWizardAnswers] = React.useState({});
  const [showImport, setShowImport] = React.useState(false);
  const [importText, setImportText] = React.useState('');
  const [importError, setImportError] = React.useState(null);
  const [importSuccess, setImportSuccess] = React.useState(false);
  const [docPopover, setDocPopover] = React.useState(null);
  const [copied, setCopied] = React.useState(false);
  const [shareId, setShareId] = React.useState(null);
  const [shareToast, setShareToast] = React.useState(false);
  const [faqOpen, setFaqOpen] = React.useState({});
  const [autofillNonce, setAutofillNonce] = React.useState(null);
  const [autofillError, setAutofillError] = React.useState(null);

  // Fetch nonce on mount for autofill HMAC validation
  React.useEffect(() => {
    fetch('/api/tools/nonce')
      .then((r) => r.ok ? r.json() : null)
      .then((data) => { if (data && data.token) setAutofillNonce(data.token); })
      .catch(() => {}); // non-fatal — autofill degrades gracefully
  }, []);

  const [fields, setFields] = React.useState({
    headline: '', author: '', datePublished: '', image: '', publisher: '', url: '',
    faqs: '', breadcrumbs: '',
    orgName: '', orgUrl: '', orgLogo: '',
    speakableCss: '.speakable', speakableXpath: '/html/head/title',
    claimText: '', claimRating: '', claimUrl: '', claimAuthor: '',
  });

  const BUNDLE_TYPES = [
    { id: 'Article', label: 'Article' },
    { id: 'FAQPage', label: 'FAQPage' },
    { id: 'BreadcrumbList', label: 'BreadcrumbList' },
    { id: 'Organization', label: 'Organization' },
    { id: 'SpeakableSpecification', label: 'Speakable' },
    { id: 'ClaimReview', label: 'ClaimReview' },
  ];

  const DOC_QUOTES = {
    headline: 'Max 110 characters. Google uses this as the title in rich results.',
    author: 'Person schema. Improves E-E-A-T signals for AI citation.',
    datePublished: 'ISO 8601 format (YYYY-MM-DD). Required for Article rich results.',
    image: 'Recommended: 1200×630px. Must be crawlable. Improves CTR in rich results.',
    publisher: 'Organization publishing the content. Required for Article rich results.',
    url: 'The canonical URL of the page being described.',
    faqs: 'One question and answer per line, separated by |. Google shows up to 10 FAQs.',
    breadcrumbs: 'Navigation path. Name|URL per line. Shown in SERP as breadcrumb trail.',
    orgName: 'Legal name of the organization. Use consistent naming across all pages.',
    orgUrl: 'URL of the organization homepage.',
    orgLogo: 'Must be at least 112×112px. PNG or SVG recommended.',
    speakableCss: 'CSS selectors identifying speakable passages for voice search.',
    speakableXpath: 'XPath selectors. Alternative to CSS selectors.',
    claimText: 'The factual claim being reviewed or fact-checked.',
    claimRating: 'Rating: true, false, mostly-true, misleading, etc.',
    claimUrl: 'URL of the source making the claim.',
    claimAuthor: 'Organization or person who made the original claim.',
  };

  // Loading microcopy cycle
  React.useEffect(() => {
    if (!loading) return;
    const interval = setInterval(() => setLoadingStep((s) => (s + 1) % 3), 500);
    return () => clearInterval(interval);
  }, [loading]);

  // Autofill — calls /api/tools/schema/autofill (Phase 2)
  const handleAutofill = React.useCallback(
    async (e) => {
      e.preventDefault();
      if (!urlInput) return;
      setLoading(true);
      setAutofillError(null);

      const startTime = Date.now();

      // Ensure minimum 1.4 s visible loading for UX polish
      const minDelay = new Promise((r) => setTimeout(r, 1400));

      // If no nonce yet (mount fetch not resolved), fall back to manual mode
      if (!autofillNonce) {
        await minDelay;
        setLoading(false);
        setAutofillError('Could not reach the server. Fill fields manually.');
        return;
      }

      let result = null;
      let errorMsg = null;

      // TODO Phase 4: mount Cloudflare Turnstile widget here (invisible).
      // On submit, retrieve token via window.turnstile.getResponse(widgetId) and
      // include in POST body as turnstile_token. See docs:
      // https://developers.cloudflare.com/turnstile/get-started/client-side-rendering/
      try {
        const resp = await fetch('/api/tools/schema/autofill', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ url: urlInput, token: autofillNonce }),
        });

        if (resp.status === 429) {
          errorMsg = 'Try again in an hour (rate limit reached).';
        } else if (resp.status === 400) {
          errorMsg = 'URL not allowed (private or internal address).';
        } else if (resp.status === 401) {
          errorMsg = 'Session expired — please refresh and try again.';
        } else if (!resp.ok) {
          errorMsg = 'Could not read that page. Fill fields manually.';
        } else {
          result = await resp.json();
        }
      } catch (_err) {
        errorMsg = 'Could not reach the server. Fill fields manually.';
      }

      // Enforce minimum loading time
      await minDelay;

      if (result && result.ok && result.fields) {
        const f = result.fields;
        setFields((prev) => ({
          ...prev,
          headline: f.headline || prev.headline,
          author: f.author || prev.author,
          datePublished: f.datePublished || prev.datePublished,
          image: f.image || prev.image,
          publisher: f.publisher || prev.publisher,
          url: urlInput,
          orgName: f.publisher || prev.orgName,
        }));

        // Auto-enable Article; add FAQPage if FAQs extracted
        const types = new Set(['Article', 'BreadcrumbList', 'Organization']);
        if (Array.isArray(f.faqs) && f.faqs.length > 0) {
          types.add('FAQPage');
          // Populate FAQ field as pipe-separated lines
          const faqLines = f.faqs
            .map((item) => `${item.q || ''}|${item.a || ''}`)
            .join('\n');
          setFields((prev) => ({ ...prev, faqs: faqLines }));
        }
        setEnabledTypes(types);
        setShowWizard(false);

        // Refresh nonce for next potential autofill
        fetch('/api/tools/nonce')
          .then((r) => r.ok ? r.json() : null)
          .then((data) => { if (data && data.token) setAutofillNonce(data.token); })
          .catch(() => {});
      } else if (errorMsg) {
        setAutofillError(errorMsg);
      }

      setLoading(false);
    },
    [urlInput, autofillNonce],
  );

  const handleWizardAnswer = React.useCallback(
    (key, val) => {
      const next = { ...wizardAnswers, [key]: val };
      setWizardAnswers(next);
      if (wizardStep < 3) {
        setWizardStep((s) => s + 1);
      } else {
        setEnabledTypes(wizardToBundle(next));
        setShowWizard(false);
      }
    },
    [wizardAnswers, wizardStep],
  );

  const toggleType = React.useCallback(
    (id) => {
      const next = new Set(enabledTypes);
      if (next.has(id)) next.delete(id);
      else next.add(id);
      setEnabledTypes(next);
      if (next.size > 0) setShowWizard(false);
    },
    [enabledTypes],
  );

  const generatedJson = React.useMemo(() => buildGraph(fields, enabledTypes), [fields, enabledTypes]);

  const handleCopy = React.useCallback(() => {
    const text = JSON.stringify(generatedJson, null, 2);
    navigator.clipboard.writeText(text).then(() => {
      setCopied(true);
      setTimeout(() => setCopied(false), 1600);
    });
  }, [generatedJson]);

  const handleDownload = React.useCallback(() => {
    const blob = new Blob([JSON.stringify(generatedJson, null, 2)], { type: 'application/ld+json' });
    const href = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = href;
    a.download = 'schema.jsonld';
    a.click();
    URL.revokeObjectURL(href);
  }, [generatedJson]);

  const handleSaveShare = React.useCallback(async () => {
    try {
      const headline = fields.headline || fields.orgName || '';
      const inputSummary = headline ? 'Schema for: ' + headline : 'Custom schema';
      const resp = await fetch('/api/report/', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ tool: 'schema', payload: generatedJson, input_summary: inputSummary }),
      });
      if (!resp.ok) return;
      const data = await resp.json();
      setShareId(data.id);
      navigate('/report/' + data.id);
      if (navigator.clipboard) {
        await navigator.clipboard.writeText(data.public_url);
        setShareToast(true);
        setTimeout(() => setShareToast(false), 3000);
      }
    } catch (_) {}
  }, [generatedJson, fields]);

  const handleImport = React.useCallback(() => {
    try {
      const result = parseImport(importText);
      setFields((prev) => ({ ...prev, ...result.fields }));
      setEnabledTypes(new Set(result.types));
      setImportError(null);
      setImportSuccess(true);
      setTimeout(() => {
        setShowImport(false);
        setImportSuccess(false);
        setImportText('');
      }, 1200);
    } catch (err) {
      setImportError(err.message);
    }
  }, [importText]);

  const updateField = (name, val) => setFields((f) => ({ ...f, [name]: val }));

  // Field with [?] doc popover
  const renderField = (name, labelText, type = 'text', multiline = false) => (
    <div style={{ marginBottom: 14, position: 'relative' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 4 }}>
        <span className="mono" style={{ fontSize: 11, textTransform: 'uppercase', letterSpacing: '0.08em', color: 'var(--ink-3)' }}>
          {labelText || t('tools.schema.v2.field.' + name) || name}
        </span>
        {DOC_QUOTES[name] && (
          <button
            onClick={() => setDocPopover(docPopover === name ? null : name)}
            style={{ background: 'none', border: 'none', color: resolvedAccent, cursor: 'pointer', padding: 0, fontSize: 11, lineHeight: 1 }}
          >
            [?]
          </button>
        )}
      </div>
      {docPopover === name && (
        <div style={{
          position: 'absolute', zIndex: 20, top: 28, left: 0, right: 0,
          background: 'var(--paper)', border: '2px solid var(--ink)', padding: '10px 12px',
          borderRadius: 8, boxShadow: '4px 4px 0 var(--ink)', fontSize: 12, lineHeight: 1.5,
          color: 'var(--ink-2)',
        }}>
          {DOC_QUOTES[name]}
        </div>
      )}
      {multiline ? (
        <textarea
          rows={4}
          value={fields[name]}
          onChange={(e) => updateField(name, e.target.value)}
          style={{
            width: '100%', boxSizing: 'border-box',
            padding: '10px 12px', border: '2px solid var(--ink)', borderRadius: 8,
            fontFamily: 'var(--mono)', fontSize: 12, background: 'var(--paper-2)',
            color: 'var(--ink)', outline: 'none', resize: 'vertical',
          }}
        />
      ) : (
        <input
          type={type}
          value={fields[name]}
          onChange={(e) => updateField(name, e.target.value)}
          style={{
            width: '100%', boxSizing: 'border-box',
            padding: '10px 12px', border: '2px solid var(--ink)', borderRadius: 8,
            fontFamily: 'var(--sans)', fontSize: 13, background: 'var(--paper-2)',
            color: 'var(--ink)', outline: 'none',
          }}
        />
      )}
    </div>
  );

  const loadingKeys = ['tools.schema.v2.loading.1', 'tools.schema.v2.loading.2', 'tools.schema.v2.loading.3'];

  const cardStyle = {
    background: 'var(--paper)',
    border: '2px solid var(--ink)',
    borderRadius: 16,
    boxShadow: '4px 4px 0 var(--ink)',
    padding: 24,
  };

  const sectionHdr = (label) => (
    <div className="mono" style={{ fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.14em', color: 'var(--ink-3)', marginBottom: 12, paddingBottom: 8, borderBottom: '1px solid var(--ink-3)' }}>
      {label}
    </div>
  );

  const wizardQ = [
    {
      key: 'q1',
      opts: ['article', 'product', 'faq', 'recipe'],
      labelKey: 'tools.schema.v2.wizard.q1.label',
      optPrefix: 'tools.schema.v2.wizard.q1.opt.',
    },
    {
      key: 'q2',
      opts: ['single', 'list'],
      labelKey: 'tools.schema.v2.wizard.q2.label',
      optPrefix: 'tools.schema.v2.wizard.q2.opt.',
    },
    {
      key: 'q3',
      opts: ['reviews', 'faq', 'steps', 'none'],
      labelKey: 'tools.schema.v2.wizard.q3.label',
      optPrefix: 'tools.schema.v2.wizard.q3.opt.',
    },
  ];

  const currentQ = wizardQ[wizardStep - 1];

  const treeNodes = [];
  if (enabledTypes.has('Article')) {
    let lines = '▸ Article';
    if (enabledTypes.has('Organization')) lines += '\n  └─ publisher → Organization #org';
    if (fields.image) lines += '\n  └─ image → ImageObject';
    treeNodes.push(lines);
  }
  if (enabledTypes.has('Organization')) treeNodes.push('▸ Organization #org\n  └─ logo → ImageObject');
  if (enabledTypes.has('BreadcrumbList')) treeNodes.push('▸ BreadcrumbList');
  if (enabledTypes.has('FAQPage')) treeNodes.push('▸ FAQPage');
  if (enabledTypes.has('SpeakableSpecification')) treeNodes.push('▸ WebPage (Speakable)');
  if (enabledTypes.has('ClaimReview')) treeNodes.push('▸ ClaimReview');

  const faqItems = [
    { q: t('tools.schema.v2.faq.q1'), a: t('tools.schema.v2.faq.a1') },
    { q: t('tools.schema.v2.faq.q2'), a: t('tools.schema.v2.faq.a2') },
    { q: t('tools.schema.v2.faq.q3'), a: t('tools.schema.v2.faq.a3') },
  ];

  return (
    <div style={{ color: 'var(--ink)' }}>
      {/* ── HERO ── */}
      <section style={{ padding: isMobile ? '40px 20px 24px' : '64px 56px 32px', borderBottom: '4px solid var(--ink)', background: 'var(--paper)' }}>
        <button
          onClick={() => navigate('/tools')}
          className="mono"
          style={{ background: 'transparent', border: 0, padding: 0, cursor: 'pointer', color: 'var(--ink-2)', fontSize: 12, marginBottom: 14 }}
        >
          ← {t('tools.back')}
        </button>
        <Reveal>
          <span className="stamp" style={{ background: resolvedAccent, borderColor: 'var(--ink)', transform: 'rotate(-0.3deg)', display: 'inline-block' }}>
            FREE · SCHEMA GENERATOR
          </span>
        </Reveal>
        <Reveal delay={80}>
          <h1 className="serif" style={{ fontSize: 'clamp(40px, 6vw, 88px)', lineHeight: 0.94, letterSpacing: '-0.025em', margin: '16px 0 14px', maxWidth: 820, textWrap: 'balance' }}>
            {t('tools.schema.v2.hero.title.a')}{' '}
            <span className="ital hl">{t('tools.schema.v2.hero.title.hl')}</span>
            {t('tools.schema.v2.hero.title.b') ? ' ' + t('tools.schema.v2.hero.title.b') : ''}
          </h1>
        </Reveal>
        <Reveal delay={180}>
          <p style={{ fontSize: 17, lineHeight: 1.55, color: 'var(--ink-2)', maxWidth: 680, margin: '0 0 20px' }}>
            {t('tools.schema.v2.hero.sub')}
          </p>
          <p className="mono" style={{ fontSize: 12, letterSpacing: '0.06em', color: 'var(--ink-3)' }}>
            {t('tools.schema.v2.hero.trust')}
          </p>
        </Reveal>
      </section>

      {/* ── URL AUTOFILL ── */}
      <section style={{ padding: isMobile ? '28px 20px' : '40px 56px', background: 'var(--paper-2)', borderBottom: '2px solid var(--ink)' }}>
        <form onSubmit={handleAutofill} style={{ display: 'flex', flexWrap: isMobile ? 'wrap' : 'nowrap', gap: 10, maxWidth: 820 }}>
          <input
            type="url"
            value={urlInput}
            onChange={(e) => setUrlInput(e.target.value)}
            placeholder={t('tools.schema.v2.input.placeholder')}
            style={{
              flex: 1, minWidth: isMobile ? '100%' : 320,
              padding: '13px 16px', border: '2px solid var(--ink)', borderRadius: 10,
              fontFamily: 'var(--sans)', fontSize: 14, background: 'var(--paper)',
              color: 'var(--ink)', outline: 'none', boxSizing: 'border-box',
            }}
          />
          <button
            type="submit"
            disabled={loading}
            style={{
              background: resolvedAccent, color: 'var(--ink)', border: '2px solid var(--ink)',
              padding: '13px 24px', borderRadius: 10, cursor: loading ? 'default' : 'pointer',
              fontFamily: 'var(--sans)', fontSize: 13, fontWeight: 700,
              boxShadow: '3px 3px 0 var(--ink)', whiteSpace: 'nowrap', minHeight: 48,
            }}
          >
            {loading ? t(loadingKeys[loadingStep]) : t('tools.schema.v2.input.cta')}
          </button>
        </form>
        {autofillError && (
          <div style={{ margin: '10px 0 0', color: '#c0392b', fontSize: 13, maxWidth: 820, padding: '8px 12px', border: '2px solid #c0392b', borderRadius: 8, background: '#fff5f5' }}>
            {autofillError}
          </div>
        )}
        <div style={{ margin: '20px 0', color: 'var(--ink-3)', fontSize: 13, maxWidth: 820, textAlign: 'center' }}>
          {t('tools.schema.v2.input.or')}
        </div>

        {/* TYPE BUNDLE CHECKBOXES */}
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 10, maxWidth: 820, marginBottom: 12 }}>
          {BUNDLE_TYPES.map((bt) => {
            const active = enabledTypes.has(bt.id);
            return (
              <button
                key={bt.id}
                onClick={() => toggleType(bt.id)}
                style={{
                  padding: '9px 14px', border: '2px solid var(--ink)', borderRadius: 10,
                  background: active ? 'var(--ink)' : 'var(--paper)',
                  color: active ? resolvedAccent : 'var(--ink)',
                  fontFamily: 'var(--sans)', fontSize: 12, fontWeight: 600,
                  boxShadow: active ? 'none' : '3px 3px 0 var(--ink)',
                  transform: active ? 'translate(3px,3px)' : 'none',
                  cursor: 'pointer', transition: 'transform .1s, background .12s',
                }}
              >
                {active ? '✓ ' : ''}{bt.label}
              </button>
            );
          })}
          {!showWizard && (
            <button
              onClick={() => { setShowWizard(true); setWizardStep(1); setWizardAnswers({}); }}
              style={{ padding: '9px 14px', border: '2px dashed var(--ink)', borderRadius: 10, background: 'var(--paper)', color: 'var(--ink-2)', fontFamily: 'var(--sans)', fontSize: 12, cursor: 'pointer' }}
            >
              Use Wizard ↺
            </button>
          )}
        </div>
      </section>

      {/* ── WIZARD ── */}
      {showWizard && (
        <section style={{ padding: isMobile ? '28px 20px' : '40px 56px', borderBottom: '2px solid var(--ink)', background: 'var(--paper)' }}>
          <Reveal>
            <div style={{ ...cardStyle, maxWidth: 720, transform: 'rotate(0.2deg)' }}>
              <div className="mono" style={{ fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.14em', color: 'var(--ink-3)', marginBottom: 10 }}>
                Step {wizardStep} of 3
              </div>
              <h2 className="serif" style={{ fontSize: 'clamp(22px, 3vw, 34px)', lineHeight: 1.1, marginBottom: 20 }}>
                {t(currentQ.labelKey)}
              </h2>
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 10 }}>
                {currentQ.opts.map((opt) => (
                  <button
                    key={opt}
                    onClick={() => handleWizardAnswer(currentQ.key, opt)}
                    style={{
                      padding: '12px 20px', border: '2px solid var(--ink)', borderRadius: 10,
                      background: 'var(--paper-2)', color: 'var(--ink)',
                      fontFamily: 'var(--sans)', fontSize: 13, fontWeight: 600,
                      boxShadow: '3px 3px 0 var(--ink)', cursor: 'pointer',
                    }}
                  >
                    {t(currentQ.optPrefix + opt)}
                  </button>
                ))}
              </div>
            </div>
          </Reveal>
        </section>
      )}

      {/* ── TWO-PANE LAYOUT ── */}
      <section style={{ padding: isMobile ? '28px 20px 48px' : '40px 56px 80px', background: 'var(--paper-2)' }}>
        <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: 20, alignItems: 'start' }}>

          {/* LEFT: Field Editor */}
          <div style={{ ...cardStyle, transform: 'rotate(-0.2deg)' }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20 }}>
              <div className="mono" style={{ fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.14em', color: 'var(--ink-3)' }}>
                {t('tools.schema.v2.bundle.label')}
              </div>
              <button
                onClick={() => setShowImport(true)}
                style={{ background: 'none', border: '1px solid var(--ink-3)', borderRadius: 6, padding: '4px 10px', cursor: 'pointer', fontFamily: 'var(--mono)', fontSize: 10, color: 'var(--ink-2)' }}
              >
                {t('tools.schema.v2.import.cta')}
              </button>
            </div>

            {enabledTypes.size === 0 && (
              <div style={{ textAlign: 'center', padding: '32px 0', opacity: 0.45 }}>
                <p style={{ fontFamily: 'var(--mono)', fontSize: 12 }}>Select types above or complete the wizard to begin.</p>
              </div>
            )}

            {enabledTypes.has('Article') && (
              <div style={{ marginBottom: 24 }}>
                {sectionHdr('Article')}
                {renderField('headline')}
                {renderField('author')}
                {renderField('datePublished', 'Date Published', 'date')}
                {renderField('image', 'Image URL')}
                {renderField('url', 'Page URL')}
              </div>
            )}

            {enabledTypes.has('FAQPage') && (
              <div style={{ marginBottom: 24 }}>
                {sectionHdr('FAQPage')}
                <div className="mono" style={{ fontSize: 10, color: 'var(--ink-3)', marginBottom: 6 }}>Question|Answer — one per line</div>
                {renderField('faqs', 'FAQs', 'text', true)}
              </div>
            )}

            {enabledTypes.has('BreadcrumbList') && (
              <div style={{ marginBottom: 24 }}>
                {sectionHdr('BreadcrumbList')}
                <div className="mono" style={{ fontSize: 10, color: 'var(--ink-3)', marginBottom: 6 }}>Name|URL — one per line</div>
                {renderField('breadcrumbs', 'Breadcrumbs', 'text', true)}
              </div>
            )}

            {enabledTypes.has('Organization') && (
              <div style={{ marginBottom: 24 }}>
                {sectionHdr('Organization')}
                {renderField('orgName', 'Org Name')}
                {renderField('orgUrl', 'Org URL')}
                {renderField('orgLogo', 'Org Logo URL')}
              </div>
            )}

            {enabledTypes.has('SpeakableSpecification') && (
              <div style={{ marginBottom: 24 }}>
                {sectionHdr('SpeakableSpecification')}
                {renderField('speakableCss', 'CSS Selector')}
                {renderField('speakableXpath', 'XPath')}
              </div>
            )}

            {enabledTypes.has('ClaimReview') && (
              <div style={{ marginBottom: 24 }}>
                {sectionHdr('ClaimReview')}
                {renderField('claimText', 'Claim Text', 'text', true)}
                {renderField('claimRating', 'Rating (true/false/etc)')}
                {renderField('claimUrl', 'Claim URL')}
                {renderField('claimAuthor', 'Claim Author')}
              </div>
            )}
          </div>

          {/* RIGHT: Tab Panels */}
          <div style={{ position: isMobile ? 'static' : 'sticky', top: 24 }}>
            {/* Tab strip */}
            <div style={{ display: 'flex', gap: 0, marginBottom: -2, zIndex: 2, position: 'relative' }}>
              {[
                { id: 'jsonld', label: t('tools.schema.v2.pane.json') },
                { id: 'serp', label: t('tools.schema.v2.pane.serp') },
                { id: 'ai', label: t('tools.schema.v2.pane.ai') },
              ].map((tab) => {
                const active = activeTab === tab.id;
                return (
                  <button
                    key={tab.id}
                    onClick={() => setActiveTab(tab.id)}
                    style={{
                      padding: '10px 18px',
                      border: '2px solid var(--ink)',
                      borderBottom: active ? '2px solid var(--paper)' : '2px solid var(--ink)',
                      borderRadius: '10px 10px 0 0',
                      background: active ? 'var(--paper)' : 'var(--paper-2)',
                      fontFamily: 'var(--mono)', fontSize: 10, fontWeight: 700,
                      letterSpacing: '0.1em', textTransform: 'uppercase',
                      cursor: 'pointer', color: 'var(--ink)',
                    }}
                  >
                    {tab.label}
                  </button>
                );
              })}
            </div>

            <div style={{ background: 'var(--paper)', border: '2px solid var(--ink)', borderRadius: '0 16px 16px 16px', padding: 24, boxShadow: '4px 4px 0 var(--ink)', minHeight: 420 }}>

              {/* JSON-LD TAB */}
              {activeTab === 'jsonld' && (
                <div>
                  {treeNodes.length >= 2 && (
                    <div style={{ marginBottom: 16 }}>
                      <div className="mono" style={{ fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.12em', color: 'var(--ink-3)', marginBottom: 6 }}>
                        {t('tools.schema.v2.tree.label')}
                      </div>
                      <pre style={{ fontFamily: 'var(--mono)', fontSize: 11, color: 'var(--ink-2)', background: 'var(--paper-2)', padding: 12, borderRadius: 8, border: '1px solid var(--ink-3)', margin: 0, lineHeight: 1.8 }}>
                        {treeNodes.join('\n')}
                      </pre>
                    </div>
                  )}
                  <pre style={{ fontFamily: '"JetBrains Mono", monospace', fontSize: 12, lineHeight: 1.6, background: '#1a1a1a', color: '#e8e6e0', padding: 20, borderRadius: 10, overflowX: 'auto', maxHeight: 440, margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-all' }}>
                    {JSON.stringify(generatedJson, null, 2)}
                  </pre>
                  <div style={{ display: 'flex', gap: 10, marginTop: 16 }}>
                    <button
                      onClick={handleCopy}
                      style={{ flex: 1, background: resolvedAccent, color: 'var(--ink)', border: '2px solid var(--ink)', padding: '11px 0', borderRadius: 10, fontFamily: 'var(--mono)', fontSize: 11, fontWeight: 700, letterSpacing: '0.08em', cursor: 'pointer', boxShadow: '3px 3px 0 var(--ink)' }}
                    >
                      {copied ? t('tools.schema.v2.action.copied') : t('tools.schema.v2.action.copy')}
                    </button>
                    <button
                      onClick={handleDownload}
                      style={{ flex: 1, background: 'var(--paper-2)', color: 'var(--ink)', border: '2px solid var(--ink)', padding: '11px 0', borderRadius: 10, fontFamily: 'var(--mono)', fontSize: 11, fontWeight: 700, letterSpacing: '0.08em', cursor: 'pointer', boxShadow: '3px 3px 0 var(--ink)' }}
                    >
                      {t('tools.schema.v2.action.download')}
                    </button>
                    <button
                      onClick={handleSaveShare}
                      style={{ flex: 1, background: '#b5f23d', color: 'var(--ink)', border: '2px solid var(--ink)', padding: '11px 0', borderRadius: 10, fontFamily: 'var(--mono)', fontSize: 11, fontWeight: 700, letterSpacing: '0.08em', cursor: 'pointer', boxShadow: '3px 3px 0 var(--ink)' }}
                    >
                      {shareToast ? 'Link copied!' : 'Save & Share'}
                    </button>
                  </div>
                </div>
              )}

              {/* SERP PREVIEW TAB */}
              {activeTab === 'serp' && (
                <div>
                  <div className="mono" style={{ fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.12em', color: 'var(--ink-3)', marginBottom: 16 }}>
                    Google Rich Result Preview (simulated)
                  </div>
                  <div style={{ fontFamily: 'arial, sans-serif', border: '1px solid #dfe1e5', borderRadius: 8, padding: '16px 20px', background: '#fff', color: '#202124' }}>
                    <div style={{ fontSize: 12, color: '#5f6368', marginBottom: 4 }}>
                      {fields.url || 'https://example.com'} › {fields.breadcrumbs ? fields.breadcrumbs.split('\n')[0].split('|')[0] : 'Blog'}
                    </div>
                    <div style={{ fontSize: 18, color: '#1a0dab', cursor: 'pointer', marginBottom: 4, fontWeight: 400 }}>
                      {fields.headline || 'Your Article Headline'}
                    </div>
                    <div style={{ fontSize: 13, color: '#5f6368', lineHeight: 1.5 }}>
                      {fields.datePublished && <span style={{ fontWeight: 600 }}>{fields.datePublished} — </span>}
                      This is how your snippet may appear in Google search results.
                    </div>
                    {enabledTypes.has('FAQPage') && fields.faqs && (
                      <div style={{ marginTop: 12, borderTop: '1px solid #ebebeb', paddingTop: 10 }}>
                        {fields.faqs.split('\n').filter(Boolean).slice(0, 2).map((line, i) => {
                          const [q] = line.split('|');
                          return (
                            <div key={i} style={{ fontSize: 13, color: '#1a0dab', padding: '4px 0', borderBottom: '1px solid #ebebeb', cursor: 'pointer' }}>
                              ▸ {q}
                            </div>
                          );
                        })}
                      </div>
                    )}
                    {enabledTypes.has('BreadcrumbList') && fields.breadcrumbs && (
                      <div style={{ marginTop: 10, fontSize: 11, color: '#5f6368' }}>
                        {fields.breadcrumbs.split('\n').filter(Boolean).map((line) => line.split('|')[0]).join(' › ')}
                      </div>
                    )}
                  </div>
                </div>
              )}

              {/* AI CITATION PREVIEW TAB */}
              {activeTab === 'ai' && (
                <div>
                  <div className="mono" style={{ fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.12em', color: 'var(--ink-3)', marginBottom: 16 }}>
                    Perplexity Citation Preview (simulated)
                  </div>
                  <div style={{ border: '1px solid #20808d', borderRadius: 10, padding: '14px 18px', background: '#f0fafa' }}>
                    <div style={{ fontSize: 11, color: '#20808d', fontWeight: 700, marginBottom: 8, letterSpacing: '0.06em', textTransform: 'uppercase' }}>
                      Perplexity · Source
                    </div>
                    <div style={{ fontSize: 15, fontWeight: 600, color: '#1a2332', marginBottom: 6 }}>
                      {fields.headline || 'Your Article Title'}
                    </div>
                    <div style={{ fontSize: 12, color: '#4a5568', marginBottom: 8 }}>
                      {fields.url || 'https://example.com'}
                      {fields.author && ` · ${fields.author}`}
                      {fields.datePublished && ` · ${fields.datePublished}`}
                    </div>
                    {enabledTypes.has('FAQPage') && fields.faqs && (
                      <div style={{ borderTop: '1px solid #cce8ea', paddingTop: 8, marginTop: 4 }}>
                        <div style={{ fontSize: 11, color: '#20808d', marginBottom: 4 }}>FAQ content detected:</div>
                        {fields.faqs.split('\n').filter(Boolean).slice(0, 2).map((line, i) => {
                          const [q, a] = line.split('|');
                          return (
                            <div key={i} style={{ fontSize: 12, color: '#4a5568', marginBottom: 4 }}>
                              <strong>Q: </strong>{q} {a && <span>— {a}</span>}
                            </div>
                          );
                        })}
                      </div>
                    )}
                    <div style={{ marginTop: 10, fontSize: 11, color: '#888', fontStyle: 'italic' }}>
                      Schema validates ✓ — strong citability signal
                    </div>
                  </div>
                </div>
              )}
            </div>
          </div>
        </div>
      </section>

      {/* ── IMPORT MODAL ── */}
      {showImport && (
        <div style={{ position: 'fixed', inset: 0, background: 'rgba(14,17,22,0.7)', zIndex: 200, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20 }}>
          <div style={{ ...cardStyle, width: '100%', maxWidth: 600, transform: 'rotate(0.3deg)' }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
              <div className="mono" style={{ fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.14em' }}>{t('tools.schema.v2.import.cta')}</div>
              <button onClick={() => { setShowImport(false); setImportError(null); setImportText(''); }} style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 18, color: 'var(--ink)' }}>×</button>
            </div>
            <textarea
              rows={10}
              value={importText}
              onChange={(e) => { setImportText(e.target.value); setImportError(null); }}
              placeholder={t('tools.schema.v2.import.placeholder')}
              style={{ width: '100%', boxSizing: 'border-box', padding: '12px', border: '2px solid var(--ink)', borderRadius: 8, fontFamily: 'var(--mono)', fontSize: 12, background: 'var(--paper-2)', color: 'var(--ink)', outline: 'none', resize: 'vertical' }}
            />
            {importError && (
              <div style={{ marginTop: 8, color: '#c0392b', fontFamily: 'var(--mono)', fontSize: 12 }}>{t('tools.schema.v2.import.error')} — {importError}</div>
            )}
            {importSuccess && (
              <div style={{ marginTop: 8, color: '#27ae60', fontFamily: 'var(--mono)', fontSize: 12 }}>{t('tools.schema.v2.import.success')}</div>
            )}
            <button
              onClick={handleImport}
              style={{ marginTop: 14, width: '100%', background: resolvedAccent, color: 'var(--ink)', border: '2px solid var(--ink)', padding: '12px', borderRadius: 10, fontFamily: 'var(--mono)', fontSize: 12, fontWeight: 700, cursor: 'pointer', boxShadow: '3px 3px 0 var(--ink)' }}
            >
              Import & Populate Fields
            </button>
          </div>
        </div>
      )}

      {/* ── CROSS-PROMO ── */}
      <section style={{ padding: isMobile ? '32px 20px' : '56px 56px', background: 'var(--paper)', borderTop: '4px solid var(--ink)' }}>
        <div className="mono" style={{ fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.14em', color: 'var(--ink-3)', marginBottom: 20 }}>
          {t('tools.schema.v2.crosspromo.label')}
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : 'repeat(3, 1fr)', gap: 14 }}>
          {[
            { slug: 'aeo-checker', label: 'AEO Checker', icon: '◎' },
            { slug: 'llms-txt-generator', label: 'llms.txt Generator', icon: '⊕' },
            { slug: 'citation-lookup', label: 'AI Citation Lookup', icon: '◉' },
          ].map((tool) => (
            <button
              key={tool.slug}
              onClick={() => navigate('/tools/' + tool.slug)}
              style={{ display: 'flex', alignItems: 'center', gap: 12, background: 'var(--paper-2)', border: '2px solid var(--ink)', borderRadius: 12, padding: '14px 18px', cursor: 'pointer', boxShadow: '3px 3px 0 var(--ink)', textAlign: 'left', transform: 'rotate(0.2deg)' }}
            >
              <span style={{ fontSize: 20 }}>{tool.icon}</span>
              <span style={{ fontFamily: 'var(--sans)', fontSize: 13, fontWeight: 600, color: 'var(--ink)' }}>{tool.label}</span>
            </button>
          ))}
        </div>
      </section>

      {/* ── FAQ ── */}
      <section style={{ padding: isMobile ? '32px 20px' : '56px 56px', background: 'var(--paper-2)', borderTop: '2px solid var(--ink)' }}>
        <h2 className="serif" style={{ fontSize: 'clamp(28px, 4vw, 52px)', lineHeight: 1.0, letterSpacing: '-0.02em', marginBottom: 28 }}>
          {t('tools.schema.v2.faq.title')}
        </h2>
        <div style={{ maxWidth: 760, display: 'grid', gap: 8 }}>
          {faqItems.map((item, i) => (
            <div key={i} style={{ border: '2px solid var(--ink)', borderRadius: 12, overflow: 'hidden', background: 'var(--paper)', transform: i % 2 === 0 ? 'rotate(0.1deg)' : 'rotate(-0.1deg)' }}>
              <button
                onClick={() => setFaqOpen((prev) => ({ ...prev, [i]: !prev[i] }))}
                style={{ width: '100%', display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '16px 20px', background: 'none', border: 'none', cursor: 'pointer', textAlign: 'left' }}
              >
                <span style={{ fontFamily: 'var(--sans)', fontSize: 14, fontWeight: 700, color: 'var(--ink)', flex: 1 }}>{item.q}</span>
                <span style={{ fontFamily: 'var(--mono)', fontSize: 16, color: 'var(--ink-2)', marginLeft: 12 }}>{faqOpen[i] ? '−' : '+'}</span>
              </button>
              {faqOpen[i] && (
                <div style={{ padding: '0 20px 16px', fontSize: 14, lineHeight: 1.65, color: 'var(--ink-2)', borderTop: '1px solid var(--ink-3)' }}>
                  {item.a}
                </div>
              )}
            </div>
          ))}
        </div>
      </section>

      {/* ── CLOSING CTA ── */}
      <section style={{ padding: isMobile ? '40px 20px' : '72px 56px', background: 'var(--paper-2)', borderTop: '4px solid var(--ink)', borderBottom: '4px solid var(--ink)' }}>
        <div style={{ maxWidth: 720 }}>
          <h2 className="serif" style={{ fontSize: 'clamp(28px, 4vw, 56px)', lineHeight: 0.98, letterSpacing: '-0.022em', margin: '0 0 16px' }}>
            {t('tools.schema.v2.cta.title')}
          </h2>
          <p style={{ fontSize: 16, lineHeight: 1.55, color: 'var(--ink-2)', margin: '0 0 28px', maxWidth: 560 }}>
            {t('tools.schema.v2.cta.body')}
          </p>
          <button
            onClick={() => navigate('/start')}
            className="btn btn-accent"
            style={{ display: 'inline-flex', alignItems: 'center', gap: 10, padding: '15px 24px', minHeight: 50 }}
          >
            <span>{t('tools.schema.v2.cta.btn')}</span>
            <span className="arrow">→</span>
          </button>
        </div>
      </section>
    </div>
  );
}

Object.assign(window, { SchemaGeneratorV2 });
