import {
  Sheet,
  SheetContent,
  SheetTrigger,
} from "@/components/ui/overlay";
import { useAuth } from "@clerk/react";
import { Link, useLocation } from "wouter";
import { CimbaWordmark } from "@/components/ui/cimba-wordmark";
import { helpIdForRoute } from "@/lib/help/route-to-help-id";
import { Button } from "@/components/ui/button";
import { TooltipProvider } from "@/components/ui/tooltip";
import { Menu } from "lucide-react";
import { useEffect, useState } from "react";
import {
  closeFloating,
  pinSidebar,
  togglePinnedCollapsed,
  useSidebarState,
} from "@/lib/sidebar-mode";
import { useHandedness } from "@/hooks/use-handedness";
import { useShouldUseCustomTitleBar } from "@/lib/pwa-title-bar";
import { Sidebar } from "./sidebar";
import { MobileBreadcrumb } from "./mobile-breadcrumb";
import { UtilityCluster } from "./utility-cluster";
import { useLiveUpdates } from "@/hooks/use-live-updates";
import { SystemNotificationsBridge } from "@/components/notifications/system-notifications-bridge";
import { NotificationsFirstRunPrompt } from "@/components/notifications/notifications-first-run-prompt";
import { usePushSubscriptionLifecycle } from "@/hooks/use-push-subscription-lifecycle";
import {
  HelpFeedbackCluster,
  HelpFeedbackInlineLink,
  useHelpFeedbackHidden,
} from "@/components/help/help-feedback-cluster";
import { TopProgressBar } from "@/components/top-progress-bar";
import { SnapshotProgressInline } from "@/components/snapshot-progress";
import { BackgroundActivityPill } from "@/components/background-activity-pill";
import { HelpDialog } from "@/components/help/help-dialog";
import { HelpPickerOverlay } from "@/components/help/help-picker-overlay";
import { HelpProvider } from "@/components/help/help-provider";
import { BillingBanner } from "@/components/billing-banner";
import { usePracticeMode } from "@/lib/practiceMode";
import { useTenant } from "@/lib/tenantContext";
import { BreadcrumbProvider } from "@/lib/breadcrumb";
import { ModalDockProvider, ModalDock } from "@/lib/modal-dock-context";

/**
 * Task #1149 — thin wrapper that plays a fade + slight slide-up whenever
 * the route changes. Excluded from the layout: sidebar, header, and the
 * status bar at the bottom are all outside this component and are never
 * part of the transition. The `key={location}` re-mounts the wrapper on
 * every navigation event so the tailwindcss-animate enter classes
 * (`animate-in fade-in-0 slide-in-from-top-1`) replay. The
 * `motion-reduce:animate-none` modifier short-circuits the animation
 * for users with `prefers-reduced-motion: reduce`.
 */
function PageContent({
  location,
  children,
}: {
  location: string;
  children: React.ReactNode;
}) {
  return (
    // Task #1506 — `w-full clear-both` here is the shared root-cause fix.
    // The page-route Suspense fallback adds a floating spinner sibling that
    // would otherwise let the next page's table collapse to its content
    // width; clearing floats + forcing the wrapper to stretch keeps list
    // tables full-width on every route. The per-page `w-full clear-both`
    // on `overflow-x-auto` wrappers is belt-and-braces for tables that
    // live inside their own non-flex `<Card>` shells.
    <div
      key={location}
      className="w-full clear-both animate-in fade-in-0 slide-in-from-top-1 duration-200 motion-reduce:animate-none"
    >
      {children}
    </div>
  );
}

