// app.jsx — main App: path routing, screen rendering, Tweaks, and per-screen SEO.
const { useState: useStateA, useEffect: useEffectA } = React;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "accent": "#1a1a18",
  "headerFont": "helvetica",
  "density": "regular",
  "showMood": true
}/*EDITMODE-END*/;

// ─── URL routing ───────────────────────────────────────────────────────────
// Real paths, no '#'. vercel.json rewrites anything that isn't a file or an
// /api/* function to index.html, so this router is the only thing that reads
// a URL — and every address it produces is one you could paste into a browser:
//
//   /                          home
//   /explore  ·  /explore/<style>
//   /creators ·  /creators/<username>
//   /brands   ·  /brands/<slug>
//   /contact /press /privacy /terms
//   /login /signup /forgot-password
//   /studio  ·  /studio/create  ·  /my-outfits      (signed in)
//   /<username>/<outfit-slug>                       a published outfit
//   /<username>                                     an old profile link
//   anything else                                   the not-found page
window.__isKnownStyle = function (slug) {
  if (!slug) return false;
  if (window.STYLE_PAGES && window.STYLE_PAGES[slug]) return true;
  const collapsed = slug.replace(/[^a-z0-9]/gi, '').toLowerCase();
  const D = window.DRIPCHECK_DATA || {};
  const pool = [...(D.MOODS || []), ...(D.MOOD_LIBRARY || [])];
  return pool.some(m => m.replace(/[^a-z0-9]/gi, '').toLowerCase() === collapsed);
};

// One table, read in both directions: a path is parsed by looking it up, and
// a screen's address is the same row read backwards. They cannot drift.
const STATIC_ROUTES = [
  ['/',                'home'],
  ['/explore',         'explore'],
  ['/creators',        'creators'],
  ['/brands',          'brands'],
  ['/contact',         'contact'],
  ['/press',           'press'],
  ['/privacy',         'privacy'],
  ['/terms',           'terms'],
  ['/login',           'login'],
  ['/signup',          'signup'],
  ['/forgot-password', 'forgot'],
  ['/studio',          'studio'],
  ['/studio/create',   'dashboard'],
  ['/my-outfits',      'myoutfits'],
];
const PATH_TO_SCREEN = new Map(STATIC_ROUTES);
const SCREEN_TO_PATH = new Map(STATIC_ROUTES.map(([path, name]) => [name, path]));

// Paths that are never a username. Mirrors mb_username_reserved() in
// supabase/schema.sql — that function is the real guard; this is so the SPA
// doesn't try to resolve one as a profile.
const RESERVED_PATHS = new Set([
  'api', 'assets', 'static', 'public', 'admin', 'login', 'signup', 'logout',
  'signin', 'signout', 'settings', 'studio', 'dashboard', 'explore', 'creators',
  'brands', 'press', 'privacy', 'terms', 'contact', 'about', 'help', 'support',
  'account', 'profile', 'outfit', 'outfits', 'my-outfits', 'forgot-password',
  'new', 'edit', 'index', 'favicon', 'robots', 'sitemap', 'well-known',
  'modaboard', 'www',
]);

// Usernames and slugs are [a-z0-9-] by database constraint, so anything else
// in a segment means the URL was never one of ours.
const PATH_SEGMENT = /^[a-z0-9][a-z0-9-]*$/i;

