// SubcategoryPage — the leaf "project index" view, reached from a subcategory
// chip on the landing or a category page. Shows a filtered grid of project
// cards (image-forward), the deepest level of the navigation. Loads AFTER
// direction-a.jsx and category-page.jsx (uses window.CrumbBar, window.A_Footer,
// window.LockIcon, window.CP_statusColor).

const SP_RULE = 'var(--rule)', SP_INK = 'var(--ink)', SP_INK2 = 'var(--ink-2)',
      SP_INK3 = 'var(--ink-3)', SP_PAPER2 = 'var(--paper-2)';

// Map a subcategory to the projects it contains. Architecture filters by firm;
// visual by discipline; everything else shows the full category list.
function projectsForSub(catWord, subId) {
  const cfg = window.CATEGORY_PAGES[catWord];
  const rows = (cfg && cfg.rows) || [];
  if (catWord === 'architecture') {
    // "projects" lists every built project regardless of firm — they're all
    // designated in the matrix (the `firm` field), so the public index shows
    // the complete set. "work-samples" is the password-gated tier (null →
    // protected screen).
    if (subId === 'projects') return rows;
    if (subId === 'work-samples') return null;
  }
  if (catWord === 'visual') {
    if (subId === 'graphics') return rows.filter((r) => ['identity', 'editorial', 'print', 'poster', 'type', 'concept art'].indexOf(r.discipline) !== -1);
    if (subId === 'motion') return []; // rendered as the hover-to-play MotionGrid (motion-clips.jsx), not cards
    if (subId === 'photo') return []; // rendered as the public sectioned PhotoGallery (photo-gallery.jsx), not cards
  }
  if (catWord === 'material') {
    // 'rd' is the r+d journal (rendered separately, not as project cards). The
    // card-driven subs filter MATERIAL_PROJECTS by each row's `sub` tag;
    // untagged rows default to 'fabrication'.
    if (subId === 'rd') return [];
    return rows.filter((r) => {
      const subs = Array.isArray(r.sub) ? r.sub : [r.sub || 'fabrication'];
      return subs.indexOf(subId) !== -1;
    });
  }
  return rows;
}

// A meta line under each card title, picked from whatever fields the row has.
function cardMeta(catWord, r) {
  if (catWord === 'architecture') return `${r.role} · ${r.loc}`;
  if (catWord === 'visual') return `${r.discipline} · ${r.client}`;
  if (catWord === 'coding') return r.stack;
  if (catWord === 'material') return `${r.process} · ${r.material}`;
  return r.status;
}

function SP_Card({ catWord, row, onOpen }) {
  const [h, setH] = React.useState(false);
  const img = (row.images && (row.images.find((im) => im.feature) || row.images[0])) || { tag: 'image', title: '' };
  const statusColor = window.CP_statusColor || (() => SP_INK);
  const LockIcon = window.LockIcon;
  const unlocked = window.usePhotoGate ? window.usePhotoGate() : true;
  const locked = (window.PhotoGate ? window.PhotoGate.isGated(row) : false) && !unlocked;
  // A card with no real still/clip (and not a motion slot) shows the ComingSoon
  // panel instead of the striped placeholder. Locked cards keep the neutral
  // placeholder so a gated project stays indistinguishable from an empty one.
  const placeholder = window.mediaIsPlaceholder
    ? window.mediaIsPlaceholder(img) : !(img.src || img.video || img.motion);
  const showSoon = placeholder && !locked;
  return (
    <div onClick={onOpen} onMouseEnter={() => setH(true)} onMouseLeave={() => setH(false)}
      style={{ cursor: 'pointer', background: h ? SP_PAPER2 : 'transparent',
        border: `1px solid ${h ? SP_INK : SP_RULE}`, transition: 'background .18s, border-color .18s',
        padding: 14, display: 'flex', flexDirection: 'column', gap: 12 }}>
      <div style={{ position: 'relative', overflow: 'hidden', aspectRatio: img.aspect || '4 / 3',
        border: `1px solid ${SP_RULE}` }}>
        {showSoon
          ? (window.ComingSoon && <window.ComingSoon tag={img.tag} />)
          : (window.MotionMedia && (
              <window.MotionMedia media={locked ? { tag: img.tag } : img} active={h && !locked} />
            ))}
      </div>
      <div>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: 12, marginBottom: 4 }}>
          <div style={{ fontSize: 17, fontWeight: 500, letterSpacing: -0.3 }}>{row.title}</div>
          <div className="mono tnum" style={{ fontSize: 12, color: SP_INK3, flexShrink: 0 }}>{row.yr}</div>
        </div>
        <div className="mono" style={{ fontSize: 12, color: SP_INK2, letterSpacing: 0.1 }}>{cardMeta(catWord, row)}</div>
      </div>
      <div className="mono" style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 11, color: SP_INK3 }}>
        <span style={{ width: 6, height: 6, background: statusColor(row.status) }} />
        {row.status}
        <span style={{ marginLeft: 'auto', color: h ? SP_INK : SP_INK3, transition: 'color .18s' }}>open ↗</span>
      </div>
    </div>
  );
}

