/* WPSiteBeam Scanner — Pages Tab
   Auto-split from Scanner.jsx.
*/
(function () {
  'use strict';
  const ScanEmptyState = window.ScanEmptyState;
  const Icon = window.Icon;

function ScannerPagesTab({ data }) {
  const { useState, useMemo } = React;

  /* ⚠️ All hooks MUST be called unconditionally at the top, before any
     early returns. React relies on the order of hook calls between renders
     to be stable. Calling useState after an `if (...) return <Empty/>` works
     on first render but crashes when the condition flips on a later render
     (e.g. when scan completes and pagesList populates) because hook order
     changes. Keep hooks first, then do conditional returns. */
  const [selected, setSelected] = useState(0);
  const [zipping, setZipping] = useState(false);
  const [filter, setFilter] = useState('');      // free-text search
  const [compact, setCompact] = useState(false); // dense row layout
  const [collapsed, setCollapsed] = useState({}); // {pathPrefix: true/false}

  /* BULLETPROOF against `data` being null/undefined entirely.
     If an older deployed bundle or a partial render passes no data prop,
     every downstream read falls back to a safe empty shape rather than
     throwing. This prevents the "Cannot read properties of undefined"
     crash class from ever reaching the error boundary. */
  const safeData = data || {};
  const pagesList = Array.isArray(safeData.pagesList) ? safeData.pagesList : [];
  const totalDiscovered = safeData.pageFetchTotal || pagesList.length;
  const wasLimited = !!safeData.pageFetchLimited;

  /* ── Sidebar grouping by URL path prefix ──────────────────────────
     For large sites, a flat list of 100+ pages in the sidebar is
     unnavigable. Group by first path segment so the sidebar becomes a
     collapsible tree:
         / (root)       — homepage, about, contact
         /blog/*        — blog posts
         /services/*    — service pages
         /products/*    — product pages
     Filter text applies AFTER grouping — typing in the search box narrows
     each group, and groups with zero matches hide entirely.

     v1.3.8 patch h6: replaced flat first-segment grouping with a true
     tree structure. Output shape:
       [
         {
           section: 'Pages' | 'Blog',
           nodes: [
             {
               page: {...} | null,          // null for path nodes with no page
               children: [...],             // recursive
               path: '/about',              // full pathname
               label: 'About',              // segment label
             },
             ...
           ]
         },
         ...
       ]
     Blog detection: any page whose URL pathname starts with one of
     /blog, /post, /posts, /news, /articles, /category, /tag, /author
     goes into the Blog section. Everything else into Pages. Sections
     sorted A-Z internally. Home (/) lives at the top of Pages.

     DEFENSIVE: Wrap entire computation in try/catch — malformed pagesList
     entries must never crash the whole tab. Fallback = empty section list. */
  const groupedPages = useMemo(() => {
    try {
      if (!Array.isArray(pagesList) || pagesList.length === 0) return [];

      const normalizedFilter = (filter || '').trim().toLowerCase();
      const BLOG_RE = /^\/(blog|post|posts|news|article|articles|category|categories|tag|tags|author|authors)(\/|$)/i;

      /* Step 1: collect filtered, indexed pages with their parsed paths */
      const items = [];
      pagesList.forEach((p, idx) => {
        if (!p || typeof p !== 'object') return;
        if (normalizedFilter) {
          const hay = [p.label, p.url, p.title, p.description].filter(Boolean).join(' ').toLowerCase();
          if (!hay.includes(normalizedFilter)) return;
        }
        let pathname = '/';
        try { pathname = new URL(p.url).pathname || '/'; } catch { pathname = '/'; }
        /* Strip trailing slash for consistent keys (except root) */
        if (pathname.length > 1 && pathname.endsWith('/')) pathname = pathname.slice(0, -1);
        items.push({
          page: { ...p, _originalIndex: idx },
          path: pathname,
          isBlog: BLOG_RE.test(pathname),
          isHome: pathname === '/' || pathname === '',
        });
      });
      if (items.length === 0) return [];

      /* Step 2: build a tree from path segments. Each node tracks its
         path, label, attached page (if any), and children. Nodes are
         created lazily when a deeper page references a missing parent
         path — so /services/web-design creates nodes for both /services
         AND /services/web-design even when /services itself wasn't
         walked. The intermediate gets page=null and renders as a
         non-clickable group label. */
      function buildTree(group) {
        const root = { children: {}, path: '', label: '', page: null };
        for (const item of group) {
          const segs = item.path.split('/').filter(Boolean);
          if (segs.length === 0) {
            /* Root / homepage */
            root.page = item.page;
            root.path = '/';
            root.label = item.page.label || 'Home';
            continue;
          }
          let cursor = root;
          let accumulated = '';
          for (let i = 0; i < segs.length; i++) {
            accumulated += '/' + segs[i];
            if (!cursor.children[segs[i]]) {
              cursor.children[segs[i]] = {
                children: {},
                path: accumulated,
                /* humanize segment for label: kebab-case → Title Case */
                label: segs[i].replace(/[-_]+/g, ' ').replace(/\b\w/g, c => c.toUpperCase()),
                page: null,
              };
            }
            cursor = cursor.children[segs[i]];
            if (i === segs.length - 1) {
              /* Leaf — attach the page. If the page has a richer label
                 (h1 or page title), prefer it over the humanized slug. */
              cursor.page = item.page;
              if (item.page.label && item.page.label.length < 60) {
                cursor.label = item.page.label;
              }
            }
          }
        }
        /* Recursive sort: each node's children sorted A-Z by label */
        function toArray(node) {
          const kids = Object.values(node.children).map(toArray);
          kids.sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: 'base' }));
          return {
            page: node.page,
            path: node.path,
            label: node.label,
            children: kids,
          };
        }
        const sorted = Object.values(root.children).map(toArray);
        sorted.sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: 'base' }));
        /* If root itself has a page (homepage), return it as the first
           top-level node so users can select it. */
        const out = [];
        if (root.page) {
          out.push({ page: root.page, path: '/', label: root.label || 'Home', children: [] });
        }
        return out.concat(sorted);
      }

      /* Step 3: split by section, build a tree per section */
      const blogItems  = items.filter(i => i.isBlog);
      const pageItems  = items.filter(i => !i.isBlog);
      const sections = [];
      if (pageItems.length > 0) sections.push({ section: 'Pages', nodes: buildTree(pageItems) });
      if (blogItems.length > 0) sections.push({ section: 'Blog',  nodes: buildTree(blogItems) });
      return sections;
    } catch (err) {
      if (typeof console !== 'undefined') console.warn('[PagesTab] groupedPages failed:', err);
      return [];
    }
  }, [pagesList, filter]);

  /* Filtered-flat count: walk the tree and count nodes with attached pages.
     Used for "X / Y matches" display. */
  function countNodes(nodes) {
    let n = 0;
    for (const node of nodes) {
      if (node.page) n++;
      if (node.children && node.children.length) n += countNodes(node.children);
    }
    return n;
  }
  const filteredCount = groupedPages.reduce((acc, sec) => acc + countNodes(sec.nodes || []), 0);

  /* Content block splitter — useMemo must run before any early return too */
  const page = pagesList[selected] || pagesList[0] || null;
  const contentBlocks = useMemo(() => {
    const raw = ((page && page.content) || '').trim();
    if (!raw) return [];
    const parts = raw.split(/\n\n(?=\[TABLE DATA\])/);
    return parts.map((p, idx) => {
      if (p.startsWith('[TABLE DATA]')) {
        const rows = p.replace('[TABLE DATA]', '').trim().split('\n').filter(Boolean)
          .map(row => row.split(' | ').map(c => c.trim()));
        return { kind: 'table', rows, key: `t${idx}` };
      }
      return { kind: 'prose', text: p, key: `p${idx}` };
    });
  }, [page]);

  /* Empty-state: no per-page content captured.
     Could mean scan failed, Railway /brain/scan/test endpoint unreachable,
     security plugin blocked the scan, or no URLs were discoverable. */
  if (pagesList.length === 0 || !page) {
    return <ScanEmptyState
      tab="pages"
      reason="Per-page content (menu, title, description, headings, body text, images) is captured by fetching each discovered URL. No pages were successfully fetched. Common causes: (1) Security plugin or WAF blocking the scanner — Wordfence, Sucuri, iThemes Security, Cloudflare bot protection, etc. can block server-to-server requests; try temporarily disabling or whitelisting WPSiteBeam IPs for the scan. (2) Site uses JavaScript-only rendering. (3) Sub-pages couldn't be reached. (4) The site has no crawlable URLs. The Sitemap tab should still show the URL list if it was discoverable."
    />;
  }

  /* ── Per-page ZIP download (this page's images only) ──────── */
  const RAILWAY = (typeof window !== 'undefined' && window.WPSB_API)
    || 'https://wpsitebeam-railway-api-production.up.railway.app';

  function proxyUrl(src, forDownload = false, fn = null) {
    const params = new URLSearchParams({ url: src });
    if (forDownload) params.set('download', '1');
    if (fn) params.set('filename', fn);
    return `${RAILWAY}/proxy/image?${params.toString()}`;
  }

  async function downloadPageImagesAsZip() {
    if (zipping) return;
    const imgs = page.images || [];
    if (imgs.length === 0) {
      window.wpsbToast?.('No images on this page', 'warn');
      return;
    }
    setZipping(true);
    try {
      if (typeof window.JSZip === 'undefined') {
        await new Promise((resolve, reject) => {
          const s = document.createElement('script');
          s.src = 'https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js';
          s.onload = resolve;
          s.onerror = () => reject(new Error('JSZip CDN failed'));
          document.head.appendChild(s);
        });
      }
      const JSZip = window.JSZip;
      const host = (safeData.site || 'site').replace(/[^a-z0-9.-]/gi, '_');
      const pageSlug = (page.label || 'page').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
      const zip = new JSZip();
      const folder = zip.folder(`${host}-${pageSlug}-images`);
      let fetched = 0;
      await Promise.all(imgs.map(async (img) => {
        const fn = (img.filename || `img-${img.id || 0}.${img.ext || 'jpg'}`).replace(/[<>:"|?*]/g, '_');
        try {
          const r = await fetch(proxyUrl(img.src), { mode: 'cors' });
          if (!r.ok) return;
          const blob = await r.blob();
          if (blob.size > 0) {
            folder.file(fn, blob);
            fetched++;
          }
        } catch (e) { /* skip */ }
      }));
      const zipBlob = await zip.generateAsync({
        type: 'blob', compression: 'DEFLATE', compressionOptions: { level: 6 },
      });
      const a = document.createElement('a');
      a.href = URL.createObjectURL(zipBlob);
      a.download = `${host}-${pageSlug}-images.zip`;
      a.style.display = 'none';
      document.body.appendChild(a);
      a.click();
      setTimeout(() => {
        URL.revokeObjectURL(a.href);
        document.body.removeChild(a);
      }, 2000);
      window.wpsbToast?.(`Downloaded ${fetched}/${imgs.length} images`, 'ok');
    } catch (e) {
      window.wpsbToast?.(`ZIP error: ${e.message}`, 'warn');
    } finally {
      setZipping(false);
    }
  }

  function downloadOneImage(img) {
    const fn = (img.filename || `image.${img.ext || 'jpg'}`).replace(/[<>:"|?*]/g, '_');
    const url = proxyUrl(img.src, true, fn);
    const a = document.createElement('a');
    a.href = url;
    a.download = fn;
    a.rel = 'noopener';
    document.body.appendChild(a);
    a.click();
    setTimeout(() => document.body.removeChild(a), 200);
    window.wpsbToast?.(`Saving ${fn}`, 'ok');
  }

  /* ── Per-page JSON export (migration bundle) ───────────────── */
  function downloadPageJson() {
    const bundle = {
      url: page.url,
      label: page.label,
      menu_name: page.menu_name,
      title: page.title,
      description: page.description,
      meta: page.meta,
      headings: page.headings,
      content: page.content,
      images: (page.images || []).map(i => ({
        src: i.src, alt: i.alt, width: i.width, height: i.height,
        type: i.type, filename: i.filename,
      })),
      stats: {
        content_length: page.content_length,
        image_count: page.image_count,
        heading_count: page.heading_count,
      },
    };
    const slug = (page.label || 'page').toLowerCase().replace(/[^a-z0-9]+/g, '-');
    window.wpsbDownload?.(`${safeData.site}-${slug}.json`, JSON.stringify(bundle, null, 2), 'application/json');
  }

  /* ── Global migration export: ALL pages as single JSON ─────── */
  function downloadAllPagesJson() {
    const bundle = {
      site: safeData.site,
      site_url: safeData.siteUrl,
      scanned_at: safeData.scannedAtISO,
      page_count: pagesList.length,
      pages: pagesList.map(p => ({
        url: p.url,
        label: p.label,
        menu_name: p.menu_name,
        title: p.title,
        description: p.description,
        meta: p.meta,
        headings: p.headings,
        content: p.content,
        image_count: p.image_count,
        heading_count: p.heading_count,
        images: (p.images || []).map(i => ({
          src: i.src, alt: i.alt, width: i.width, height: i.height,
          type: i.type, filename: i.filename,
        })),
      })),
    };
    window.wpsbDownload?.(`${safeData.site}-pages-migration.json`, JSON.stringify(bundle, null, 2), 'application/json');
  }

  /* Protect against malformed page — if page is null (shouldn't happen
     after the empty-state return above, but defense-in-depth), use empty
     objects so later JSX doesn't explode. */
  const issues = (page && page.meta) || {};
  const hasMeta = issues.title || issues.description || issues.og_image || issues.canonical;

  return (
    <>
      {/* Partial results notice — always shown */}
      <div style={{
        display:'flex', gap:10, alignItems:'flex-start',
        padding:'9px 13px', marginBottom:12,
        background:'var(--orange-dim)',
        border:'1px solid var(--orange-dim)',
        borderRadius:7, fontSize:'.76rem', color:'var(--dim)', lineHeight:1.6,
      }}>
        <span style={{ fontSize:'1rem', flexShrink:0, marginTop:1 }}>⚠</span>
        <span>
          <strong style={{ color:'var(--text)', fontWeight:600 }}>Partial results possible.</strong>
          {' '}Draft posts, private pages, password-protected content, pages not linked from the sitemap or nav, and content behind authentication will not appear here.
          Installing the WPSiteBeam plugin on the source site provides a complete page inventory including drafts.
        </span>
      </div>
    <div style={{ display: 'grid', gridTemplateColumns: 'minmax(240px, 260px) 1fr', gap: 14, alignItems: 'flex-start' }}>
      {/* ── LEFT SIDEBAR: grouped page tree + search ────────────────── */}
      <div className="card" style={{ position: 'sticky', top: 14, maxHeight: 'calc(100vh - 40px)', overflow: 'auto', display: 'flex', flexDirection: 'column' }}>
        <div className="card-head" style={{ flexShrink: 0 }}>
          <h3 className="card-title" style={{ fontSize: '.82rem' }}>
            Pages ({filter ? `${filteredCount} / ${pagesList.length}` : pagesList.length})
          </h3>
          <button
            className="btn btn-ghost btn-sm"
            onClick={() => setCompact(c => !c)}
            title={compact ? 'Switch to detailed view' : 'Switch to compact view'}
            style={{ fontSize: '.62rem', padding: '3px 7px' }}
          >
            {compact ? '▤' : '▥'}
          </button>
        </div>

        {/* Search bar — filters groups + rows live */}
        <div style={{ padding: '8px 10px', borderBottom: '1px solid var(--border)', flexShrink: 0 }}>
          <input
            type="text"
            value={filter}
            onChange={e => setFilter(e.target.value)}
            placeholder="Filter pages…"
            aria-label="Filter pages by name or URL"
            style={{
              width: '100%', padding: '6px 10px',
              background: 'var(--surface-2, rgba(255,255,255,0.04))',
              border: '1px solid var(--border)', borderRadius: 4,
              color: 'var(--text)', fontSize: '.74rem',
              fontFamily: 'var(--font-mono)',
            }}
          />
        </div>

        {/* Sectioned tree (Pages / Blog) — v1.3.8 patch h6.
            Each section header is a section label (Pages / Blog).
            Each node is either:
              - A leaf with an attached page → clickable button
              - An intermediate group with children → toggle + nested render
            Sections, groups, and children all sorted A-Z. */}
        <div style={{ flex: 1, overflow: 'auto', minHeight: 0 }}>
          {groupedPages.length === 0 && (
            <div style={{ padding: 20, color: 'var(--dim)', fontSize: '.74rem', textAlign: 'center' }}>
              No pages match "{filter}".
            </div>
          )}

          {(() => {
            /* Recursive node renderer. depth=0 sits flush left. */
            function renderNode(node, depth) {
              if (!node) return null;
              const hasChildren = Array.isArray(node.children) && node.children.length > 0;
              const collapseKey = node.path || ('node-' + depth + '-' + node.label);
              const isCollapsed = !!collapsed[collapseKey];
              const indentPx = 12 + (depth * 12);
              const isSelected = node.page && node.page._originalIndex === selected;

              /* Three render variants:
                 (a) Leaf with page, no children   → just the button
                 (b) Group with no page (intermediate) → caret + label, recurse
                 (c) Page WITH children            → button stack + caret + recursed children */
              if (!node.page && !hasChildren) return null;  /* nothing to show */

              return (
                <div key={collapseKey}>
                  {/* Page button (rendered if this node has a page) */}
                  {node.page && (() => {
                    const p = node.page;
                    if (compact) {
                      return (
                        <button
                          onClick={() => setSelected(p._originalIndex)}
                          style={{
                            width: '100%', textAlign: 'left',
                            padding: '5px 12px 5px ' + (indentPx + 12) + 'px',
                            background: isSelected ? 'var(--beam-dim)' : 'transparent',
                            border: 'none',
                            borderLeft: isSelected ? '3px solid var(--beam)' : '3px solid transparent',
                            cursor: 'pointer',
                            color: isSelected ? 'var(--beam)' : 'var(--text)',
                            fontSize: '.74rem',
                            display: 'flex', alignItems: 'center', gap: 6,
                            borderBottom: '1px solid var(--border)',
                          }}
                          aria-pressed={isSelected}
                          title={p.url}
                        >
                          {/* If this page also has children, show a caret to toggle */}
                          {hasChildren ? (
                            <span
                              role="button"
                              tabIndex={0}
                              aria-label={isCollapsed ? 'Expand children' : 'Collapse children'}
                              onClick={(e) => { e.stopPropagation(); setCollapsed(c => ({ ...c, [collapseKey]: !c[collapseKey] })); }}
                              style={{ fontSize: '1.05rem', lineHeight: 1, fontWeight: 700, color: 'var(--dim)', cursor: 'pointer', userSelect: 'none', minWidth: 18 }}>
                              {isCollapsed ? '▸' : '▾'}
                            </span>
                          ) : (
                            <span style={{ minWidth: 12 }}/>
                          )}
                          <span style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                            {node.label || p.label || ('Page ' + (p._originalIndex + 1))}
                          </span>
                          {p.image_count > 0 && (
                            <span style={{ fontSize: '.58rem', color: 'var(--dim)', fontFamily: 'var(--font-mono)' }}>
                              {p.image_count}🖼
                            </span>
                          )}
                        </button>
                      );
                    }
                    /* Non-compact: full info card per page */
                    return (
                      <button
                        onClick={() => setSelected(p._originalIndex)}
                        style={{
                          width: '100%', textAlign: 'left',
                          padding: '10px 12px 10px ' + (indentPx + 12) + 'px',
                          background: isSelected ? 'var(--beam-dim)' : 'transparent',
                          border: 'none',
                          borderLeft: isSelected ? '3px solid var(--beam)' : '3px solid transparent',
                          borderBottom: '1px solid var(--border)',
                          cursor: 'pointer', display: 'flex', flexDirection: 'column', gap: 3,
                          color: isSelected ? 'var(--beam)' : 'var(--text)',
                        }}
                        aria-pressed={isSelected}
                      >
                        <span style={{ fontSize: '.82rem', fontWeight: 600, display: 'flex', alignItems: 'center', gap: 6 }}>
                          {hasChildren && (
                            <span
                              role="button"
                              tabIndex={0}
                              aria-label={isCollapsed ? 'Expand children' : 'Collapse children'}
                              onClick={(e) => { e.stopPropagation(); setCollapsed(c => ({ ...c, [collapseKey]: !c[collapseKey] })); }}
                              style={{ fontSize: '1.05rem', lineHeight: 1, fontWeight: 700, color: 'var(--dim)', cursor: 'pointer', userSelect: 'none' }}>
                              {isCollapsed ? '▸' : '▾'}
                            </span>
                          )}
                          {node.label || p.label || ('Page ' + (p._originalIndex + 1))}
                        </span>
                        <span style={{ fontSize: '.62rem', color: 'var(--dim)', fontFamily: 'var(--font-mono)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                          {p.url.replace(/^https?:\/\//, '').substring(0, 42)}
                        </span>
                        <span style={{ fontSize: '.6rem', color: 'var(--dim)', display: 'flex', gap: 8, fontFamily: 'var(--font-mono)' }}>
                          {p.heading_count > 0 && <span>H:{p.heading_count}</span>}
                          {p.image_count > 0 && <span>🖼:{p.image_count}</span>}
                          {p.content_length > 0 && <span>{Math.round(p.content_length / 100) / 10}kb</span>}
                          {hasChildren && <span>· {node.children.length} child{node.children.length === 1 ? '' : 'ren'}</span>}
                        </span>
                      </button>
                    );
                  })()}

                  {/* Intermediate-only group header — no page attached but
                      has children. Renders as a non-clickable label with
                      caret to expand/collapse. */}
                  {!node.page && hasChildren && (
                    <button
                      onClick={() => setCollapsed(c => ({ ...c, [collapseKey]: !c[collapseKey] }))}
                      style={{
                        width: '100%', textAlign: 'left',
                        padding: '6px 12px 6px ' + indentPx + 'px',
                        background: 'transparent',
                        border: 'none', borderBottom: '1px solid var(--border)',
                        color: 'var(--dim)', fontFamily: 'var(--font-mono)',
                        fontSize: '.7rem', letterSpacing: '.04em',
                        cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 6,
                      }}
                      aria-expanded={!isCollapsed}
                    >
                      <span style={{ fontSize: '1.05rem', lineHeight: 1, fontWeight: 700 }}>{isCollapsed ? '▸' : '▾'}</span>
                      <span style={{ flex: 1 }}>{node.label}</span>
                      <span style={{ fontSize: '.62rem' }}>({node.children.length})</span>
                    </button>
                  )}

                  {/* Children — recurse */}
                  {hasChildren && !isCollapsed && (
                    <div>
                      {node.children.map(child => renderNode(child, depth + 1))}
                    </div>
                  )}
                </div>
              );
            }

            return groupedPages.map(section => {
              if (!section || !Array.isArray(section.nodes)) return null;
              const sectionKey = '__section__' + section.section;
              const isCollapsed = !!collapsed[sectionKey];
              const sectionCount = countNodes(section.nodes);
              return (
                <div key={section.section}>
                  <button
                    onClick={() => setCollapsed(c => ({ ...c, [sectionKey]: !c[sectionKey] }))}
                    style={{
                      width: '100%', textAlign: 'left',
                      padding: '8px 12px',
                      background: 'var(--surface-2, rgba(255,255,255,0.04))',
                      border: 'none', borderTop: '1px solid var(--border)', borderBottom: '1px solid var(--border)',
                      color: 'var(--text)', fontFamily: 'var(--font-mono)',
                      fontSize: '.68rem', letterSpacing: '.08em', textTransform: 'uppercase', fontWeight: 700,
                      cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 6,
                    }}
                    aria-expanded={!isCollapsed}
                  >
                    <span style={{ fontSize: '1.05rem', lineHeight: 1, fontWeight: 700 }}>{isCollapsed ? '▸' : '▾'}</span>
                    <span style={{ flex: 1 }}>{section.section}</span>
                    <span style={{ fontSize: '.62rem', color: 'var(--dim)' }}>({sectionCount})</span>
                  </button>
                  {!isCollapsed && (
                    <div>
                      {section.nodes.map(node => renderNode(node, 0))}
                    </div>
                  )}
                </div>
              );
            });
          })()}
        </div>

        {wasLimited && (
          <div style={{ padding: 10, fontSize: '.68rem', color: 'var(--dim)', borderTop: '1px solid var(--border)', flexShrink: 0 }}>
            Showing first {pagesList.length} of {totalDiscovered} pages discovered.
            Deep scan add-on captures all pages.
          </div>
        )}
        <div style={{ padding: 10, borderTop: '1px solid var(--border)', flexShrink: 0 }}>
          <button className="btn btn-primary btn-sm" onClick={downloadAllPagesJson} style={{ width: '100%', fontSize: '.72rem' }}>
            <Icon name="download" size={11}/>Export all pages (JSON)
          </button>
        </div>
      </div>

      {/* ── RIGHT PANEL: page detail ────────────────────────────────── */}
      <div style={{ display: 'flex', flexDirection: 'column', gap: 14, minWidth: 0 }}>
        {/* Page Identity */}
        <div className="card">
          <div className="card-head">
            <h2 className="card-title">Page Identity</h2>
            <div style={{ display: 'flex', gap: 6 }}>
              <button className="btn btn-ghost btn-sm" onClick={downloadPageJson} title="Export this page as JSON">
                <Icon name="download" size={12}/>JSON
              </button>
              <a className="btn btn-ghost btn-sm" href={page.url} target="_blank" rel="noopener noreferrer" title="Open the live page">
                ↗ Open
              </a>
            </div>
          </div>
          <div className="card-body">
            <table className="table" style={{ width: '100%' }}>
              <tbody>
                <tr>
                  <td style={{ width: 140, color: 'var(--dim)', fontSize: '.75rem' }}>Menu name</td>
                  <td style={{ fontSize: '.82rem', fontWeight: 600 }}>{page.menu_name || page.label || '—'}</td>
                </tr>
                <tr>
                  <td style={{ color: 'var(--dim)', fontSize: '.75rem' }}>Page title</td>
                  <td style={{ fontSize: '.82rem' }}>
                    {page.title ? (
                      <button
                        className="btn btn-ghost btn-sm"
                        style={{ padding: 0, background: 'transparent', border: 'none', color: 'var(--text)', fontSize: '.82rem', cursor: 'pointer', textAlign: 'left' }}
                        onClick={() => window.wpsbCopy?.(page.title, 'Page title copied')}
                        title="Click to copy"
                      >{page.title}</button>
                    ) : <span style={{ color: 'var(--warn)' }}>⚠ Not set</span>}
                  </td>
                </tr>
                <tr>
                  <td style={{ color: 'var(--dim)', fontSize: '.75rem' }}>Page URL</td>
                  <td>
                    <code style={{ fontSize: '.72rem', wordBreak: 'break-all', color: 'var(--beam)' }}>{page.url}</code>
                  </td>
                </tr>
                {page.description && (
                  <tr>
                    <td style={{ color: 'var(--dim)', fontSize: '.75rem', verticalAlign: 'top' }}>Description</td>
                    <td style={{ fontSize: '.78rem', color: 'var(--dim)', lineHeight: 1.6 }}>
                      {page.description.substring(0, 300)}{page.description.length > 300 ? '…' : ''}
                    </td>
                  </tr>
                )}
              </tbody>
            </table>
          </div>
        </div>

        {/* On-Page SEO */}
        {hasMeta && (
          <div className="card">
            <div className="card-head">
              <h2 className="card-title">On-Page SEO</h2>
            </div>
            <div className="card-body" style={{ padding: 0 }}>
              <table className="table" style={{ width: '100%', fontSize: '.78rem' }}>
                <tbody>
                  <tr>
                    <td style={{ width: 160, color: 'var(--dim)' }}>Meta title</td>
                    <td>{page.meta?.title || <span style={{ color: 'var(--warn)' }}>⚠ Not set</span>}</td>
                  </tr>
                  <tr>
                    <td style={{ color: 'var(--dim)' }}>Meta description</td>
                    <td style={{ lineHeight: 1.6 }}>
                      {page.meta?.description
                        ? page.meta.description.substring(0, 300)
                        : <span style={{ color: 'var(--warn)' }}>⚠ Not set</span>}
                    </td>
                  </tr>
                  {page.meta?.keywords && <tr><td style={{ color: 'var(--dim)' }}>Keywords</td><td>{page.meta.keywords}</td></tr>}
                  {page.meta?.canonical && (
                    <tr>
                      <td style={{ color: 'var(--dim)' }}>Canonical URL</td>
                      <td><code style={{ fontSize: '.72rem', color: 'var(--beam)' }}>{page.meta.canonical}</code></td>
                    </tr>
                  )}
                  {page.meta?.robots && <tr><td style={{ color: 'var(--dim)' }}>Robots</td><td><code style={{ fontSize: '.74rem' }}>{page.meta.robots}</code></td></tr>}
                  {page.meta?.og_title && <tr><td style={{ color: 'var(--dim)' }}>OG title</td><td>{page.meta.og_title}</td></tr>}
                  {page.meta?.og_image && (
                    <tr>
                      <td style={{ color: 'var(--dim)' }}>OG image</td>
                      <td>
                        <a href={page.meta.og_image} target="_blank" rel="noopener noreferrer" style={{ color: 'var(--beam)', fontSize: '.72rem', wordBreak: 'break-all' }}>
                          {page.meta.og_image}
                        </a>
                      </td>
                    </tr>
                  )}
                  {page.meta?.twitter_title && <tr><td style={{ color: 'var(--dim)' }}>Twitter title</td><td>{page.meta.twitter_title}</td></tr>}
                </tbody>
              </table>
            </div>
          </div>
        )}

        {/* Page Structure (Headings) */}
        {page.headings && page.headings.length > 0 && (
          <div className="card">
            <div className="card-head">
              <h2 className="card-title">Page Structure</h2>
              <span className="tag">{page.headings.length} headings</span>
            </div>
            <div className="card-body">
              <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
                {page.headings.map((h, i) => {
                  const levelNum = parseInt(h.level.replace('h', ''), 10) || 1;
                  const indent = (levelNum - 1) * 18;
                  const color = levelNum === 1 ? 'var(--text)' : levelNum === 2 ? 'var(--dim)' : 'var(--muted)';
                  return (
                    <div key={i} style={{ display: 'flex', gap: 10, padding: '4px 0', borderBottom: i < page.headings.length - 1 ? '1px solid var(--border)' : 'none', alignItems: 'center', paddingLeft: indent }}>
                      <span style={{
                        fontFamily: 'var(--font-mono)', fontSize: '.62rem',
                        color: levelNum === 1 ? 'var(--beam)' : 'var(--dim)',
                        fontWeight: 700, minWidth: 26, textTransform: 'uppercase',
                      }}>{h.level.toUpperCase()}</span>
                      <span style={{ fontSize: '.8rem', color, lineHeight: 1.4 }}>{h.text}</span>
                    </div>
                  );
                })}
              </div>
            </div>
          </div>
        )}

        {/* Page Content */}
        <div className="card">
          <div className="card-head">
            <h2 className="card-title">Page Content</h2>
            <span className="tag" style={{ fontFamily: 'var(--font-mono)' }}>
              {page.content_length > 0 ? `${Math.round(page.content_length / 100) / 10}kb` : 'empty'}
            </span>
          </div>
          <div className="card-body">
            {contentBlocks.length === 0 ? (
              <div style={{ color: 'var(--dim)', fontSize: '.78rem', fontStyle: 'italic' }}>
                No text content detected. Page may render content via images or JavaScript.
              </div>
            ) : (
              <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
                {contentBlocks.map(block => {
                  if (block.kind === 'prose') {
                    return (
                      <div key={block.key} style={{
                        fontSize: '.82rem', lineHeight: 1.8, color: 'var(--dim)',
                        whiteSpace: 'pre-wrap',
                      }}>
                        {block.text.substring(0, 5000)}
                        {block.text.length > 5000 ? '… (content truncated for display)' : ''}
                      </div>
                    );
                  }
                  /* Table block */
                  return (
                    <div key={block.key} style={{ overflowX: 'auto', marginTop: 6 }}>
                      <div style={{ fontSize: '.66rem', color: 'var(--dim)', marginBottom: 5, textTransform: 'uppercase', letterSpacing: '.05em' }}>
                        Structured data
                      </div>
                      <table className="table" style={{ fontSize: '.74rem', width: '100%' }}>
                        <tbody>
                          {block.rows.map((row, ri) => (
                            <tr key={ri} style={{ background: ri === 0 ? 'var(--surface-2)' : 'transparent' }}>
                              {row.map((cell, ci) => (
                                ri === 0
                                  ? <th key={ci} style={{ padding: '6px 10px', textAlign: 'left', fontWeight: 600 }}>{cell}</th>
                                  : <td key={ci} style={{ padding: '4px 10px' }}>{cell}</td>
                              ))}
                            </tr>
                          ))}
                        </tbody>
                      </table>
                    </div>
                  );
                })}
              </div>
            )}
          </div>
        </div>

        {/* Images on this page */}
        <div className="card">
          <div className="card-head">
            <h2 className="card-title">Images on this page</h2>
            <div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
              <span className="tag">{page.images?.length || 0}</span>
              {page.images && page.images.length > 0 && (
                <button className="btn btn-primary btn-sm" onClick={downloadPageImagesAsZip} disabled={zipping}>
                  <Icon name="download" size={12}/>{zipping ? 'Zipping…' : 'Download all as ZIP'}
                </button>
              )}
            </div>
          </div>
          <div className="card-body">
            {!page.images || page.images.length === 0 ? (
              <div style={{ color: 'var(--dim)', fontSize: '.78rem', fontStyle: 'italic' }}>
                No images detected on this page.
              </div>
            ) : (
              <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(140px, 1fr))', gap: 10 }}>
                {page.images.map(img => (
                  <div key={img.id || img.src} style={{ border: '1px solid var(--border)', borderRadius: 8, overflow: 'hidden', background: 'var(--surface)' }}>
                    <div style={{ aspectRatio: '1 / 1', background: 'var(--surface-2)', overflow: 'hidden', position: 'relative', cursor: 'pointer' }}
                         onClick={() => window.wpsbCopy?.(img.src, 'URL copied')}
                         title={img.alt || img.filename}>
                      <img src={proxyUrl(img.src)}
                           alt={img.alt || img.filename}
                           loading="lazy"
                           style={{ width: '100%', height: '100%', objectFit: 'contain' }}
                           onError={(e) => {
                             e.currentTarget.style.display = 'none';
                             e.currentTarget.parentElement.innerHTML = '<div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--dim);font-size:.65rem;padding:6px;text-align:center">Could not load</div>';
                           }}/>
                      {(img.width || img.height) && (
                        <span style={{
                          position: 'absolute', bottom: 4, right: 4,
                          fontSize: '.56rem', fontFamily: 'var(--font-mono)',
                          background: 'rgba(0,0,0,.65)', color: '#fff', padding: '1px 5px', borderRadius: 3,
                        }}>{img.width || '?'}×{img.height || '?'}</span>
                      )}
                    </div>
                    <div style={{ padding: 7, display: 'flex', flexDirection: 'column', gap: 4 }}>
                      <div style={{ fontSize: '.66rem', fontFamily: 'var(--font-mono)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}
                           title={img.filename}>
                        {img.filename}
                      </div>
                      {img.alt && (
                        <div style={{ fontSize: '.62rem', color: 'var(--dim)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}
                             title={`alt: ${img.alt}`}>
                          alt: {img.alt}
                        </div>
                      )}
                      <div style={{ display: 'flex', gap: 3 }}>
                        <button className="btn btn-ghost btn-sm"
                                style={{ padding: '2px 6px', fontSize: '.64rem', flex: 1 }}
                                onClick={(e) => { e.stopPropagation(); downloadOneImage(img); }}>
                          <Icon name="download" size={10}/>Save
                        </button>
                      </div>
                    </div>
                  </div>
                ))}
              </div>
            )}
          </div>
        </div>
      </div>
    </div>
    </>
  );
}
window.ScannerPagesTab = ScannerPagesTab;
/* window.ScannerPagesTab already set inside body */
})();