// A path always resolves to something — the not-found screen is a screen, not
// a null. That is what makes an unknown address render a page instead of
// silently falling back to the homepage.
function parsePath(pathname) {
  const raw = String(pathname || '/').replace(/\/+$/, '') || '/';
  let parts;
  try { parts = raw.split('/').filter(Boolean).map(decodeURIComponent); }
  catch (e) { return { name: 'notfound' }; }

  const lower = parts.map(p => p.toLowerCase());
  const staticName = PATH_TO_SCREEN.get('/' + lower.join('/'));
  if (staticName) return { name: staticName };

  // Past the static routes, every segment has to look like a username or a
  // slug. That rules out a dot, which means a file: the rewrite hands those to
  // the filesystem first, so one arriving here is an asset that isn't there.
  if (parts.some(p => !PATH_SEGMENT.test(p))) return { name: 'notfound' };

  if (lower[0] === 'explore' && parts.length === 2) {
    // An unknown style still renders Explore, with a note — the grid is the
    // useful answer, and the URL is corrected to /explore.
    return window.__isKnownStyle(lower[1])
      ? { name: 'explore', slug: lower[1] }
      : { name: 'explore', slug: null, _redirected: true };
  }
  if (lower[0] === 'creators' && parts.length === 2) {
    return RESERVED_PATHS.has(lower[1]) ? { name: 'notfound' } : { name: 'profile', username: lower[1] };
  }
  if (lower[0] === 'brands' && parts.length === 2) return { name: 'brand', slug: lower[1] };

  if (parts.length > 2 || RESERVED_PATHS.has(lower[0])) return { name: 'notfound' };
  if (parts.length === 2) return { name: 'share', username: lower[0], slug: lower[1] };
  // A bare /<username> is where profiles used to live. Still resolves, and the
  // URL effect rewrites it to the canonical /creators/<username>.
  return { name: 'profile', username: lower[0], _legacyProfilePath: true };
}

// The address a screen lives at. Screens with no address of their own (the
// verify/pending steps, the not-found page) return null.
function pathForScreen(screen) {
  if (!screen) return null;
  switch (screen.name) {
    case 'explore': return screen.slug ? '/explore/' + encodeURIComponent(screen.slug) : '/explore';
    case 'profile': return screen.username ? '/creators/' + encodeURIComponent(screen.username) : '/creators';
    case 'brand':   return screen.slug ? '/brands/' + encodeURIComponent(screen.slug) : '/brands';
    case 'share':   return screen.username && screen.slug
      ? '/' + encodeURIComponent(screen.username) + '/' + encodeURIComponent(screen.slug)
      : null;
    default:        return SCREEN_TO_PATH.get(screen.name) || null;
  }
}

