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

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

  /* Hooks first (Rules of Hooks) */
  const [filter, setFilter] = useState('');
  const [collapsedGroups, setCollapsedGroups] = useState({});
  const [showExtras, setShowExtras] = useState(false);
  /* 2026-05-18 v4 per Jordan: track if user has manually toggled any group,
     so the default-collapse for large sites doesn't override their choice. */
  const [userToggled, setUserToggled] = useState(false);

  /* Filter out non-page protocol URLs that sometimes leak into sitemap data
     from anchor extraction. tel:/mailto:/sms:/javascript:/# are interactive
     links, not navigable pages — they shouldn't appear in a site map. Also
     filter empty/null paths defensively. The filter is conservative: we
     keep anything that starts with / or http (real URLs) and reject the
     known-bad protocols by prefix match. */
  const PROTOCOL_BLOCKLIST = ['tel:', 'mailto:', 'sms:', 'javascript:', 'data:', 'callto:', 'fax:'];
  const rawSitemap = Array.isArray(data?.sitemap) ? data.sitemap : [];
  const sitemap = rawSitemap.filter(n => {
    if (!n || typeof n.p !== 'string') return false;
    const path = n.p.trim();
    if (!path || path === '#') return false;
    const lower = path.toLowerCase();
    for (const proto of PROTOCOL_BLOCKLIST) {
      if (lower.startsWith(proto)) return false;
    }
    return true;
  });
  const totalPages = data?.pages || sitemap.length;

  /* 2026-05-18 v4 per Jordan: extract "discovered extras" — URLs the scanner
     crawled that aren't in the published sitemap.xml. Previously we only
     teased "...and N more pages discovered" without letting users see them.
     Source: data._raw.urls (the raw crawl list from Railway). Diff against
     sitemap paths; result is shown in a collapsible section at the bottom.
     If _raw.urls isn't available (older scans), fall back to the teaser. */
  const discoveredExtras = useMemo(() => {
    const rawUrls = Array.isArray(data?._raw?.urls) ? data._raw.urls : [];
    if (rawUrls.length === 0) return [];
    // Build a Set of sitemap paths for O(1) lookup
    const sitemapPaths = new Set(sitemap.map(n => n.p.replace(/\/$/, '')));
    // Get domain for stripping host portion
    const siteDomain = (data?.site || '').replace(/^https?:\/\//, '').replace(/\/$/, '');
    const extras = [];
    for (const u of rawUrls) {
      try {
        const url = typeof u === 'string' ? u : (u.url || u.href || '');
        if (!url) continue;
        const parsed = new URL(url);
        // Only same-domain
        if (siteDomain && !parsed.hostname.includes(siteDomain.replace(/^www\./, ''))) continue;
        const path = parsed.pathname.replace(/\/$/, '') || '/';
        if (!sitemapPaths.has(path)) {
          extras.push({ path, url });
        }
      } catch(e) { /* skip malformed */ }
    }
    return extras;
  }, [data?._raw?.urls, sitemap.length, data?.site]);

  /* Build a path → label + group map.
     Small sites (≤20 pages): flat list, no grouping overhead.
     Larger sites: group by first path segment, collapsible sections.
     Home is always shown un-grouped at the top. */
  const SMALL_SITE_THRESHOLD = 20;
  const LARGE_SITE_THRESHOLD = 30;
  const useGroupedView = sitemap.length > SMALL_SITE_THRESHOLD;
  const isLargeSite = sitemap.length > LARGE_SITE_THRESHOLD;

  /* Derived structure: {home: node, groups: [{key, label, pages: []}]} */
  const structured = useMemo(() => {
    const home = sitemap.find(n => n.p === '/') || null;
    const nonHome = sitemap.filter(n => n.p !== '/');

    /* Filter by text if provided */
    const normalized = filter.trim().toLowerCase();
    const matchesFilter = (n) => !normalized || n.p.toLowerCase().includes(normalized);

    if (!useGroupedView) {
      /* Flat list for small sites */
      return {
        mode: 'flat',
        home: home && matchesFilter(home) ? home : null,
        pages: nonHome.filter(matchesFilter),
      };
    }

    /* Grouped by first path segment (e.g. /blog/post → "blog" group) */
    const groups = {};
    nonHome.filter(matchesFilter).forEach(n => {
      const segments = n.p.split('/').filter(Boolean);
      const groupKey = segments[0] || 'root';
      if (!groups[groupKey]) groups[groupKey] = [];
      groups[groupKey].push(n);
    });
    const sortedGroups = Object.keys(groups).sort().map(key => ({
      key,
      label: key === 'root' ? 'Top level' : `/${key}`,
      pages: groups[key].sort((a, b) => a.p.localeCompare(b.p)),
    }));
    return {
      mode: 'grouped',
      home: home && matchesFilter(home) ? home : null,
      groups: sortedGroups,
    };
  }, [sitemap, filter, useGroupedView]);

  const shownCount = structured.mode === 'flat'
    ? (structured.home ? 1 : 0) + structured.pages.length
    : (structured.home ? 1 : 0) + structured.groups.reduce((n, g) => n + g.pages.length, 0);

  function toggleGroup(key) {
    setUserToggled(true);
    setCollapsedGroups(s => ({ ...s, [key]: !s[key] }));
  }
  function isGroupCollapsed(key) {
    // If user has explicitly toggled groups, respect their state.
    // Otherwise, large sites default to collapsed so the page isn't an endless scroll.
    if (userToggled) return !!collapsedGroups[key];
    if (isLargeSite) return collapsedGroups[key] === undefined ? true : !!collapsedGroups[key];
    return !!collapsedGroups[key];
  }
  function expandAll() {
    setUserToggled(true);
    const allKeys = structured.mode === 'grouped' ? Object.fromEntries(structured.groups.map(g => [g.key, false])) : {};
    setCollapsedGroups(allKeys);
  }
  function collapseAll() {
    setUserToggled(true);
    const allKeys = structured.mode === 'grouped' ? Object.fromEntries(structured.groups.map(g => [g.key, true])) : {};
    setCollapsedGroups(allKeys);
  }

  function renderPageRow(n, isHome = false, indent = 0) {
    const label = isHome ? 'Home' : n.p;
    return (
      <div key={n.p}
           style={{
             paddingLeft: 12 + indent,
             padding:'5px 12px', paddingLeft: 12 + indent,
             color: isHome ? 'var(--text)' : n.d === 1 ? 'var(--dim)' : 'var(--muted)',
             display:'flex', gap:8, alignItems:'center',
             borderRadius:4,
           }}>
        <span style={{ color:'var(--border-2)', fontSize:'.7rem' }}>
          {isHome ? '●' : '└─'}
        </span>
        <a href={`https://${data.site}${n.p}`}
           target="_blank" rel="noopener noreferrer"
           style={{ color:'inherit', textDecoration:'none', flex:1, minWidth:0 }}
           onMouseEnter={e => e.currentTarget.style.color = 'var(--beam)'}
           onMouseLeave={e => e.currentTarget.style.color = ''}>
          <span style={{ fontWeight: isHome ? 600 : 400 }}>{label}</span>
          {isHome && <span style={{ marginLeft:8, color:'var(--dim)', fontSize:'.68rem', fontFamily:'var(--font-mono)' }}>/</span>}
        </a>
      </div>
    );
  }

  return (
    <div className="card">
      <div className="card-head">
        <h2 className="card-title">Sitemap · {totalPages} pages</h2>
        <div style={{ display:'flex', gap:6, alignItems:'center', flexWrap:'wrap' }}>
          <span className="tag beam">FROM /sitemap.xml</span>
          <button className="btn btn-ghost btn-sm"
                  onClick={() => window.wpsbDownload(`${data.site}-sitemap.xml`, payloads.sitemapXml, 'application/xml')}>
            <Icon name="download" size={12}/>XML
          </button>
          <button className="btn btn-ghost btn-sm"
                  onClick={() => window.wpsbDownload(`${data.site}-sitemap.txt`, payloads.sitemapTxt, 'text/plain')}>
            <Icon name="download" size={12}/>TXT
          </button>
        </div>
      </div>

      {/* Filter — only shown when grouped view is in use (large sites) */}
      {useGroupedView && (
        <div style={{ padding:'8px 14px', borderBottom:'1px solid var(--border)' }}>
          <input
            type="text"
            value={filter}
            onChange={e => setFilter(e.target.value)}
            placeholder="Filter paths…"
            aria-label="Filter sitemap paths"
            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:'.76rem', fontFamily:'var(--font-mono)',
            }}
          />
          <div style={{ display:'flex', alignItems:'center', gap:10, marginTop:6, flexWrap:'wrap' }}>
            <div style={{ fontSize:'.66rem', color:'var(--dim)' }}>
              Showing {shownCount} of {sitemap.length} sitemap entries
              {isLargeSite ? ' · groups collapsed by default for large sites' : ''}
            </div>
            {/* 2026-05-18 v4 per Jordan: expand-all / collapse-all controls so
                users can quickly toggle visibility for large sitemaps. */}
            {isLargeSite && (
              <div style={{ marginLeft:'auto', display:'flex', gap:6 }}>
                <button type="button" onClick={expandAll}
                        style={{ fontSize:'.64rem', padding:'2px 8px', borderRadius:3, background:'var(--surface-2)', border:'1px solid var(--border)', color:'var(--muted)', cursor:'pointer', fontFamily:'var(--font-mono)' }}>
                  Expand all
                </button>
                <button type="button" onClick={collapseAll}
                        style={{ fontSize:'.64rem', padding:'2px 8px', borderRadius:3, background:'var(--surface-2)', border:'1px solid var(--border)', color:'var(--muted)', cursor:'pointer', fontFamily:'var(--font-mono)' }}>
                  Collapse all
                </button>
              </div>
            )}
          </div>
        </div>
      )}

      <div className="card-body" style={{ fontFamily:'var(--font-mono)', fontSize:'.78rem', lineHeight:1.5, padding:'8px 0' }}>
        {sitemap.length === 0 && (
          <div style={{ color:'var(--dim)', padding:'20px 14px', fontStyle:'italic' }}>
            No sitemap entries available.
          </div>
        )}

        {/* Home row — always first, always visible */}
        {structured.home && renderPageRow(structured.home, true, 0)}

        {/* Flat list mode (small sites) */}
        {structured.mode === 'flat' && structured.pages.map(n => renderPageRow(n, false, 20))}

        {/* Grouped mode (large sites) */}
        {structured.mode === 'grouped' && structured.groups.map(group => {
          const isCollapsed = isGroupCollapsed(group.key);
          return (
            <div key={group.key}>
              <button
                type="button"
                onClick={() => toggleGroup(group.key)}
                aria-expanded={!isCollapsed}
                style={{
                  width:'100%', textAlign:'left',
                  background: 'var(--surface-2, rgba(255,255,255,0.02))',
                  border:'none',
                  borderTop:'1px solid var(--border)', borderBottom:'1px solid var(--border)',
                  padding:'6px 14px',
                  color:'var(--dim)', fontFamily:'var(--font-mono)',
                  fontSize:'.7rem', letterSpacing:'.04em', textTransform:'uppercase',
                  cursor:'pointer', display:'flex', alignItems:'center', gap:6,
                  marginTop:6,
                }}>
                <span style={{ fontSize:'1.05rem', lineHeight:1, fontWeight:600 }}>{isCollapsed ? '▸' : '▾'}</span>
                <span style={{ color:'var(--text)' }}>{group.label}</span>
                <span style={{ marginLeft:'auto', fontSize:'.64rem' }}>({group.pages.length})</span>
              </button>
              {!isCollapsed && group.pages.map(n => renderPageRow(n, false, 20))}
            </div>
          );
        })}

        {shownCount === 0 && filter && (
          <div style={{ color:'var(--dim)', padding:'20px 14px', fontStyle:'italic', textAlign:'center' }}>
            No paths match "{filter}".
          </div>
        )}

        {/* 2026-05-18 v4 per Jordan: actual discovered-extras list (was a tease).
            Replaces "...and N more pages discovered (not in sitemap.xml)" with
            a collapsible section that LISTS the URLs. Pulls from _raw.urls
            diffed against sitemap paths. If _raw.urls isn't available (older
            scans), falls back to the original teaser line. */}
        {discoveredExtras.length > 0 ? (
          <div style={{ marginTop:10, borderTop:'1px solid var(--border)' }}>
            <button
              type="button"
              onClick={() => setShowExtras(s => !s)}
              aria-expanded={showExtras}
              style={{
                width:'100%', textAlign:'left',
                background: 'rgba(0, 194, 209, 0.04)',
                border:'none',
                borderBottom:'1px solid var(--border)',
                padding:'8px 14px',
                color:'var(--beam, #00C2D1)', fontFamily:'var(--font-mono)',
                fontSize:'.7rem', letterSpacing:'.04em', textTransform:'uppercase',
                cursor:'pointer', display:'flex', alignItems:'center', gap:6, fontWeight:700,
              }}>
              <span style={{ fontSize:'1.05rem', lineHeight:1 }}>{showExtras ? '▾' : '▸'}</span>
              <span>Discovered extras — not in sitemap.xml</span>
              <span style={{ marginLeft:'auto', fontSize:'.64rem', fontWeight:400, color:'var(--muted)' }}>({discoveredExtras.length})</span>
            </button>
            {showExtras && (
              <div style={{ padding:'4px 0' }}>
                <div style={{ padding:'6px 14px', fontSize:'.7rem', color:'var(--muted)', lineHeight:1.5, background:'rgba(0, 194, 209, 0.02)' }}>
                  These URLs were crawled by WPSiteBeam but aren't listed in <code style={{ background:'var(--surface-2)', padding:'1px 4px', borderRadius:3 }}>sitemap.xml</code>.
                  Consider adding them so search engines can index them properly.
                </div>
                {discoveredExtras.map((extra, i) => (
                  <div key={'extra-'+i} style={{
                    paddingLeft: 24, paddingRight:14, paddingTop:3, paddingBottom:3,
                    fontFamily:'var(--font-mono)', fontSize:'.74rem', color:'var(--muted)',
                    borderBottom: i < discoveredExtras.length - 1 ? '1px solid rgba(255,255,255,.03)' : 'none',
                  }}>
                    <a href={extra.url} target="_blank" rel="noopener noreferrer" style={{ color:'inherit', textDecoration:'none' }}
                       onMouseOver={e => e.currentTarget.style.color = 'var(--text)'}
                       onMouseOut={e => e.currentTarget.style.color = 'var(--muted)'}>
                      {extra.path}
                    </a>
                  </div>
                ))}
              </div>
            )}
          </div>
        ) : (totalPages > sitemap.length && (
          <div style={{ marginTop:10, padding:'8px 14px', color:'var(--dim)', fontSize:'.72rem', borderTop:'1px solid var(--border)' }}>
            …and {totalPages - sitemap.length} more pages discovered during the scan (not in sitemap.xml)
          </div>
        ))}
      </div>
    </div>
  );
}

/* ═════════════════════════════════════════════════════════════════════
   PagesTab — per-page content viewer (v1 parity)

   Renders a left-sidebar page selector + right content panel showing
   per-page Identity, SEO, Headings, Content, and Images. Data comes
   from data.pagesList[] populated by enrichPagesData() — each entry
   has {url, label, menu_name, title, description, meta, headings,
   content, images, content_length, image_count, heading_count}.

   Migration-focused features:
     • Per-page content export: JSON bundle of all page data
     • Per-page image download: ZIP of just this page's images
     • Global export: single JSON with every page (for migration tools)
     • Copy-to-clipboard on URLs, titles, descriptions for quick re-use

   Design notes:
     • Sidebar fixed-width 260px, content panel fills remaining width
     • Content panel scrolls independently (sticky sidebar)
     • Headings rendered as visual hierarchy (indent by H1/H2/H3 level)
     • Content renders narrative as prose + [TABLE DATA] blocks as tables
     • Images grid uses same tile + Save button pattern as ScannerImagesTab
     • Mobile: sidebar collapses to a <select> at <768px breakpoint
   ═════════════════════════════════════════════════════════════════════ */

window.ScannerSitemapTab = ScannerSitemapTab;
})();
