// photo-gate.jsx — soft password gate for firm (TVA) project photography.
//
// SOFT GATE, NOT REAL SECURITY: the gated images ship inside the static build,
// so this deters casual viewing only — a determined visitor with dev tools can
// still reach the files. It exists to keep firm work-samples out of plain sight
// for prospective clients, mirroring the old external gated link but in-page.
//
// What's gated: a project's photos are hidden when row.firm === 'TVA'. Names,
// briefs, and facts stay public. Independent (futureform) work is never gated.
// Unlock is perpetual per-device (localStorage), cleared only via Re-lock.
//
// Load order: AFTER direction-a.jsx (uses window.LockIcon) and BEFORE the pages
// that read the gate (category-page.jsx, subcategory-page.jsx) + app.jsx, which
// mounts <PhotoGateModal/> once. Other Babel scopes call everything via window.*

const PHOTO_GATE_KEY  = 'sr-arch-photos-unlocked';
const PHOTO_GATE_PW   = 'formfollowsfunction';
const PHOTO_GATE_EVT  = 'sr-photo-gate-change';
// The hidden, un-linked access route. Visiting  <site>/#access  opens the
// password prompt — there is no button or link to it anywhere in the UI.
// Change this string to rename the secret URL.
const PHOTO_GATE_SLUG = 'access';