// ─── Old hash links ────────────────────────────────────────────────────────
// modaboard.com/#/contact was the address for a year, and those links are out
// in the world. Translate one to its clean path before anything reads the URL,
// so a link someone shared then lands on the page it names — at the address
// that page keeps from now on.
const LEGACY_HASH_ROUTES = {
  '/': '/', '/explore': '/explore', '/login': '/login', '/signup': '/signup',
  '/forgot-password': '/forgot-password', '/contact': '/contact', '/press': '/press',
  '/privacy': '/privacy', '/terms': '/terms', '/studio': '/studio',
  '/my-outfits': '/my-outfits',
};
function pathFromLegacyHash(hash) {
  const raw = String(hash || '').replace(/^#/, '');
  // '#top' and friends are anchors on the page, not routes. Only '#/…' was
  // ever a route, so only that shape is rewritten.
  if (!raw.startsWith('/')) return null;
  const clean = (raw.replace(/\/+$/, '') || '/').toLowerCase();
  let m;
  if ((m = clean.match(/^\/explore\/([a-z0-9-]+)$/))) return '/explore/' + m[1];
  // #/creator/<handle> predates profiles owning a path of their own.
  if ((m = clean.match(/^\/creator\/([a-z0-9._-]+)$/))) return '/creators/' + m[1];
  return LEGACY_HASH_ROUTES[clean] || null;
}
function migrateLegacyHashUrl() {
  const to = pathFromLegacyHash(window.location.hash);
  if (!to) return false;
  // replaceState, not push: the hash URL was an alias for this page, not a
  // previous step, and Back should leave the site rather than bounce.
  window.history.replaceState(null, '', to + window.location.search);
  return true;
}
// Runs at load, before the first render reads window.location.
migrateLegacyHashUrl();

// Screens that require an approved creator session.
const PROTECTED = new Set(['studio', 'myoutfits']);

function App() {
  const session = useSession();
  const [screen, setScreenRaw] = useStateA(() => parsePath(window.location.pathname));
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [redirectToast, setRedirectToast] = useStateA(false);
  const [gateToast, setGateToast] = useStateA(false);

  const signedIn = !!session;

  // Guard: protected screens require an approved session; otherwise bounce.
  const guard = (next) => {
    if (PROTECTED.has(next.name) && !MBAuth.isSignedIn()) {
      setGateToast(true);
      setTimeout(() => setGateToast(false), 4000);
      return { name: 'login' };
    }
    return next;
  };

  const setScreen = (next) => {
    const resolved = typeof next === 'function' ? next(screen) : next;
    setScreenRaw(guard(resolved));
  };
  // Expose a global go() so auth-layout chrome (logo home button) can navigate.
  window.__dripcheckGo = setScreen;

  // ─── Sync URL ↔ screen ──────────────────────────────────────────────────
  useEffectA(() => {
    // The not-found page keeps the address that produced it — replacing it
    // would hide what the visitor actually typed or followed.
    if (screen.name === 'notfound') return;
    const desired = pathForScreen(screen) || '/';
    const current = window.location.pathname.replace(/\/+$/, '') || '/';
    // A leftover '#/contact' is ours to clear. '#access_token=…' from a
    // Supabase email link is not — supabase-js reads that fragment at load,
    // and stripping it here would depend on winning a race we shouldn't enter.
    const staleHash = !!window.location.hash && pathFromLegacyHash(window.location.hash) !== null;
    if (current === desired && !staleHash) return;

    // Arriving at a URL that resolves to this very screen by another spelling
    // — an old bare /<username>, a trailing slash, a stale #/contact — is a
    // correction, not a step in the visitor's history. Replace it, or Back
    // would bounce them straight forward again.
    const at = parsePath(window.location.pathname);
    const correcting = at.name === screen.name
      && String(at.username || '') === String(screen.username || '')
      && String(at.slug || '') === String(screen.slug || '');
    window.history[correcting ? 'replaceState' : 'pushState'](null, '', desired + window.location.search);
  }, [screen]);

  // Listen for back/forward navigation.
  useEffectA(() => {
    const onPop = () => {
      // Someone may have pasted an old hash link into the address bar.
      migrateLegacyHashUrl();
      const next = parsePath(window.location.pathname);
      setScreenRaw(guard(next));
      if (next._redirected) {
        setRedirectToast(true);
        setTimeout(() => setRedirectToast(false), 4000);
      }
    };
    window.addEventListener('hashchange', onPop);
    window.addEventListener('popstate', onPop);
    return () => {
      window.removeEventListener('hashchange', onPop);
      window.removeEventListener('popstate', onPop);
    };
  }, [screen.name]);

  // On boot: redirect notice for unknown slug, and guard a deep-linked protected route.
  useEffectA(() => {
    const at = parsePath(window.location.pathname);
    if (at._redirected) {
      setRedirectToast(true);
      const tm = setTimeout(() => setRedirectToast(false), 4000);
      return () => clearTimeout(tm);
    }
    if (PROTECTED.has(at.name)) {
      const guarded = guard(at);
      if (guarded.name !== at.name) setScreenRaw(guarded);
    }
  }, []);

  // Scroll to top on nav.
  useEffectA(() => { window.scrollTo({ top: 0, behavior: 'instant' }); }, [screen]);

  // Per-screen SEO meta (title, description, canonical, OG/Twitter).
  useEffectA(() => { if (window.applyScreenMeta) window.applyScreenMeta(screen); }, [screen]);

  // Apply font tweak as CSS var
  const fontStack = t.headerFont === 'serif'
    ? "'Instrument Serif', Georgia, serif"
    : t.headerFont === 'grotesk'
    ? "'Space Grotesk', 'Helvetica Neue', Helvetica, Arial, sans-serif"
    : "'Helvetica Neue', Helvetica, Arial, sans-serif";

  // Chrome (nav + tweaks) hidden on studio + all full-bleed auth screens.
  const fullBleed = ['studio', 'login', 'signup', 'forgot', 'verify', 'pending', 'rejected', 'share'].includes(screen.name);

  return (
    <div data-screen-label={screen.name + (screen.slug ? '-' + screen.slug : '')} style={{ minHeight: '100vh', fontFamily: "'Helvetica Neue', Helvetica, Arial, sans-serif", color: '#1a1a18', background: '#FAFAF7' }}>
      <style>{`
        body { margin: 0; background: #FAFAF7; }
        h1, h2, h3 { font-family: ${fontStack}; text-wrap: pretty; }
        button:hover { filter: brightness(.97); }
        a { color: inherit; }
        ::selection { background: #1a1a18; color: #FAFAF7; }
      `}</style>
      {!fullBleed && <Nav screen={screen} go={setScreen} signedIn={signedIn} onSignOut={async () => { await MBAuth.signOut(); setScreen({ name: 'home' }); }} onStudio={() => setScreen({ name: 'studio' })} />}
      {screen.name === 'home' && <HomeScreen go={setScreen} density={t.density} />}
      {screen.name === 'explore' && <ExploreScreen go={setScreen} density={t.density} slug={screen.slug}/>}
      {screen.name === 'outfit' && <OutfitDetailScreen outfitId={screen.id} go={setScreen} />}
      {screen.name === 'profile' && <ProfileScreen username={screen.username} go={setScreen} />}
      {screen.name === 'creators' && <CreatorsScreen go={setScreen} />}
      {screen.name === 'brands' && <BrandsScreen go={setScreen} />}
      {screen.name === 'brand' && <BrandDetailScreen slug={screen.slug} go={setScreen} />}
      {screen.name === 'contact' && <ContactScreen go={setScreen} />}
      {screen.name === 'press' && <PressScreen go={setScreen} />}
      {screen.name === 'privacy' && <PrivacyScreen go={setScreen} />}
      {screen.name === 'terms' && <TermsScreen go={setScreen} />}
      {screen.name === 'dashboard' && <DashboardScreen go={setScreen} editId={screen.editId} savedId={screen.savedId} />}
      {screen.name === 'myoutfits' && <MyOutfitsScreen go={setScreen} />}
      {screen.name === 'share' && <SharedOutfitScreen username={screen.username} slug={screen.slug} go={setScreen} />}
      {screen.name === 'notfound' && <NotFoundScreen go={setScreen} />}
      {screen.name === 'studio' && <StudioScreen go={setScreen} exitStudio={() => setScreen({ name: 'home' })} initialView={screen.view || 'overview'} />}

      {/* Auth + onboarding */}
      {screen.name === 'login' && <LoginScreen go={setScreen} />}
      {screen.name === 'signup' && <SignupScreen go={setScreen} />}
      {screen.name === 'forgot' && <ForgotPasswordScreen go={setScreen} />}
      {screen.name === 'verify' && <VerifyEmailScreen go={setScreen} />}
      {screen.name === 'pending' && <PendingApprovalScreen go={setScreen} />}
      {screen.name === 'rejected' && <RejectedScreen go={setScreen} />}

      {/* Style-not-found notice (auto-dismiss) */}
      {redirectToast && (
        <Toast text="Style page not found — showing all outfits."/>
      )}
      {gateToast && (
        <Toast text="Please log in to access your Studio."/>
      )}

      {!fullBleed && (
      <TweaksPanel>
        <TweakSection label="Layout" />
        <TweakRadio label="Grid density" value={t.density}
                    options={['compact', 'regular', 'comfy']}
                    onChange={(v) => setTweak('density', v)} />
        <TweakSection label="Typography" />
        <TweakSelect label="Headline font" value={t.headerFont}
                     options={[
                       { value: 'helvetica', label: 'Helvetica Neue (default)' },
                       { value: 'serif', label: 'Instrument Serif (editorial)' },
                       { value: 'grotesk', label: 'Space Grotesk (modern)' },
                     ]}
                     onChange={(v) => setTweak('headerFont', v)} />
        <TweakSection label="Outfit card" />
        <TweakToggle label="Show mood tag" value={t.showMood}
                     onChange={(v) => setTweak('showMood', v)} />
      </TweaksPanel>
      )}
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);

// Reusable bottom toast.
function Toast({ text }) {
  return (
    <div style={{
      position: 'fixed', bottom: 24, left: '50%', transform: 'translateX(-50%)',
      padding: '12px 18px', borderRadius: 999, background: '#1a1a18',
      color: '#FAFAF7', fontSize: 12.5, letterSpacing: '0.01em',
      display: 'flex', alignItems: 'center', gap: 10,
      boxShadow: '0 16px 40px rgba(20,20,18,0.18)', zIndex: 1000,
    }}>
      <span style={{ fontFamily: "'Instrument Serif',serif", fontStyle: 'italic', opacity: 0.6 }}>·</span>
      <span>{text}</span>
    </div>
  );
}
