// Routes the production shell to the latest NEWNEW visual source components.
// Keep this thin: it prevents old prototype screens from leaking back into /app.html.

const { useEffect: useSourceEffect, useMemo: useSourceMemo, useState: useSourceState } = React;

const OWNER_ROUTES = {
  dashboard: "today",
  today: "today",
  calendar: "calendar",
  bookings: "bookings",
  customers: "customers",
  messages: "messages",
  storefront: "storefront",
  services: "services",
  staff: "staff",
  marketing: "marketing",
  insights: "insights",
  payouts: "payouts",
  settings: "settings",
};

const LOCAL_SESSION_KEY = "bookuno.local_test_session";
const LOCAL_BOOKINGS_KEY = "bookuno.local_customer_bookings";

function parseSourceHash() {
  const raw = window.location.hash.replace(/^#\/?/, "");
  const [path = "", query = ""] = raw.split("?");
  const clean = path.replace(/^app\//, "").replace(/^customer\//, "customer/");
  if (!clean) {
    // Path-based storefront URLs (/b/<slug>, via the vercel.json rewrite to
    // index.html) arrive with no hash — route them like #/b/<slug>.
    const m = window.location.pathname.match(/^\/(b|book|booking|storefront)\/([^/?#]+)/);
    if (m) return { path: `${m[1]}/${m[2]}`, query: window.location.search.replace(/^\?/, "") };
  }
  return {
    path: clean || "public",
    query,
  };
}

function sourceNavigate(path) {
  if (path.startsWith("/")) {
    window.location.href = path;
    return;
  }
  window.location.hash = `#/${path}`;
}

function sourceToast(message) {
  let toast = document.querySelector("[data-bookuno-toast]");
  if (!toast) {
    toast = document.createElement("div");
    toast.setAttribute("data-bookuno-toast", "true");
    Object.assign(toast.style, {
      position: "fixed",
      left: "50%",
      bottom: "22px",
      transform: "translateX(-50%)",
      zIndex: "99999",
      maxWidth: "min(92vw, 430px)",
      padding: "13px 16px",
      borderRadius: "16px",
      background: "rgba(15, 14, 26, 0.94)",
      color: "#fff",
      boxShadow: "0 18px 50px rgba(0,0,0,.22)",
      font: "800 14px 'Plus Jakarta Sans', system-ui, sans-serif",
      textAlign: "center",
      pointerEvents: "none",
      opacity: "0",
      transition: "opacity .18s ease, transform .18s ease",
    });
    document.body.appendChild(toast);
  }
  toast.textContent = message;
  toast.style.opacity = "1";
  toast.style.transform = "translateX(-50%) translateY(0)";
  clearTimeout(window.__bookunoSourceToastTimer);
  window.__bookunoSourceToastTimer = setTimeout(() => {
    toast.style.opacity = "0";
    toast.style.transform = "translateX(-50%) translateY(6px)";
  }, 2500);
}

async function sourceCopyCurrentUrl() {
  try {
    await navigator.clipboard?.writeText?.(window.location.href);
    sourceToast("Link copied.");
  } catch {
    sourceToast("Copy is blocked in this browser. You can copy the address bar instead.");
  }
}

function ownerUrl(route) {
  return `/business-app/app.html#${route}`;
}

function sourceLocalSession() {
  try {
    return JSON.parse(window.localStorage.getItem(LOCAL_SESSION_KEY) || "null");
  } catch {
    return null;
  }
}

// Capacitor native (iOS/Android) WebView. Social OAuth can't complete here yet
// (no capacitor:// deep-link handler), so native uses email/password only.
function sourceIsNative() {
  try {
    const C = window.Capacitor;
    if (!C) return false;
    if (typeof C.isNativePlatform === "function") return C.isNativePlatform();
    if (typeof C.getPlatform === "function") return C.getPlatform() !== "web";
    return false;
  } catch { return false; }
}

function sourceIsSignedIn() {
  return Boolean(sourceLocalSession()?.user?.email);
}

function sourceIsOwner() {
  const user = sourceLocalSession()?.user;
  const role = user?.user_metadata?.role || user?.app_metadata?.role || '';
  return role === 'owner' || role === 'business' || role === 'platform_admin';
}

function sourceIsPlatformAdmin() {
  const user = sourceLocalSession()?.user;
  const role = user?.user_metadata?.role || user?.app_metadata?.role || '';
  return role === 'platform_admin';
}

function sourceAccountType() {
  const params = new URLSearchParams(parseSourceHash().query);
  return params.get("type") === "business" ? "business" : "customer";
}

function sourceSessionRole(authResult) {
  const user = authResult?.user || authResult?.session?.user || sourceLocalSession()?.user;
  const role = user?.user_metadata?.role || user?.app_metadata?.role || "";
  return role === "owner" || role === "business" || role === "platform_admin" ? "business" : "customer";
}

function sourceNextBookingRoute() {
  return sourceIsSignedIn() ? "booking/start" : "auth?mode=register&next=booking/start";
}

function saveSourceBooking(payload) {
  const current = (() => {
    try {
      return JSON.parse(window.localStorage.getItem(LOCAL_BOOKINGS_KEY) || "[]");
    } catch {
      return [];
    }
  })();
  const row = {
    id: `BK-${Date.now()}`,
    createdAt: new Date().toISOString(),
    status: "Pending confirmation",
    businessName: "Knot & Comb",
    serviceName: payload.serviceName || "Skin fade",
    staffName: payload.staffName || "Mark",
    dateLabel: payload.dateLabel || "Today",
    timeLabel: payload.timeLabel || "14:30",
    priceLabel: payload.priceLabel || "£24",
    durationLabel: payload.durationLabel || "45 min",
    customerName: payload.customerName,
    customerEmail: payload.customerEmail,
    customerPhone: payload.customerPhone,
  };
  window.localStorage.setItem(LOCAL_BOOKINGS_KEY, JSON.stringify([row, ...current].slice(0, 20)));
  return row;
}

function SourceAuthPage({ mode = "login" }) {
  const isRegister = mode === "register";
  const accountType = sourceAccountType();
  const [notice, setNotice] = useSourceState("");
  const [busy, setBusy] = useSourceState(false);
  const [forgotMode, setForgotMode] = useSourceState(false);
  const [forgotDone, setForgotDone] = useSourceState(false);
  const [verifyEmail, setVerifyEmail] = useSourceState("");
  const showSocial = !sourceIsNative();

  async function submit(event) {
    event.preventDefault();
    if (busy) return;
    const form = new FormData(event.currentTarget);
    const name = String(form.get("name") || "").trim();
    const email = String(form.get("email") || "").trim().toLowerCase();
    const password = String(form.get("password") || "");
    const confirmPassword = String(form.get("confirmPassword") || "");
    const next = new URLSearchParams(parseSourceHash().query).get("next");

    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
      setNotice("Please enter a valid email address.");
      return;
    }
    if (password.length < 8) {
      setNotice("Password must be at least 8 characters.");
      return;
    }
    if (isRegister && password !== confirmPassword) {
      setNotice("Passwords do not match. Please check both password fields.");
      return;
    }

    setBusy(true);
    setNotice("");
    try {
      const api = window.BOOKUNO_API;
      if (!api) throw new Error("Bookuno is still loading. Please try again.");
      let authResult = null;
      if (isRegister) {
        authResult = await api.signUp?.({
          email,
          password,
          name: name || email.split("@")[0],
          role: accountType === "business" ? "owner" : "customer",
        });
      } else {
        authResult = await api.signIn?.(email, password);
      }
      // Registration with email confirmation enabled returns no session — the
      // user must verify their email before they can sign in. Surface that
      // instead of silently bouncing them back to a logged-out home page.
      if (isRegister && authResult && !authResult.session && !authResult.localTest) {
        setVerifyEmail(email);
        return;
      }
      const signedInType = sourceSessionRole(authResult);
      if (signedInType === "business" || accountType === "business") {
        sourceNavigate(isRegister ? "business/start" : "/business-app/app.html#today");
      } else {
        sourceNavigate(next || "public");
      }
    } catch (error) {
      setNotice(error?.message || "We could not sign you in. Please check the details and try again.");
    } finally {
      setBusy(false);
    }
  }

  async function submitForgot(event) {
    event.preventDefault();
    if (busy) return;
    const email = String(new FormData(event.currentTarget).get("email") || "").trim().toLowerCase();
    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { setNotice("Please enter a valid email address."); return; }
    setBusy(true); setNotice("");
    try {
      const api = window.BOOKUNO_API;
      await api?.resetPassword?.(email);
      setForgotDone(true);
    } catch(e) {
      setNotice(e?.message || "Could not send reset email. Please try again.");
    } finally { setBusy(false); }
  }

  async function signInWithProvider(provider) {
    setNotice(`Opening ${provider === "apple" ? "Apple" : "Google"} sign-in...`);
    try {
      const signIn = window.BOOKUNO_API?.signInWithProvider || window.BOOKUNO_API?.auth?.signInWithProvider;
      if (!signIn) {
        throw new Error("OAuth is still loading. Please try again in a moment.");
      }
      await signIn(provider, {
        audience: accountType,
        returnRoute: accountType === "business" ? "business/start" : "public",
      });
    } catch (error) {
      setNotice(error?.message || `${provider} sign-in is not ready yet.`);
    }
  }

  if (verifyEmail) {
    return (
      <div className="source-auth">
        <div className="source-auth-shell">
          <BULogo variant="default" size={32}/>
          <div className="source-auth-card">
            <span className="badge pri">Verify your email</span>
            <h1>Almost there.</h1>
            <div style={{ textAlign: 'center', padding: '16px 0' }}>
              <div style={{ fontSize: 36, marginBottom: 12 }}>📬</div>
              <div style={{ fontWeight: 700, marginBottom: 8 }}>Check your inbox</div>
              <p style={{ color: 'var(--muted)', fontSize: 14, marginBottom: 20 }}>
                We've sent a confirmation link to <strong>{verifyEmail}</strong>. Click it to activate your account, then sign in.
              </p>
              <button className="btn primary lg block" type="button" onClick={() => { setVerifyEmail(""); sourceNavigate("auth"); }}>Back to sign in</button>
            </div>
            {notice && <div className="source-auth-note">{notice}</div>}
          </div>
        </div>
      </div>
    );
  }

  if (forgotMode) {
    return (
      <div className="source-auth">
        <div className="source-auth-shell">
          <BULogo variant="default" size={32}/>
          <div className="source-auth-card">
            <span className="badge pri">Reset password</span>
            <h1>Forgot your password?</h1>
            <p>Enter your email and we'll send a reset link.</p>
            {forgotDone ? (
              <div style={{ textAlign: 'center', padding: '24px 0' }}>
                <div style={{ fontSize: 36, marginBottom: 12 }}>📬</div>
                <div style={{ fontWeight: 700, marginBottom: 8 }}>Check your email</div>
                <p style={{ color: 'var(--muted)', fontSize: 14, marginBottom: 20 }}>A password reset link has been sent if that account exists.</p>
                <button className="btn outline lg block" type="button" onClick={() => { setForgotMode(false); setForgotDone(false); setNotice(""); }}>Back to sign in</button>
              </div>
            ) : (
              <form className="source-auth-form" onSubmit={submitForgot}>
                <label>Email<input name="email" required type="email" autoComplete="email" placeholder="you@example.com"/></label>
                <div className="source-auth-actions">
                  <button className="btn primary lg block" type="submit" disabled={busy}>{busy ? "Sending…" : "Send reset link"}</button>
                  <button className="btn outline lg block" type="button" onClick={() => { setForgotMode(false); setNotice(""); }}>Back to sign in</button>
                </div>
              </form>
            )}
            {notice && <div className="source-auth-note">{notice}</div>}
          </div>
        </div>
      </div>
    );
  }

  return (
    <div className="source-auth">
      <div className="source-auth-shell">
        <BULogo variant="default" size={32}/>
        <div className="source-auth-card">
          <span className="badge pri">{accountType === "business" ? "Business account" : isRegister ? "Create account" : "Welcome back"}</span>
          <h1>{accountType === "business" ? "Set up your business." : isRegister ? "Start booking calmly." : "Sign in to Bookuno."}</h1>
          <p>{accountType === "business" ? "Create a workspace, choose your services and open your Bookuno dashboard." : isRegister ? "Create a customer account to book trusted local businesses and manage every appointment in one place." : "Use your Bookuno account to browse, book, reschedule and manage appointments."}</p>
          <form className="source-auth-form" onSubmit={submit}>
            {isRegister && (
              <label>
                Name
                <input name="name" required autoComplete="name" placeholder={accountType === "business" ? "Business owner name" : "Sam Fowler"}/>
              </label>
            )}
            <label>
              Email
              <input name="email" required type="email" autoComplete="email" placeholder="you@example.com"/>
            </label>
            <label>
              Password
              <input name="password" required type="password" autoComplete={isRegister ? "new-password" : "current-password"} placeholder="••••••••"/>
            </label>
            {!isRegister && (
              <div style={{ textAlign: 'right', marginTop: -8, marginBottom: 4 }}>
                <button type="button" className="btn ghost" style={{ fontSize: 13, padding: '2px 0', textDecoration: 'underline', color: 'var(--muted)' }}
                  onClick={() => { setForgotMode(true); setNotice(""); }}>Forgot password?</button>
              </div>
            )}
            {isRegister && (
              <label>
                Confirm password
                <input name="confirmPassword" required type="password" autoComplete="new-password" placeholder="••••••••"/>
              </label>
            )}
            <div className="source-auth-actions">
              {showSocial && (
                <div className="source-social-row">
                  <button className="btn outline lg block" type="button" onClick={() => signInWithProvider("google")}>
                    <Ic.google style={{ width: 18, height: 18 }}/> Continue with Google
                  </button>
                  <button className="btn outline lg block" type="button" onClick={() => signInWithProvider("apple")}>
                    <Ic.apple style={{ width: 18, height: 18 }}/> Continue with Apple
                  </button>
                </div>
              )}
              <button className="btn primary lg block" type="submit" disabled={busy}>{busy ? "Please wait..." : isRegister ? "Create account" : "Sign in"}</button>
              <button className="btn outline lg block" type="button" onClick={() => sourceNavigate(isRegister ? `auth${accountType === "business" ? "?type=business" : ""}` : `auth?mode=register${accountType === "business" ? "&type=business" : ""}`)}>
                {isRegister ? "Already have an account? Sign in" : "New here? Create account"}
              </button>
              <button className="btn ghost lg block" type="button" onClick={() => sourceNavigate("auth?mode=register&type=business")}>Set up a business</button>
            </div>
          </form>
          {notice && <div className="source-auth-note">{notice}</div>}
        </div>
      </div>
    </div>
  );
}

function PageResetPassword() {
  const [busy, setBusy] = useSourceState(false);
  const [done, setDone] = useSourceState(false);
  const [notice, setNotice] = useSourceState("");

  async function submit(event) {
    event.preventDefault();
    if (busy) return;
    const form = new FormData(event.currentTarget);
    const password = String(form.get("password") || "");
    const confirm = String(form.get("confirmPassword") || "");
    if (password.length < 8) { setNotice("Password must be at least 8 characters."); return; }
    if (password !== confirm) { setNotice("Passwords do not match. Please check both fields."); return; }
    setBusy(true); setNotice("");
    try {
      const api = window.BOOKUNO_API;
      if (!api?.updatePassword) throw new Error("Bookuno is still loading. Please try again.");
      await api.updatePassword(password);
      setDone(true);
      window.setTimeout(() => sourceNavigate("account"), 1200);
    } catch (error) {
      const msg = error?.message || "";
      setNotice(/session|token|expired|missing/i.test(msg)
        ? "This reset link has expired or is invalid. Please request a new password reset email."
        : (msg || "Could not update your password. Please try again."));
    } finally {
      setBusy(false);
    }
  }

  return (
    <div className="source-auth">
      <div className="source-auth-shell">
        <BULogo variant="default" size={32}/>
        <div className="source-auth-card">
          <span className="badge pri">Reset password</span>
          <h1>Choose a new password.</h1>
          {done ? (
            <div style={{ textAlign: 'center', padding: '24px 0' }}>
              <div style={{ fontSize: 36, marginBottom: 12 }}>✅</div>
              <div style={{ fontWeight: 700, marginBottom: 8 }}>Password updated</div>
              <p style={{ color: 'var(--muted)', fontSize: 14 }}>You're signed in. Taking you to your account…</p>
            </div>
          ) : (
            <>
              <p>Enter a new password for your Bookuno account.</p>
              <form className="source-auth-form" onSubmit={submit}>
                <label>New password<input name="password" required type="password" autoComplete="new-password" placeholder="••••••••"/></label>
                <label>Confirm new password<input name="confirmPassword" required type="password" autoComplete="new-password" placeholder="••••••••"/></label>
                <div className="source-auth-actions">
                  <button className="btn primary lg block" type="submit" disabled={busy}>{busy ? "Saving…" : "Update password"}</button>
                  <button className="btn outline lg block" type="button" onClick={() => sourceNavigate("auth")}>Back to sign in</button>
                </div>
              </form>
            </>
          )}
          {notice && <div className="source-auth-note">{notice}</div>}
        </div>
      </div>
    </div>
  );
}

function PageBookingStart() {
  const session = sourceLocalSession();
  const userName = session?.user?.name || session?.user?.user_metadata?.full_name || "";
  const userEmail = session?.user?.email || "";
  const [notice, setNotice] = useSourceState("");

  function submit(event) {
    event.preventDefault();
    const form = new FormData(event.currentTarget);
    saveSourceBooking({
      customerName: String(form.get("name") || "").trim(),
      customerEmail: String(form.get("email") || "").trim(),
      customerPhone: String(form.get("phone") || "").trim(),
    });
    setNotice("Booking request saved. It is now visible in your bookings and the business dashboard for this local run.");
    window.setTimeout(() => sourceNavigate("account"), 450);
  }

  return (
    <div className="web">
      <TopNav active="discover" logoVariant="default" loggedIn/>
      <section className="container" style={{ paddingTop: 36, paddingBottom: 72 }}>
        <div className="card" style={{ maxWidth: 760, margin: "0 auto", padding: 24 }}>
          <span className="badge pri">Booking request</span>
          <h1 style={{ fontSize: "clamp(30px, 6vw, 52px)", lineHeight: 1, letterSpacing: "-0.04em", marginTop: 14 }}>Confirm your haircut.</h1>
          <p style={{ color: "var(--muted)", fontSize: 16, lineHeight: 1.55, marginTop: 10 }}>This local-safe flow creates a test booking request without taking payment or sending notifications.</p>
          <div style={{ display: "grid", gap: 12, marginTop: 22 }}>
            {[
              ["Business", "Knot & Comb"],
              ["Service", "Skin fade"],
              ["When", "Today · 14:30 · 45 min"],
              ["Price", "£24 · pay the business directly"],
            ].map(([label, value]) => (
              <div key={label} style={{ display: "flex", justifyContent: "space-between", gap: 16, padding: "14px 0", borderBottom: "1px solid var(--line)" }}>
                <span style={{ color: "var(--muted)", fontWeight: 700 }}>{label}</span>
                <strong style={{ textAlign: "right" }}>{value}</strong>
              </div>
            ))}
          </div>
          <form className="source-auth-form" onSubmit={submit} style={{ marginTop: 22 }}>
            <label>
              Name
              <input name="name" required defaultValue={userName} placeholder="Sam Fowler"/>
            </label>
            <label>
              Email
              <input name="email" required type="email" defaultValue={userEmail} placeholder="you@example.com"/>
            </label>
            <label>
              Phone
              <input name="phone" required inputMode="tel" placeholder="07123 456789"/>
            </label>
            <button className="btn primary lg block" type="submit">Request booking</button>
            <button className="btn outline lg block" type="button" onClick={() => sourceNavigate("b/sharp-edge")}>Back to storefront</button>
          </form>
          {notice && <div className="source-auth-note">{notice}</div>}
        </div>
      </section>
      <Footer logoVariant="default"/>
    </div>
  );
}

function PageSetupPayouts() {
  const [busy, setBusy] = useSourceState(false);
  const [error, setError] = useSourceState('');

  async function connectStripe() {
    setBusy(true);
    setError('');
    try {
      const result = await window.BOOKUNO_API.connectStripe();
      const redirectUrl = result?.redirect || result?.url;
      if (result?.mocked) {
        // local test mode — no real redirect
      } else if (redirectUrl && (redirectUrl.startsWith('https://connect.stripe.com') || redirectUrl.startsWith('https://stripe.com'))) {
        window.location.href = redirectUrl;
      } else if (redirectUrl) {
        // Unexpected redirect URL — log and use safe fallback
        console.warn('Bookuno: unexpected Stripe redirect URL domain, using safe fallback');
        window.location.href = 'https://connect.stripe.com';
      } else {
        throw new Error('Could not get Stripe onboarding link. Please try again.');
      }
    } catch (err) {
      setError(err?.message || 'Something went wrong. Please try again.');
      setBusy(false);
    }
  }

  return (
    <div className="source-auth">
      <div className="source-auth-shell">
        <BULogo variant="default" size={32}/>
        <div className="source-auth-card">
          <span className="badge pri">🎉 14-day free Pro trial started</span>
          <h1>Get paid for every booking.</h1>
          <p>Connect your bank account so customers can pay you instantly through Bookuno. Takes 2 minutes — handled securely by Stripe.</p>

          <div style={{ display: 'grid', gap: 10, margin: '20px 0' }}>
            {[
              ['💳', 'Customers pay online at checkout'],
              ['⚡', 'Funds arrive in your bank automatically'],
              ['🔒', 'Fully secure — Bookuno never stores card details'],
              ['📊', 'See every payout in your dashboard'],
            ].map(([icon, text]) => (
              <div key={text} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '10px 14px', borderRadius: 12, background: 'var(--chip)', fontSize: 14, fontWeight: 600 }}>
                <span style={{ fontSize: 18 }}>{icon}</span> {text}
              </div>
            ))}
          </div>

          {error && <div className="source-auth-note" style={{ marginBottom: 12 }}>{error}</div>}

          <div className="source-auth-actions">
            <button className="btn primary lg block" type="button" onClick={connectStripe} disabled={busy}>
              {busy ? 'Opening Stripe…' : 'Connect bank account →'}
            </button>
            <button className="btn outline lg block" type="button" onClick={() => window.location.href = '/business-app/app.html#today'}>
              Skip for now
            </button>
          </div>
          <p style={{ fontSize: 12, color: 'var(--muted)', textAlign: 'center', marginTop: 14, lineHeight: 1.5 }}>
            Your 14-day free Pro trial is active. Your card won't be charged until the trial ends — cancel anytime.
          </p>
        </div>
      </div>
    </div>
  );
}

function OwnerRedirect({ to }) {
  useSourceEffect(() => {
    window.location.replace(ownerUrl(to));
  }, [to]);

  return (
    <div className="source-auth">
      <div className="source-auth-shell">
        <BULogo variant="default" size={32}/>
        <div className="source-auth-card">
          <span className="badge pri">Business workspace</span>
          <h1>Opening your dashboard.</h1>
          <p>Business routes now use the latest NEWNEW owner app shell.</p>
          <a className="btn primary lg block" href={ownerUrl(to)}>Open business app</a>
        </div>
      </div>
    </div>
  );
}

function ExternalRedirect({ href, title, body }) {
  useSourceEffect(() => {
    window.location.replace(href);
  }, [href]);

  return (
    <div className="source-auth">
      <div className="source-auth-shell">
        <BULogo variant="default" size={32}/>
        <div className="source-auth-card">
          <span className="badge pri">Bookuno</span>
          <h1>{title}</h1>
          <p>{body}</p>
          <a className="btn primary lg block" href={href}>Open now</a>
        </div>
      </div>
    </div>
  );
}

function SourceRouterApp() {
  // Handle Supabase auth callback hashes (magic links, email confirmation, OAuth)
  useSourceEffect(() => {
    const hash = window.location.hash;
    const searchParams = new URLSearchParams(window.location.search);
    const hasPkceCode = searchParams.get('code') && searchParams.get('state');
    // Supabase callback hashes are bare param strings (#access_token=…&type=signup),
    // never router paths (#/auth?type=business). Only treat non-route hashes as
    // callbacks — a plain `type=` substring match used to hijack every
    // "#/auth?…&type=business" signup link and redirect it to #/account.
    const isRouteHash = hash.startsWith('#/');
    const isCallbackHash = !isRouteHash && (hash.includes('access_token=') || /(^#|&)type=/.test(hash));
    const isRecovery = searchParams.get('bookuno_recovery') === '1' || (!isRouteHash && hash.includes('type=recovery'));
    if (!isCallbackHash && !hasPkceCode && !isRecovery) return;

    const api = window.BOOKUNO_API;
    const supabaseClient = window.BOOKUNO_SUPABASE || api?._supabase || api?.supabase || window.supabase;

    // Password recovery: once the recovery session is established (implicit hash
    // or PKCE code, auto-processed by supabase-js detectSessionInUrl), send the
    // user to the set-new-password screen rather than straight into the account.
    if (isRecovery) {
      const goReset = (() => {
        let done = false;
        return () => { if (done) return; done = true; window.location.replace('/app.html#/reset-password'); };
      })();
      if (supabaseClient?.auth) {
        const { data: { subscription } = {} } = supabaseClient.auth.onAuthStateChange((event) => {
          if (event === 'PASSWORD_RECOVERY' || event === 'SIGNED_IN') { subscription?.unsubscribe?.(); goReset(); }
        }) || {};
        supabaseClient.auth.getSession().then(({ data }) => { if (data?.session) { subscription?.unsubscribe?.(); goReset(); } }).catch(() => {});
        window.setTimeout(goReset, 1500);
      } else {
        goReset();
      }
      return;
    }

    function finishOAuth(result) {
      const session = result?.data?.session || result?.session || result;
      const user = session?.user || result?.user;
      const role = user?.user_metadata?.role || user?.app_metadata?.role || '';
      const isOwner = role === 'owner' || role === 'business' || role === 'platform_admin';
      if (session) {
        try { window.localStorage.setItem('bookuno.local_test_session', JSON.stringify({ session, user })); } catch {}
      }
      const returnRoute = localStorage.getItem('bookuno.oauth_return_route');
      localStorage.removeItem('bookuno.oauth_return_route');
      const dest = returnRoute ? '/app.html#/' + returnRoute : (isOwner ? '/business-app/app.html#today' : '/app.html#/account');
      window.location.replace(dest);
    }

    if (hasPkceCode) {
      // PKCE flow: Supabase client auto-exchanges the code; wait for the session
      if (!supabaseClient) return;
      let handled = false;
      const { data: { subscription } } = supabaseClient.auth.onAuthStateChange((event, session) => {
        if (handled || event !== 'SIGNED_IN') return;
        handled = true;
        subscription?.unsubscribe?.();
        finishOAuth({ session, user: session?.user });
      });
      supabaseClient.auth.getSession().then(({ data: { session } }) => {
        if (handled || !session) return;
        handled = true;
        subscription?.unsubscribe?.();
        finishOAuth({ session, user: session?.user });
      }).catch(() => {});
      return;
    }

    // Implicit grant flow (access_token or type in hash)
    const params = new URLSearchParams(hash.replace(/^#/, ''));
    const type = params.get('type');
    const getSession = supabaseClient
      ? () => supabaseClient.auth.getSession()
      : api?.getSession?.bind(api);
    if (!getSession) return;
    getSession().then(result => {
      const session = result?.data?.session || result?.session || result;
      const user = session?.user || result?.user;
      const role = user?.user_metadata?.role || user?.app_metadata?.role || '';
      const isOwner = role === 'owner' || role === 'business' || role === 'platform_admin';
      if (session) {
        try { window.localStorage.setItem('bookuno.local_test_session', JSON.stringify({ session, user })); } catch {}
      }
      if (type === 'email_change') {
        window.location.replace('/app.html#/account');
      } else if (type === 'recovery') {
        window.location.replace(isOwner ? '/business-app/app.html#today' : '/app.html#/account');
      } else {
        // signup, magiclink, or OAuth (no type in hash)
        finishOAuth(result);
      }
    }).catch(err => console.warn('[bookuno] auth callback handler failed', err));
  }, []);

  const [route, setRoute] = useSourceState(parseSourceHash());

  useSourceEffect(() => {
    const sync = () => setRoute(parseSourceHash());
    window.addEventListener("hashchange", sync);
    window.addEventListener("popstate", sync);
    // Re-render auth gates when the signed-in marker changes: the boot-time auth
    // bridge (lib/bookuno-api.js) restores a Supabase session asynchronously, and
    // login/logout in another tab fires 'storage'. Without this a refreshed page
    // could render logged-out until the next navigation.
    window.addEventListener("bookuno-auth-changed", sync);
    const onStorage = (e) => { if (!e || e.key === LOCAL_SESSION_KEY) sync(); };
    window.addEventListener("storage", onStorage);
    return () => {
      window.removeEventListener("hashchange", sync);
      window.removeEventListener("popstate", sync);
      window.removeEventListener("bookuno-auth-changed", sync);
      window.removeEventListener("storage", onStorage);
    };
  }, []);

  // Dynamic page title based on hash route
  useSourceEffect(() => {
    function updateTitle() {
      const hash = window.location.hash.replace(/^#\/?/, '').split('?')[0];
      if (!hash || hash === '/' || hash === 'home') {
        document.title = 'Bookuno — Book anything instantly';
      } else if (hash === 'search' || hash.startsWith('search/')) {
        document.title = 'Find local services — Bookuno';
      } else if (hash === 'pricing') {
        document.title = 'Pricing — Bookuno';
      } else if (hash === 'business/start') {
        document.title = 'Start your free trial — Bookuno';
      } else if (hash === 'auth' || hash.startsWith('auth/')) {
        document.title = 'Sign in — Bookuno';
      } else if (hash.startsWith('b/')) {
        document.title = 'Book now — Bookuno';
      } else {
        document.title = 'Bookuno';
      }
    }
    updateTitle();
    window.addEventListener('hashchange', updateTitle);
    return () => window.removeEventListener('hashchange', updateTitle);
  }, []);

  useSourceEffect(() => {
    function onClick(event) {
      // Don't intercept clicks on real routes that have their own handlers
      const _currentPath = parseSourceHash().path;
      const _realRoutes = ['business/start', 'business/setup-payouts', 'onboarding', 'auth', 'register', 'billing', 'account', 'booking/start'];
      if (_realRoutes.some(r => _currentPath === r || _currentPath.startsWith(r + '/'))) return;

      const control = event.target.closest("a, button");
      if (!control) return;
      if (control.tagName === "BUTTON" && control.getAttribute("type") === "submit") return;
      // Don't intercept storefront inline booking buttons — they have their own React handlers
      if (event.target.closest('.storefront-service-book, #sf-booking-form, .sf-widget')) return;

      const href = control.getAttribute("href");
      const label = (control.textContent || control.getAttribute("aria-label") || "").trim().toLowerCase();
      const isLocalPlaceholder = href === "#" || href === "";
      if (!isLocalPlaceholder && href && !href.startsWith("#")) return;

      const utilityActions = [
        [/show all photos|1 \/ 24/, "Photo gallery is available in the live app view."],
        [/directions|public transport/, "Directions will open from the business address when maps are connected."],
        [/share/, null],
        [/heart|save/, sourceIsSignedIn() ? "Saved to your Bookuno favourites." : "Create an account to save this business."],
        [/see all \d+|reviews/, "Reviews are shown below on this storefront."],
        [/overview|services|team|policies/, "Section selected."],
      ];
      const utility = utilityActions.find(([pattern]) => pattern.test(label));
      if (utility) {
        event.preventDefault();
        if (/share/.test(label)) sourceCopyCurrentUrl();
        else sourceToast(utility[1]);
        return;
      }

      const rules = [
        [/continue.*opening hours|save & exit|save draft|finish setup|open dashboard/, "/business-app/app.html#today"],
        [/book a service|book again|see available|start booking|continue to book|book$|message|call/, sourceNextBookingRoute()],
        [/search|discover|categories|cities|businesses near|find/, "search"],
        [/my bookings|all bookings|saved|offers|notifications|messages|open(?! menu| nav| drawer)|reschedule|account|profile|privacy|payments|past|cancelled/, "account"],
        [/for business|bookuno pro|start free/, "auth?mode=register&type=business"],
        [/help|support|contact|trust|terms|privacy|cookies|status/, "help"],
        [/log in|sign in|welcome back/, "auth"],
        [/sign up|create account/, "auth?mode=register"],
        [/home|bookuno app|discover/, "public"],
      ];

      const match = rules.find(([pattern]) => pattern.test(label));
      if (!match) {
        if (isLocalPlaceholder) event.preventDefault();
        return;
      }

      event.preventDefault();
      if (String(match[1]).startsWith("/business-app/")) {
        try {
          window.BOOKUNO_API?.seedPilotWorkspace?.("barbershop");
        } catch (error) {
          console.warn("Bookuno local workspace seed skipped", error);
        }
      }
      sourceNavigate(match[1]);
    }

    document.addEventListener("click", onClick, true);
    return () => document.removeEventListener("click", onClick, true);
  }, []);

  const searchParams = useSourceMemo(() => new URLSearchParams(route.query), [route.query]);
  const path = route.path;

  const fadeKey = path;

  let content;
  if (OWNER_ROUTES[path] && sourceIsOwner()) content = <OwnerRedirect to={OWNER_ROUTES[path]}/>;
  else if (OWNER_ROUTES[path]) content = sourceIsSignedIn() ? <PageAccount logoVariant="default"/> : <SourceAuthPage mode="login"/>;
  else if (path === "admin" || path === "crm") {
    content = sourceIsPlatformAdmin()
      ? <ExternalRedirect href="/crm/" title="Opening the CRM." body="Admin routes now use the latest NEWNEW CRM shell."/>
      : (sourceIsSignedIn() ? <PageAccount logoVariant="default"/> : <SourceAuthPage mode="login"/>);
  }
  else if (path === "auth" || path === "login") content = <SourceAuthPage mode={searchParams.get("mode") === "register" ? "register" : "login"}/>;
  else if (path === "register") content = <SourceAuthPage mode="register"/>;
  else if (path === "reset-password") content = <PageResetPassword/>;
  else if (path === "booking/start") content = sourceIsSignedIn() ? <PageBookingStart/> : <SourceAuthPage mode="register"/>;
  else if (path === "search" || path === "categories" || path === "discover") content = <PageSearch logoVariant="default" loggedIn/>;
  else if (
    path === "store" ||
    path === "storefront" ||
    path.startsWith("b/") ||
    path.startsWith("book/") ||
    path.startsWith("booking/") ||
    (path.startsWith("business/") && path !== "business/start" && path !== "business/setup-payouts")
  ) content = <PageStorefront logoVariant="default"/>;
  else if (path === "account" || path === "customer/bookings" || path === "portal") content = sourceIsSignedIn() ? <PageAccount logoVariant="default"/> : <SourceAuthPage mode="login"/>;
  else if (path === "business/start" || path === "onboarding" || path === "business-onboarding") content = <PageBizOnboarding logoVariant="default"/>;
  else if (path === "business/setup-payouts") content = <PageSetupPayouts/>;
  else if (path === "pricing") content = <PagePricing/>;
  else if (path === "billing/success") content = <PageBillingSuccess/>;
  else if (path === "billing") content = sourceIsSignedIn() ? <PageBilling/> : <SourceAuthPage mode="login"/>;
  else if (path === "admin/billing") content = sourceIsPlatformAdmin() ? <PageAdminBilling/> : (sourceIsSignedIn() ? <PageAccount logoVariant="default"/> : <SourceAuthPage mode="login"/>);
  else if (path === "help" || path === "faq") content = <PageHelp logoVariant="default"/>;
  else if (path === "business") content = <PageBizSales logoVariant="default"/>;
  else if (path === "public" || path === "home" || path === "") content = <PageHome logoVariant="default"/>;

  if (content) return <React.Fragment><div key={fadeKey} className="page-fade-in">{content}</div><SourceTabBar/></React.Fragment>;

  // 404 — unknown route
  return (
    <div key={fadeKey} className="page-fade-in">
    <div className="web">
      <TopNav active="" logoVariant="default"/>
      <section className="container" style={{ paddingTop: 80, paddingBottom: 80, textAlign: 'center' }}>
        <div style={{ maxWidth: 440, margin: '0 auto' }}>
          <div style={{ fontSize: 80, fontWeight: 900, letterSpacing: -4, color: 'var(--line-2)' }}>404</div>
          <h1 style={{ fontSize: 28, fontWeight: 800, letterSpacing: -0.025, marginTop: 12 }}>Page not found</h1>
          <p style={{ color: 'var(--muted)', fontSize: 15, lineHeight: 1.6, marginTop: 10 }}>
            The page you're looking for doesn't exist or may have moved.
          </p>
          <div style={{ display: 'flex', gap: 10, justifyContent: 'center', marginTop: 28, flexWrap: 'wrap' }}>
            <a className="btn primary" href="/app.html#/public">Go to Home</a>
            <a className="btn outline" href="/app.html#/search">Browse services</a>
          </div>
        </div>
      </section>
      <Footer logoVariant="default"/>
    </div>
    </div>
  );
}

/* ─── Native / mobile bottom tab bar ──────────────────────────────────────────
 * Persistent primary nav for the app (Capacitor iOS/Android) and mobile web.
 * Uses full /app.html#/… hrefs so the router's label-based click interceptor
 * (which only handles "#"-prefixed hrefs) leaves these alone and the SPA hash
 * router picks up the change. Hidden on immersive flows (storefront, booking,
 * auth, onboarding) so it never covers a primary CTA. CSS shows it only at
 * mobile widths — see bookuno-source-router.css (.bk-tabbar).
 */
function SourceTabBar() {
  const [hash, setHash] = React.useState(window.location.hash);
  React.useEffect(() => {
    const on = () => setHash(window.location.hash);
    window.addEventListener("hashchange", on);
    window.addEventListener("popstate", on);
    return () => {
      window.removeEventListener("hashchange", on);
      window.removeEventListener("popstate", on);
    };
  }, []);

  const path = (hash.replace(/^#\/?/, "").split("?")[0]) || "public";
  const hidden = /^(b\/|book\/|booking|auth|register|business\/start|business\/setup|onboarding|billing)/.test(path);
  if (hidden) return null;

  const I = {
    home: <svg width="22" height="22" viewBox="0 0 24 24" fill="none"><path d="M3 10.5 12 3l9 7.5M5 9.5V20a1 1 0 0 0 1 1h4v-6h4v6h4a1 1 0 0 0 1-1V9.5" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/></svg>,
    search: <svg width="22" height="22" viewBox="0 0 24 24" fill="none"><circle cx="11" cy="11" r="7" stroke="currentColor" strokeWidth="1.8"/><path d="m20 20-3.2-3.2" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round"/></svg>,
    bookings: <svg width="22" height="22" viewBox="0 0 24 24" fill="none"><rect x="3.5" y="5" width="17" height="16" rx="2.5" stroke="currentColor" strokeWidth="1.8"/><path d="M3.5 9.5h17M8 3v4M16 3v4" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round"/></svg>,
    business: <svg width="22" height="22" viewBox="0 0 24 24" fill="none"><path d="M4 9h16v9a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V9Z" stroke="currentColor" strokeWidth="1.8" strokeLinejoin="round"/><path d="M9 9V6a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v3" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round"/></svg>,
  };

  const tabs = [
    { id: "public",   label: "Home",     href: "/app.html#/public",   icon: I.home,     match: ["public", "home", ""] },
    { id: "search",   label: "Explore",  href: "/app.html#/search",   icon: I.search,   match: ["search", "categories", "discover"] },
    { id: "account",  label: "Bookings", href: "/app.html#/account",  icon: I.bookings, match: ["account", "customer/bookings", "portal"] },
    { id: "business", label: "Business", href: "/app.html#/business", icon: I.business, match: ["business", "pricing"] },
  ];
  const activeId = (tabs.find(t => t.match.includes(path)) || {}).id
    || (path.startsWith("search") ? "search" : "");

  return (
    <nav className="bk-tabbar" role="navigation" aria-label="Primary">
      {tabs.map(t => (
        <a key={t.id} href={t.href}
           className={"bk-tab" + (activeId === t.id ? " act" : "")}
           aria-current={activeId === t.id ? "page" : undefined}>
          <span className="bk-tab-ic" aria-hidden="true">{t.icon}</span>
          <span className="bk-tab-l">{t.label}</span>
        </a>
      ))}
    </nav>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<SourceRouterApp/>);