export function AppLayout({ children }: { children: React.ReactNode }) {
  const { isSignedIn } = useAuth();
  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
  // Reflects whether the user has fully hidden the floating Help &
  // Feedback cluster (third visibility state — beyond expanded /
  // collapsed). The footer "Help & Feedback" inline link is only
  // shown in that state, so it's the dedicated recovery path back
  // from "hidden". When the cluster is visible (either as the round
  // button or expanded pill) the link stays out of the status bar so
  // there's only one entry point on screen.
  const helpFeedbackHidden = useHelpFeedbackHidden();
  // Three-mode sidebar: pinned-expanded (full docked), pinned-collapsed
  // (icon strip), or floating (undocked dropdown anchored to the title
  // bar logo). Persisted in localStorage so the user lands back on
  // their preferred docking each load. The mobile drawer always
  // renders the sidebar expanded — collapsed/floating only apply to
  // the desktop md+ layout.
  const sidebar = useSidebarState();
  const useCustomTitleBar = useShouldUseCustomTitleBar();
  // Floating mode only makes sense when the title bar is present
  // (that's where the trigger lives). On non-WCO browsers we treat a
  // stored "floating" mode as "pinned-expanded" so the user always
  // has a way to reach the menu — otherwise opening the app in a
  // regular browser tab after using PWA mode would leave them with
  // no nav at all.
  const effectiveMode =
    sidebar.mode === "floating" && !useCustomTitleBar
      ? "pinned-expanded"
      : sidebar.mode;
  const isFloating = effectiveMode === "floating";
  const sidebarCollapsed = effectiveMode === "pinned-collapsed";
  const floatingOpen = isFloating && sidebar.floatingOpen;
  const [location] = useLocation();
  const isPopup = new URLSearchParams(window.location.search).get("popup") === "true";
  const practice = usePracticeMode();
  const tenant = useTenant();
  // Sidebar palette has two orthogonal axes:
  //   1. Base palette — chambers/barrister identity. Practice mode (barrister
  //      demo) always wins so the user's manual override isn't silently
  //      replaced; otherwise we use the active tenant's role so the colour
  //      tracks tenant switches and superadmin impersonation correctly.
  //   2. Elevation stripe — additive thin left-edge bar that signals
  //      elevated privileges (superadmin → royal purple, tenant admin →
  //      deep wine) WITHOUT overwriting the base palette. This way an
  //      admin barrister still reads as a barrister at a glance, with
  //      the stripe telling them they have admin powers. `isSupport`
  //      is platform-only read access and intentionally gets no stripe.
  const basePaletteClass = practice.isBarristerMode || tenant.activeRole === "barrister"
    ? "role-barrister"
    : "";
  const elevationClass = tenant.isSuperadmin
    ? "role-elevation-superadmin"
    : tenant.isTenantAdmin
      ? "role-elevation-admin"
      : (practice.isBarristerMode || tenant.activeRole === "barrister")
        ? "role-elevation-barrister"
        : "role-elevation-staff";
  const roleClass = [basePaletteClass, elevationClass].filter(Boolean).join(" ");
  // Task #1373 — single role-aware sidebar; `practice.isBarristerMode` is
  // now consumed inside Sidebar itself rather than branching here.
  const SidebarComponent = Sidebar;
  const pageHelpId = helpIdForRoute(location);
  // Task #1150 — read the user's handedness preference so we can place the
  // hamburger button and sidebar drawer on the same side as their dominant
  // hand. Right-handed (default) → button + drawer on the right.
  // Left-handed → button + drawer on the left.
  const handedness = useHandedness();

  // Close the floating dropdown when the user navigates to a new
  // page — otherwise the menu would stay open over the destination,
  // which feels broken. Pin / collapse toggles already close it via
  // the store actions.
  useEffect(() => {
    if (floatingOpen) closeFloating();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [location]);

  // Dismiss the floating dropdown on Escape — matches the implicit
  // contract of every other Radix popover in the app.
  useEffect(() => {
    if (!floatingOpen) return;
    function onKey(e: KeyboardEvent) {
      if (e.key === "Escape") closeFloating();
    }
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [floatingOpen]);

  useLiveUpdates();
  // Task #601 — keep the server's WebPush subscription record in sync
  // with the user's current opt-in / permission state.
  usePushSubscriptionLifecycle();

  if (!isSignedIn) {
    return <>{children}</>;
  }

  if (isPopup) {
    return (
      <div className="min-h-screen bg-background">
        <main className="p-4 md:p-6">
          {children}
        </main>
      </div>
    );
  }

  return (
    <ModalDockProvider>
    <BreadcrumbProvider>
      <HelpProvider>
      {/* TooltipProvider wraps the whole shell so collapsed-sidebar nav
          icons (and any other tooltip-using components) can render
          tooltips without each call site re-declaring a provider. */}
      <TooltipProvider delayDuration={200}>
      {/* Task #601 — bridges new SSE notification events to OS-level
          Web Notifications and prompts the user to opt in on first
          run. Both render nothing visible until the user interacts. */}
      <SystemNotificationsBridge />
      <NotificationsFirstRunPrompt />
      {/* Outer shell height: use the dynamic viewport (`100dvh`) so the
          mobile URL bar collapsing/expanding doesn't leave a stray
          scrollbar, and subtract the published title-bar height so we
          fit inside the visible region when the WCO custom title bar
          is mounted (`tabbed-router-shell` already trims the outer
          window by the same amount). The variable is unset (→ 0px)
          in regular browser tabs and on mobile, so the calc collapses
          to `100dvh` there. Without this the footer would push the
          shell past the viewport and an extra scrollbar would appear
          on every page. */}
      <div
        className={`bg-background flex flex-col md:flex-row overflow-hidden ${roleClass}`}
        style={{ height: "calc(100dvh - var(--cimba-title-bar-height, 0px))" }}
      >
        {/* Desktop Sidebar — only docked when the mode is pinned. In
            floating mode the aside is hidden entirely so the main
            content fills the full window width; the menu lives in
            the floating dropdown anchored to the title bar logo
            (rendered below). Width transitions between expanded
            (w-64, 16rem) and collapsed (w-16, 4rem ≈ icon strip)
            on `width` only so the inner sidebar contents can
            re-layout instantly without a janky text fade. */}
        {!isFloating && (
          <aside
            className={`hidden md:flex flex-shrink-0 flex-col border-r bg-sidebar cimba-sidebar-surface h-full overflow-hidden transition-[width] duration-200 ease-out ${roleClass} ${
              sidebarCollapsed ? "w-16" : "w-64"
            }`}
            data-collapsed={sidebarCollapsed ? "true" : "false"}
            data-sidebar-mode={effectiveMode}
          >
            <SidebarComponent
              collapsed={sidebarCollapsed}
              onToggleCollapsed={togglePinnedCollapsed}
              variant="pinned"
            />
          </aside>
        )}

        {/* Mobile Header & Sidebar — single inline row.
            Task #1150: layout adapts to handedness preference.
            Right-handed (default):
              [Cimba logo] [breadcrumb] [bell] [timer] [hamburger ▶]
            Left-handed:
              [◀ hamburger] [Cimba logo] [breadcrumb] [bell] [timer]
            The breadcrumb used to live on its own second row; folding it
            inline keeps the chrome compact on small screens while still
            surfacing where the user is in the app. */}
        {/* `relative z-30` keeps the mobile header — and crucially the
            hamburger button inside it — painted above any sibling
            overlay (e.g. the BillingBanner absolute wrapper in <main>,
            which is z-20 with a `pointer-events-auto` inner). Without
            this, taps near the right edge of the screen could be
            absorbed by the banner layer instead of the menu button. */}
        <header className="md:hidden relative z-30 flex-shrink-0 border-b bg-card">
          <div className="flex items-center gap-2 px-4 py-2.5 min-w-0">
            {/* Task #1150 — hamburger on the LEFT for left-handed users */}
            {handedness === "left" && (
              <Sheet open={mobileMenuOpen} onOpenChange={setMobileMenuOpen} modal={false}>
                <SheetTrigger asChild>
                  <Button variant="ghost" size="icon" className="shrink-0" accessibleName="Open navigation menu">
                    <Menu className="h-5 w-5" />
                  </Button>
                </SheetTrigger>
                {/* The role class (`role-barrister` etc.) lives on the
                    shell wrapper above, but SheetContent portals to
                    document.body and so escapes that scope — without
                    re-applying the class here the mobile drawer would
                    always render in the default Chambers palette even
                    when the user is in barrister mode.
                    Task #1150: subtle shadow on inner edge so the drawer
                    reads clearly as a floating layer. Left drawer → shadow
                    on the right edge. */}
                <SheetContent side="left" className={`p-0 w-64 bg-sidebar cimba-sidebar-surface flex flex-col h-full overflow-hidden shadow-[4px_0_16px_rgba(0,0,0,0.18)] ${roleClass}`}>
                  <SidebarComponent onNavigate={() => setMobileMenuOpen(false)} />
                </SheetContent>
              </Sheet>
            )}
            <Link
              href="/dashboard"
              className="font-serif font-bold text-lg text-primary shrink-0"
              data-testid="link-mobile-home"
            >
              <CimbaWordmark />
            </Link>
            {/* Wrap the breadcrumb in the flex-1 spacer rather than
                relying on the breadcrumb itself to claim the remaining
                space — `MobileBreadcrumb` returns null on routes with
                no useful trail (e.g. the dashboard root), and without
                this wrapper the bell / timer / hamburger would bunch
                left against the Cimba logo. */}
            <div className="flex-1 min-w-0">
              <MobileBreadcrumb />
            </div>
            {/* Mobile chrome utility cluster — search, timer, bell.
                Shares UtilityCluster with the desktop overlay and
                the PWA title bar so the three icons always appear
                together in the same order on every surface. */}
            <UtilityCluster className="shrink-0" />
            {/* Task #1150 — hamburger on the RIGHT for right-handed users (default) */}
            {handedness !== "left" && (
              <Sheet open={mobileMenuOpen} onOpenChange={setMobileMenuOpen} modal={false}>
                <SheetTrigger asChild>
                  <Button variant="ghost" size="icon" className="shrink-0" accessibleName="Open navigation menu">
                    <Menu className="h-5 w-5" />
                  </Button>
                </SheetTrigger>
                {/* Task #1150: subtle shadow on inner edge so the drawer
                    reads clearly as a floating layer. Right drawer →
                    shadow on the left edge. */}
                <SheetContent side="right" data-stripe-side="right" className={`p-0 w-64 bg-sidebar cimba-sidebar-surface flex flex-col h-full overflow-hidden shadow-[-4px_0_16px_rgba(0,0,0,0.18)] ${roleClass}`}>
                  <SidebarComponent onNavigate={() => setMobileMenuOpen(false)} />
                </SheetContent>
              </Sheet>
            )}
          </div>
        </header>

        {/* Main Content */}
        <main id="main-content" tabIndex={-1} className="flex-1 flex flex-col min-w-0 overflow-hidden relative bg-background cimba-canvas-surface focus:outline-none">
          {/* Subscription banner overlays the top of the main pane so
              its presence/absence never shifts page content up or down
              (it's now dismissable, so we explicitly do NOT want a
              relayout when the user closes it). Right-padding reserves
              clearance so the banner never tucks under the bell/timer
              cluster, and `z-20` keeps it below the cluster (z-30) so
              the dismiss + Manage billing buttons can't be obscured by
              the cluster either. */}
          <div className="absolute top-0 left-0 right-0 z-20 pointer-events-none pr-28 md:pr-32">
            <div className="pointer-events-auto">
              <BillingBanner />
            </div>
          </div>
          {/* Desktop bell + timer cluster fallback — only when the
              custom title bar isn't mounted (regular browser tab,
              no Window Controls Overlay). Anchored to the
              non-scrolling `<main>` element (rather than the
              inner scroll container) so it stays pinned at the top
              of the visible pane while the page content scrolls
              behind it. `z-30` puts it above page-level sticky
              bulk-action bars (which use `z-20`) AND above the
              billing banner (z-20), so the cluster is always
              reachable. The inner `md:pr-32` reserves horizontal
              space and `md:pt-12` reserves vertical space on the
              children wrapper so long headings and right-aligned
              page action buttons stay clear of the cluster. */}
          {!useCustomTitleBar && (
            <div className="hidden md:flex absolute top-3 right-4 md:right-8 z-30 pointer-events-none">
              <UtilityCluster className="pointer-events-auto rounded-md bg-background/85 backdrop-blur-sm" />
            </div>
          )}
          <div className="flex-1 overflow-y-auto p-4 md:p-8" data-help-id={pageHelpId}>
            {/* Bell + timer cluster clearance — only when the cluster is
                actually overlaid on this pane (non-WCO desktop). The
                previous approach wrapped the whole children tree in
                `md:pr-32 md:pt-12`, which reserved a 128px gutter all
                the way down the page, leaving a tall empty column on
                the right of every screen. A `float-right` spacer here
                only occupies the top-right corner: the page's first row
                of content (titles, action buttons, etc.) flows around
                it as if it were a sidebar word-wrap, and any content
                that extends past the spacer's height fills the full
                pane width. The spacer matches the cluster's bounding
                box: `top-3 right-4 md:right-8` + two ~36px icon
                buttons + a small backdrop pill ≈ 8rem wide × 3rem tall
                (h-12 / w-32, the same dimensions the old padding was
                using). `aria-hidden` because it carries no semantics. */}
            {!useCustomTitleBar && (
              <div
                className="hidden md:block float-right h-12 w-40"
                aria-hidden="true"
              />
            )}
            {/* Task #1149 — brief fade+slide-up on route change so the
                user can see the content area has changed. The Framer
                Motion key re-mounts the div on every navigation event;
                useReducedMotion disables all animation when the OS
                setting is active. Sidebar, header, and status bar are
                outside this wrapper and are never animated. */}
            <PageContent location={location}>{children}</PageContent>
          </div>
          {/* Status bar — progress strip sits absolutely at the top of
              this relative container so it doesn't affect the height of
              the text row below it.
              Fixed height (h-6) + `whitespace-nowrap` + `overflow-hidden`
              so the bar can never word-wrap onto a second line at narrow
              widths. The version segment uses `truncate` + `min-w-0` so
              it's the piece that gets clipped if there genuinely isn't
              room — everything to its left (snapshot/activity pills and
              the Help & Feedback link) stays fully readable. */}
          <div className="relative flex-shrink-0 border-t overflow-hidden">
            <TopProgressBar variant="status-bar" />
            <div className="px-4 md:px-8 h-6 text-[10px] text-muted-foreground/60 select-none flex items-center gap-1.5 whitespace-nowrap overflow-hidden">
              <SnapshotProgressInline />
              <BackgroundActivityPill />
              <span className="flex-1" />
              {/* Only show the inline "Help & Feedback" link when the
                  floating cluster has been fully hidden by the user
                  (via the EyeOff button in the expanded pill). In the
                  default and collapsed states the round help button
                  is already visible, so duplicating it here would be
                  redundant noise. */}
              {helpFeedbackHidden && (
                <>
                  <HelpFeedbackInlineLink className="text-[10px] text-muted-foreground/80" />
                  <span aria-hidden="true">&middot;</span>
                </>
              )}
              <span className="truncate min-w-0">
                {__APP_VERSION__} &middot; Published {new Date(__BUILD_TIME__).toLocaleString("en-AU", { day: "2-digit", month: "short", year: "numeric", hour: "2-digit", minute: "2-digit", hour12: true })}
              </span>
            </div>
          </div>
        </main>

        {/* Floating sidebar dropdown. Only mounted when the user is in
            floating mode AND has clicked the title-bar logo to open
            it. Rendered as a fixed-position panel in the top-left
            corner directly under the title bar. A backdrop catches
            outside clicks (no visible scrim — the user wanted the
            menu to feel weightless, not modal).
            IMPORTANT: this dropdown is ALWAYS anchored to the left
            edge (`left-2`) — it does NOT honour `handedness`. The
            anchor must visually drop out from underneath the dark
            Cimba logo in the title bar, which is itself always on
            the left. Handedness only flips the mobile drawer
            (Sheet) above, not this desktop/PWA dropdown. */}
        {floatingOpen && (
          <>
            <div
              className="hidden md:block fixed inset-0 z-30"
              onClick={closeFloating}
              aria-hidden="true"
              data-testid="floating-sidebar-backdrop"
            />
            <div
              className={`hidden md:flex fixed left-2 z-40 w-64 max-h-[calc(100vh-3.5rem)] flex-col bg-sidebar cimba-sidebar-surface border border-sidebar-border rounded-lg shadow-2xl overflow-hidden cimba-floating-sidebar ${roleClass}`}
              style={{ top: "calc(var(--cimba-title-bar-height, 36px) + 4px)" }}
              role="dialog"
              aria-label="Sidebar menu"
              data-testid="floating-sidebar"
            >
              <SidebarComponent
                variant="floating"
                onPin={pinSidebar}
                onNavigate={closeFloating}
              />
            </div>
          </>
        )}

        <HelpFeedbackCluster />
        <HelpDialog />
        <HelpPickerOverlay />
        {/* Minimised modal pill dock — rendered above all other UI */}
        <ModalDock />
      </div>
      </TooltipProvider>
      </HelpProvider>
    </BreadcrumbProvider>
    </ModalDockProvider>
  );
}