// True when the current URL hash points at the hidden access route, e.g.
// '#access' or '#/access' (case-insensitive).
function pgIsAccessHash() {
  try {
    const h = (window.location.hash || '').replace(/^#\/?/, '').toLowerCase();
    return h === PHOTO_GATE_SLUG;
  } catch (e) { return false; }
}

function pgIsUnlocked() {
  try { return localStorage.getItem(PHOTO_GATE_KEY) === '1'; } catch (e) { return false; }
}
function pgUnlock(pw) {
  if ((pw || '').trim() !== PHOTO_GATE_PW) return false;
  try { localStorage.setItem(PHOTO_GATE_KEY, '1'); } catch (e) {}
  try { window.dispatchEvent(new Event(PHOTO_GATE_EVT)); } catch (e) {}
  return true;
}
function pgLock() {
  try { localStorage.removeItem(PHOTO_GATE_KEY); } catch (e) {}
  try { window.dispatchEvent(new Event(PHOTO_GATE_EVT)); } catch (e) {}
}
// A project's photos are gated when it is firm (TVA) work.
function pgIsGated(row) { return !!row && row.firm === 'TVA'; }
// Currently hidden = gated AND not yet unlocked.
function pgIsLocked(row) { return pgIsGated(row) && !pgIsUnlocked(); }

// Hook: re-render the caller whenever the unlock state flips (this tab via the
// custom event, or another tab via the native 'storage' event).
function usePhotoGate() {
  const [unlocked, setUnlocked] = React.useState(pgIsUnlocked());
  React.useEffect(() => {
    const h = () => setUnlocked(pgIsUnlocked());
    window.addEventListener(PHOTO_GATE_EVT, h);
    window.addEventListener('storage', h);
    return () => {
      window.removeEventListener(PHOTO_GATE_EVT, h);
      window.removeEventListener('storage', h);
    };
  }, []);
  return unlocked;
}

// Imperative opener, registered by <PhotoGateModal/>:
//   window.openPasswordPrompt({ onUnlock })  — onUnlock runs after a correct pw.
function PhotoGateModal() {
  const [open, setOpen]   = React.useState(false);
  const [shown, setShown] = React.useState(false);
  const [val, setVal]     = React.useState('');
  const [err, setErr]     = React.useState(false);
  const cb        = React.useRef(null);
  const inputRef  = React.useRef(null);
  const closeTime = React.useRef(0);
  const LockIcon  = window.LockIcon;
  const mobile    = window.useIsMobile ? window.useIsMobile() : false;
  const reduce = typeof window.matchMedia === 'function'
    && window.matchMedia('(prefers-reduced-motion: reduce)').matches;

  React.useEffect(() => {
    window.openPasswordPrompt = (opts) => {
      const o = opts || {};
      cb.current = typeof o.onUnlock === 'function' ? o.onUnlock : null;
      setVal(''); setErr(false);
      clearTimeout(closeTime.current);
      setOpen(true);
      setTimeout(() => { setShown(true); if (inputRef.current) inputRef.current.focus(); }, 30);
    };
    return () => { try { delete window.openPasswordPrompt; } catch (e) {} };
  }, []);

  const close = React.useCallback(() => {
    setShown(false);
    clearTimeout(closeTime.current);
    closeTime.current = setTimeout(() => setOpen(false), reduce ? 0 : 260);
  }, [reduce]);

  const submit = (e) => {
    if (e) e.preventDefault();
    if (pgUnlock(val)) {
      const fn = cb.current; cb.current = null;
      close();
      if (fn) setTimeout(fn, reduce ? 0 : 180);
    } else {
      setErr(true);
    }
  };

  // Esc to close + lock body scroll while open.
  React.useEffect(() => {
    if (!open) return;
    const onKey = (e) => { if (e.key === 'Escape') close(); };
    window.addEventListener('keydown', onKey);
    const prev = document.documentElement.style.overflow;
    document.documentElement.style.overflow = 'hidden';
    return () => {
      window.removeEventListener('keydown', onKey);
      document.documentElement.style.overflow = prev;
    };
  }, [open, close]);

  if (!open) return null;
  const ease = 'cubic-bezier(.16,1,.3,1)';

  return (
    <div onClick={close}
      style={{
        position: 'fixed', inset: 0, zIndex: 9500,
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        padding: mobile ? 18 : 'clamp(16px, 5vw, 48px)', boxSizing: 'border-box',
        background: 'oklch(20% 0.02 140 / 0.74)',
        backdropFilter: shown ? 'blur(7px)' : 'blur(0px)', WebkitBackdropFilter: shown ? 'blur(7px)' : 'blur(0px)',
        opacity: shown ? 1 : 0,
        transition: reduce ? 'none' : 'opacity .26s ease, backdrop-filter .26s ease',
      }}>
      <form onClick={(e) => e.stopPropagation()} onSubmit={submit}
        style={{
          width: mobile ? '100%' : 420, maxWidth: '100%', boxSizing: 'border-box',
          background: 'var(--paper)', border: '1px solid var(--ink)',
          padding: mobile ? '24px 20px' : '30px 30px 26px',
          display: 'flex', flexDirection: 'column', gap: 16,
          opacity: shown ? 1 : 0,
          transform: reduce ? 'none' : (shown ? 'scale(1) translateY(0)' : 'scale(.96) translateY(8px)'),
          transition: reduce ? 'none' : `opacity .28s ease, transform .34s ${ease}`,
        }}>

        <div className="mono" style={{ display: 'flex', alignItems: 'center', gap: 9,
          fontSize: 11, letterSpacing: 0.6, textTransform: 'uppercase', color: 'var(--ink-3)' }}>
          {LockIcon && <span style={{ display: 'inline-flex', color: 'var(--ink-2)' }}><LockIcon size={12} /></span>}
          Private access
        </div>

        <div>
          <div style={{ fontSize: mobile ? 21 : 24, fontWeight: 500, letterSpacing: -0.6, lineHeight: 1.1, marginBottom: 8 }}>
            Enter access password
          </div>
          <div style={{ fontSize: 13.5, color: 'var(--ink-2)', lineHeight: 1.5, textWrap: 'pretty' }}>
            Unlock the full photography archive for the built work. Access is remembered on this device.
          </div>
        </div>

        <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
          <input ref={inputRef} type="password" value={val} autoComplete="off"
            placeholder="Password"
            onChange={(e) => { setVal(e.target.value); if (err) setErr(false); }}
            className="mono"
            style={{
              fontFamily: 'var(--font-mono)', fontSize: 14, letterSpacing: 0.3,
              padding: '12px 14px', boxSizing: 'border-box', width: '100%',
              background: 'var(--paper-2)', color: 'var(--ink)',
              border: `1px solid ${err ? 'var(--accent-c)' : 'var(--rule)'}`,
              outline: 'none', borderRadius: 0, transition: 'border-color .15s',
            }}
            onFocus={(e) => { if (!err) e.currentTarget.style.borderColor = 'var(--ink)'; }}
            onBlur={(e) => { if (!err) e.currentTarget.style.borderColor = 'var(--rule)'; }} />
          {err && (
            <div className="mono" style={{ fontSize: 11.5, color: 'var(--accent-c)', letterSpacing: 0.2 }}>
              Incorrect password — try again.
            </div>
          )}
        </div>

        <div style={{ display: 'flex', gap: 10, alignItems: 'center', marginTop: 2 }}>
          <button type="submit" className="mono"
            style={{
              fontFamily: 'var(--font-mono)', fontSize: 13, letterSpacing: 0.2,
              padding: '12px 18px', cursor: 'pointer',
              border: '1px solid var(--ink)', background: 'var(--ink)', color: 'var(--paper)',
              display: 'inline-flex', alignItems: 'center', gap: 9, borderRadius: 0,
            }}>
            {LockIcon && <LockIcon size={12} />} Unlock
          </button>
          <button type="button" onClick={close} className="mono"
            style={{
              fontFamily: 'var(--font-mono)', fontSize: 13, letterSpacing: 0.2,
              padding: '12px 14px', cursor: 'pointer',
              border: '1px solid var(--rule)', background: 'transparent', color: 'var(--ink-2)',
              borderRadius: 0, transition: 'border-color .15s, color .15s',
            }}
            onMouseEnter={(e) => { e.currentTarget.style.borderColor = 'var(--ink)'; e.currentTarget.style.color = 'var(--ink)'; }}
            onMouseLeave={(e) => { e.currentTarget.style.borderColor = 'var(--rule)'; e.currentTarget.style.color = 'var(--ink-2)'; }}>
            Cancel
          </button>
          <span className="mono" style={{ marginLeft: 'auto', fontSize: 10.5, color: 'var(--ink-3)', letterSpacing: 0.2, textAlign: 'right', maxWidth: 120, lineHeight: 1.4 }}>
            Stays unlocked on this device
          </span>
        </div>
      </form>
    </div>
  );
}

window.PhotoGate = {
  isUnlocked: pgIsUnlocked,
  unlock: pgUnlock,
  lock: pgLock,
  isGated: pgIsGated,
  isLocked: pgIsLocked,
  slug: PHOTO_GATE_SLUG,
  isAccessHash: pgIsAccessHash,
};
window.usePhotoGate = usePhotoGate;
window.PhotoGateModal = PhotoGateModal;