// SP_Matrix — the architecture-style "index" matrix view, reused for the
// graphics subcategory's index ⇄ gallery toggle. Renders the sortable column
// header + the exact same expandable CP_Row used on category pages, fed the
// filtered subcategory rows and the parent category's column config.
function SP_Matrix({ cfg, rows, openTitle, setOpenTitle, rowRefs }) {
  const CP_Row = window.CP_Row;
  const SortCaret = window.CP_SortCaret;
  const sortValue = window.CP_sortValue;
  const isMobile = window.useIsMobile();
  const [sort, setSort] = React.useState({ key: null, dir: 'asc' });
  const onSort = (key) => setSort((s) => (s.key === key
    ? { key, dir: s.dir === 'asc' ? 'desc' : 'asc' }
    : { key, dir: 'asc' }));
  const sortedRows = React.useMemo(() => {
    if (!sort.key) return rows;
    const col = cfg.columns.find((c) => c.key === sort.key);
    if (!col) return rows;
    const dir = sort.dir === 'desc' ? -1 : 1;
    return [...rows].sort((a, b) => {
      const va = sortValue(col, a), vb = sortValue(col, b);
      if (va < vb) return -1 * dir;
      if (va > vb) return 1 * dir;
      return 0;
    });
  }, [rows, sort, cfg]);
  return (
    <div>
      {!isMobile && (
        <div className="mono" style={{ display: 'grid', gridTemplateColumns: cfg.grid, gap: 16,
          padding: '14px 56px', fontSize: 11, color: SP_INK3, letterSpacing: 0.5, textTransform: 'uppercase',
          background: SP_PAPER2, borderBottom: `1px solid ${SP_RULE}` }}>
          {cfg.columns.map((col) => {
            const active = sort.key === col.key;
            const right = col.align === 'right';
            return (
              <div key={col.key} onClick={() => onSort(col.key)}
                style={{ display: 'flex', alignItems: 'center', gap: 5, cursor: 'pointer', userSelect: 'none',
                  justifyContent: right ? 'flex-end' : 'flex-start',
                  color: active ? SP_INK : SP_INK3, transition: 'color .15s' }}
                onMouseEnter={(e) => { if (!active) e.currentTarget.style.color = SP_INK2; }}
                onMouseLeave={(e) => { if (!active) e.currentTarget.style.color = SP_INK3; }}>
                {right && <SortCaret active={active} dir={sort.dir} />}
                <span>{col.label}</span>
                {!right && <SortCaret active={active} dir={sort.dir} />}
              </div>
            );
          })}
        </div>
      )}
      {sortedRows.map((row, i) => (
        <CP_Row key={row.title} cfg={cfg} row={row} last={i === sortedRows.length - 1}
          rootRef={(el) => { if (rowRefs) rowRefs.current[row.title] = el; }}
          open={openTitle === row.title}
          onToggle={() => setOpenTitle((t) => (t === row.title ? null : row.title))} />
      ))}
    </div>
  );
}

