// ─────────────────────────────────────────────────────────────
// LifeWheel Coach — hash router for SPA navigation.
// One HTML page, all JSX compiled once, internal nav is React-only.
//
// Route shape: { name, params }
//   #/dashboard               → { name: 'dashboard' }
//   #/client/<userUid>        → { name: 'client', params: { id } }
//   #/session?clientId=&sessionId=
//                              → { name: 'session', params: { clientId, sessionId } }
//   #/calendar                → { name: 'calendar' }
//   #/settings#account         → { name: 'settings', params: { tab: 'account' } }
//   #/invite                  → { name: 'invite' }
//   #/login                   → { name: 'login' } (un-gated)
//   #/signup                  → { name: 'signup' } (un-gated)
//   #/claim/<token>           → { name: 'claim', params: { token } } (un-gated)
//
// Default → dashboard.
// ─────────────────────────────────────────────────────────────

const _routeSubs = new Set();

function parseRoute() {
  const raw = window.location.hash.replace(/^#/, '') || '/dashboard';
  // Split route from query: "/foo/bar?x=1" → path "/foo/bar", search "x=1"
  const qIdx = raw.indexOf('?');
  const path  = qIdx >= 0 ? raw.slice(0, qIdx) : raw;
  const query = qIdx >= 0 ? raw.slice(qIdx + 1) : '';
  const params = {};
  if (query) {
    const sp = new URLSearchParams(query);
    sp.forEach((v, k) => { params[k] = v; });
  }
  // Match patterns
  const segs = path.split('/').filter(Boolean); // ['client','abc'] etc.
  if (!segs.length || segs[0] === 'dashboard') return { name: 'dashboard', params };
  if (segs[0] === 'clients')              return { name: 'clients',  params };
  if (segs[0] === 'client'   && segs[1])  return { name: 'client',   params: { ...params, id: decodeURIComponent(segs[1]) } };
  if (segs[0] === 'session')              return { name: 'session',  params };
  if (segs[0] === 'calendar')             return { name: 'calendar', params };
  if (segs[0] === 'feed')                 return { name: 'feed',     params };
  if (segs[0] === 'settings') {
    // Support /settings/<tab> as well as #/settings#tab
    const tab = segs[1] ? decodeURIComponent(segs[1]) : (params.tab || 'account');
    return { name: 'settings', params: { ...params, tab } };
  }
  if (segs[0] === 'invite')               return { name: 'invite',   params };
  if (segs[0] === 'marathon')             return { name: 'marathon', params };
  if (segs[0] === 'cms')                  return { name: 'cms',      params };
  if (segs[0] === 'login')                return { name: 'login',    params };
  if (segs[0] === 'signup')               return { name: 'signup',   params };
  if (segs[0] === 'claim'    && segs[1])  return { name: 'claim',    params: { ...params, token: decodeURIComponent(segs[1]) } };
  return { name: 'dashboard', params };
}

function buildHash(name, params) {
  const p = params || {};
  switch (name) {
    case 'dashboard': return '#/dashboard';
    case 'clients':   return '#/clients';
    case 'client':    return '#/client/' + encodeURIComponent(p.id || '');
    case 'session': {
      const sp = new URLSearchParams();
      if (p.clientId)  sp.set('clientId',  p.clientId);
      if (p.sessionId) sp.set('sessionId', p.sessionId);
      const q = sp.toString();
      return '#/session' + (q ? '?' + q : '');
    }
    case 'calendar':  return '#/calendar';
    case 'feed':      return '#/feed';
    case 'settings':  return '#/settings' + (p.tab ? '/' + encodeURIComponent(p.tab) : '');
    case 'invite':    return '#/invite';
    case 'marathon':  return '#/marathon';
    case 'cms':       return '#/cms' + (p.cohortId ? '/' + encodeURIComponent(p.cohortId) : '');
    case 'login':     return '#/login' + (p.next ? '?next=' + encodeURIComponent(p.next) : '');
    case 'signup':    return '#/signup';
    case 'claim':     return '#/claim/' + encodeURIComponent(p.token || '');
    default:          return '#/dashboard';
  }
}

// Programmatic navigation. Updates hash and notifies subscribers.
// `replace` = true uses history.replaceState (no new entry in history).
function navigate(name, params, opts = {}) {
  const hash = typeof name === 'string' && name.startsWith('#') ? name : buildHash(name, params);
  const nextUrl = window.location.pathname + window.location.search + hash;
  if (opts.replace) {
    history.replaceState(null, '', nextUrl);
  } else {
    history.pushState(null, '', nextUrl);
  }
  // hashchange doesn't fire on programmatic same-hash, but we always update via
  // pushState/replaceState so we manually broadcast.
  _routeSubs.forEach(cb => { try { cb(parseRoute()); } catch (e) { console.error(e); } });
}

// Subscribe to route changes (manual + back/forward).
window.addEventListener('hashchange', () => {
  _routeSubs.forEach(cb => { try { cb(parseRoute()); } catch (e) { console.error(e); } });
});
window.addEventListener('popstate', () => {
  _routeSubs.forEach(cb => { try { cb(parseRoute()); } catch (e) { console.error(e); } });
});

// Hook: returns the current route, updates on change.
function useRoute() {
  const [route, setRoute] = React.useState(() => parseRoute());
  React.useEffect(() => {
    const cb = (r) => setRoute(r);
    _routeSubs.add(cb);
    return () => _routeSubs.delete(cb);
  }, []);
  return route;
}

// Map an old-style URL ("Dashboard.html", "Client.html?id=abc", etc.) to the
// matching SPA hash route. Used by:
//   1. Link component, so existing JSX with href="Client.html?id=X" still works.
//   2. The redirect-shim HTMLs that bounce old direct URLs into the SPA.
function legacyHrefToHash(href) {
  if (!href || typeof href !== 'string') return null;
  // Already a hash route
  if (href.startsWith('#')) return href;
  // Anchor-only on same page
  if (href.startsWith('#')) return href;
  try {
    const url = new URL(href, window.location.origin + window.location.pathname);
    const path = url.pathname.toLowerCase();
    const sp = url.searchParams;
    if (path.endsWith('/dashboard.html')) return '#/dashboard';
    if (path.endsWith('/client.html'))    return '#/client/' + (sp.get('id') || '');
    if (path.endsWith('/session.html')) {
      const ns = new URLSearchParams();
      if (sp.get('clientId'))  ns.set('clientId',  sp.get('clientId'));
      if (sp.get('sessionId')) ns.set('sessionId', sp.get('sessionId'));
      const q = ns.toString();
      return '#/session' + (q ? '?' + q : '');
    }
    if (path.endsWith('/calendar.html'))  return '#/calendar';
    if (path.endsWith('/settings.html'))  return '#/settings' + (url.hash || '');
    if (path.endsWith('/invite.html'))    return '#/invite';
    if (path.endsWith('/login.html'))     return '#/login' + (sp.get('next') ? '?next=' + encodeURIComponent(sp.get('next')) : '');
    if (path.endsWith('/signup.html'))    return '#/signup';
    if (path.endsWith('/claim.html'))     return '#/claim/' + (sp.get('token') || '');
  } catch {}
  return null;
}

// Drop-in replacement for <a href> that does in-app navigation when the link
// targets another SPA page. Falls through to plain anchor for external URLs.
function Link({ to, href, children, ...rest }) {
  const target = to || href;
  const onClick = (e) => {
    if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return; // let modifier-clicks open new tab
    if (rest.target === '_blank') return;
    const hashRoute = (typeof target === 'string' && target.startsWith('#')) ? target : legacyHrefToHash(target);
    if (!hashRoute) return; // external URL — full nav
    e.preventDefault();
    if (window.location.hash === hashRoute) return;
    history.pushState(null, '', window.location.pathname + window.location.search + hashRoute);
    _routeSubs.forEach(cb => { try { cb(parseRoute()); } catch (er) { console.error(er); } });
  };
  return React.createElement('a', { href: target, onClick, ...rest }, children);
}

// Global click interceptor — catches plain <a href="..."> in JSX we haven't
// rewritten to <Link>. Activated once at app boot.
function installLinkInterceptor() {
  document.addEventListener('click', (e) => {
    if (e.defaultPrevented) return;
    if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
    if (e.button !== 0) return; // left-click only
    let el = e.target;
    while (el && el.nodeName !== 'A') el = el.parentNode;
    if (!el || el.target === '_blank') return;
    const href = el.getAttribute('href');
    if (!href) return;
    const hashRoute = href.startsWith('#') ? href : legacyHrefToHash(href);
    if (!hashRoute) return;
    e.preventDefault();
    if (window.location.hash === hashRoute) return;
    history.pushState(null, '', window.location.pathname + window.location.search + hashRoute);
    _routeSubs.forEach(cb => { try { cb(parseRoute()); } catch (er) { console.error(er); } });
  }, true); // capture phase so we beat React's own listeners
}

// Navigate to a legacy-style href ("Client.html?id=X") via SPA when possible,
// otherwise full-page nav as a last resort. Use this instead of
// `window.location.href = '...'` from within JSX.
function go(href) {
  const hash = legacyHrefToHash(href);
  if (hash) {
    if (window.location.hash !== hash) {
      history.pushState(null, '', window.location.pathname + window.location.search + hash);
      _routeSubs.forEach(cb => { try { cb(parseRoute()); } catch (er) { console.error(er); } });
    }
    return true;
  }
  // Not a SPA-routable target — fall back.
  window.location.href = href;
  return false;
}

// Vanilla (non-React) subscription to route changes — for things like
// document.title sync that live outside React.
function onChange(cb) {
  _routeSubs.add(cb);
  return () => _routeSubs.delete(cb);
}

window.LWRouter = {
  parseRoute, buildHash, navigate, useRoute, Link, legacyHrefToHash, installLinkInterceptor, go, onChange,
};