function SubcategoryPage({ catWord, subId, navigate }) {
  const cat = window.CATEGORIES.find((c) => c.word === catWord);
  const cfg = window.CATEGORY_PAGES[catWord];
  const Footer = window.A_Footer;
  const LockIcon = window.LockIcon;
  const CrumbBar = window.CrumbBar;
  const mobile = window.useIsMobile();
  // Graphics gets the architecture-style index ⇄ gallery toggle. State lives
  // here (above the early return so hooks stay unconditional); the gallery card
  // flips to the index view and expands the matching row, mirroring CategoryPage.
  const hasToggle = catWord === 'visual' && subId === 'graphics';
  const [view, setView] = React.useState(() => {
    if (!hasToggle) return 'gallery';
    try { return localStorage.getItem('sp-view-' + catWord + '-' + subId) || 'index'; } catch (e) { return 'index'; }
  });
  const [openTitle, setOpenTitle] = React.useState(null);
  const rowRefs = React.useRef({});
  const pendingScroll = React.useRef(null);
  React.useEffect(() => {
    if (!hasToggle) return;
    try { localStorage.setItem('sp-view-' + catWord + '-' + subId, view); } catch (e) {}
  }, [view, hasToggle, catWord, subId]);
  React.useEffect(() => {
    if (view !== 'index' || !pendingScroll.current) return;
    const title = pendingScroll.current;
    const id = setTimeout(() => {
      const el = rowRefs.current[title];
      if (el) window.scrollTo(0, el.getBoundingClientRect().top + window.scrollY - 12);
      pendingScroll.current = null;
    }, 170);
    return () => clearTimeout(id);
  }, [view]);
  const openFromGallery = React.useCallback((title) => {
    pendingScroll.current = title;
    setOpenTitle(title);
    setView('index');
  }, []);
  if (!cat || !cfg) return null;
  const sub = cat.subs.find((s) => s.id === subId) || { id: subId, label: subId, meta: '' };
  const isMotion = catWord === 'visual' && subId === 'motion';
  const isPhoto = catWord === 'visual' && subId === 'photo';
  const motionCount = (window.MOTION_CLIPS || []).length;
  const photoCount = isPhoto
    ? (window.PHOTO_SECTIONS || []).reduce((n, s) => n + ((s.images || []).length), 0) : 0;
  const items = projectsForSub(catWord, subId);
  const locked = items === null;
  const noun = isMotion ? (motionCount === 1 ? 'clip' : 'clips')
    : isPhoto ? (photoCount === 1 ? 'photo' : 'photos')
    : ((items && items.length === 1) ? 'item' : 'items');
  const count = isMotion ? motionCount : isPhoto ? photoCount : (items ? items.length : 0);
  // For the architecture work-samples tier, the protected screen also offers a
  // direct entry to the existing password-protected portfolio page.
  const gateUrl = (catWord === 'architecture' && subId === 'work-samples')
    ? window.META.workSamplesUrl : null;
  return (
    <div style={{ display: 'flex', flexDirection: 'column', minHeight: '100vh' }}>
      <CrumbBar section={cfg.section} navigate={navigate}
        trail={[
          { label: 'index', to: { page: 'index' } },
          { label: cat.word, to: { page: 'category', cat: cat.word } },
          { label: sub.label },
        ]} />

      <div style={{ padding: mobile ? '34px 22px 26px' : '48px 56px 30px', borderBottom: `1px solid ${SP_RULE}`,
        display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', gap: mobile ? 16 : 40, flexWrap: 'wrap' }}>
        <div>
          <div className="mono" style={{ fontSize: mobile ? 9.5 : 11, color: SP_INK3, letterSpacing: 0.5,
            textTransform: 'uppercase', marginBottom: 14 }}>§ {cfg.section}</div>
          <div style={{ display: 'flex', alignItems: 'baseline', gap: mobile ? 11 : 16, flexWrap: 'wrap' }}>
            {sub.locked && LockIcon && <span style={{ color: SP_INK2 }}><LockIcon size={mobile ? 18 : 26} /></span>}
            <div style={{ fontSize: mobile ? 'clamp(30px, 8vw, 40px)' : 60, fontWeight: 500, letterSpacing: -2, lineHeight: 0.95 }}>{sub.label}</div>
            <div className="mono" style={{ fontSize: mobile ? 13 : 16, color: SP_INK3 }}>· {sub.meta}</div>
          </div>
        </div>
        <div className="mono tnum" style={{ fontSize: mobile ? 12 : 13, color: SP_INK3 }}>
          {hasToggle ? '' : (locked ? 'protected' : `${String(count).padStart(2, '0')} ${noun}`)}
        </div>
      </div>

      {/* Graphics: architecture-style index ⇄ gallery toggle bar. */}
      {hasToggle && window.CP_ViewToggle && (
        <window.CP_ViewToggle view={view} setView={setView} count={items.length} />
      )}

      {hasToggle ? (
        view === 'index' ? (
          <div style={{ flex: 1 }}>
            <SP_Matrix cfg={cfg} rows={items} openTitle={openTitle}
              setOpenTitle={setOpenTitle} rowRefs={rowRefs} />
          </div>
        ) : (
          <div style={{ flex: 1, padding: mobile ? '26px 22px 48px' : '36px 56px 60px' }}>
            <div style={{ display: 'grid',
              gridTemplateColumns: mobile ? '1fr' : 'repeat(auto-fill, minmax(280px, 1fr))', gap: mobile ? 14 : 18 }}>
              {items.map((row) => (
                <SP_Card key={row.title} catWord={catWord} row={row}
                  onOpen={() => openFromGallery(row.title)} />
              ))}
            </div>
          </div>
        )
      ) : (
      <div style={{ flex: 1, padding: mobile ? '26px 22px 48px' : '36px 56px 60px' }}>
        {locked ? (
          <div style={{ maxWidth: 520, padding: mobile ? '20px 0' : '40px 0' }}>
            <div className="mono" style={{ fontSize: 11, color: SP_INK3, letterSpacing: 0.5,
              textTransform: 'uppercase', marginBottom: 16, display: 'flex', alignItems: 'center', gap: 8 }}>
              {LockIcon && <LockIcon size={12} />} {gateUrl ? 'Protected portfolio' : 'Protected archive'}
            </div>
            <div style={{ fontSize: mobile ? 17 : 20, lineHeight: 1.45, color: SP_INK, letterSpacing: -0.3, marginBottom: 20, textWrap: 'pretty' }}>
              {gateUrl
                ? 'A detailed portfolio of work samples — drawings, process, and case studies — is password-protected for prospective clients. Enter below, or request credentials.'
                : `The ${sub.label} archive is password-protected. Available on request — get in touch for access.`}
            </div>
            <div style={{ display: 'flex', flexWrap: 'wrap', gap: 10, alignItems: 'center' }}>
              {gateUrl && (
                <a href={gateUrl} target="_blank" rel="noopener" className="mono"
                  style={{ fontFamily: 'var(--font-mono)', fontSize: 13, padding: '13px 16px', cursor: 'pointer',
                    border: `1px solid ${SP_INK}`, background: SP_INK, color: 'var(--paper)', textDecoration: 'none',
                    display: 'inline-flex', alignItems: 'center', gap: 10 }}>
                  {LockIcon && <LockIcon size={12} />} enter work samples <span style={{ opacity: 0.7 }}>↗</span>
                </a>
              )}
              <button onClick={() => window.openContactModal && window.openContactModal({ reason: `${sub.label} archive` })} className="mono"
                style={{ fontFamily: 'var(--font-mono)', fontSize: 13, padding: '13px 16px', cursor: 'pointer',
                  border: `1px solid ${gateUrl ? SP_RULE : SP_INK}`, background: gateUrl ? 'transparent' : SP_INK,
                  color: gateUrl ? SP_INK : 'var(--paper)',
                  display: 'inline-flex', alignItems: 'center', gap: 10 }}>
                request access <span style={{ opacity: 0.7 }}>→</span>
              </button>
            </div>
          </div>
        ) : isMotion && window.MotionGrid ? (
          <window.MotionGrid mobile={mobile} />
        ) : isPhoto && window.PhotoGallery ? (
          <window.PhotoGallery mobile={mobile} />
        ) : (
          <div style={{ display: 'grid',
            gridTemplateColumns: mobile ? '1fr' : 'repeat(auto-fill, minmax(280px, 1fr))', gap: mobile ? 14 : 18 }}>
            {items.map((row) => (
              <SP_Card key={row.title} catWord={catWord} row={row}
                onOpen={() => navigate({ page: 'category', cat: catWord, open: row.title })} />
            ))}
          </div>
        )}
      </div>
      )}

      {Footer ? <Footer /> : null}
    </div>
  );
}

window.SubcategoryPage = SubcategoryPage;
// Exposed so CategoryPage's gallery view (architecture index ⇄ gallery toggle)
// can reuse the exact same photo-forward card.
window.SP_Card = SP_Card;
window.SP_cardMeta = cardMeta;
