'use client';

import { useEffect, useState, type ComponentProps, type ReactNode } from 'react';

import { cn } from '@/lib/utils';
import { Sidebar } from '@/components/layout/sidebar';
import { Topbar } from '@/components/layout/topbar';
import { MobileLayoutProvider } from '@/components/layout/mobile/mobile-layout-provider';
import { MobileTopbar } from '@/components/layout/mobile/mobile-topbar';
import { MobileBottomTabs } from '@/components/layout/mobile/mobile-bottom-tabs';
import { MoreSheet } from '@/components/layout/mobile/more-sheet';
import { MobileSearchOverlay } from '@/components/search/mobile-search-overlay';

type SidebarProps = ComponentProps<typeof Sidebar>;
type TopbarProps = ComponentProps<typeof Topbar>;

interface AppShellProps {
  portRoles: SidebarProps['portRoles'];
  isSuperAdmin: boolean;
  user: NonNullable<SidebarProps['user']>;
  ports: TopbarProps['ports'];
  /** Per-port logo URLs resolved server-side. Sidebar picks the entry
   *  matching the currently-active port from the UI store. */
  portLogoUrls: Record<string, string | null>;
  /**
   * Server-rendered form-factor hint (from the request User-Agent). The
   * shell mounts the matching tree on first render so we never paint the
   * wrong shell, and only switches if the runtime viewport matchMedia
   * disagrees (e.g. desktop browser resized below lg).
   */
  initialFormFactor: 'mobile' | 'desktop';
  children: ReactNode;
}

const MOBILE_QUERY = '(max-width: 1023.98px)';

/**
 * #26 + H-09: single-tree responsive shell with stable children subtree.
 *
 * The shell renders ONE `<main>` and ONE `<MobileLayoutProvider>` at all
 * viewports — only the chrome (sidebar+topbar vs mobile-topbar+bottom-tabs)
 * conditionally renders. Two payoffs:
 *
 *   - #26 / first ship: no double-mount of chrome subtrees (Sidebar +
 *     MobileTopbar both running fetches / providers in parallel like the
 *     old layout did).
 *   - H-09: `{children}` stays mounted across viewport flips. A rep
 *     editing an inline field on desktop who resizes through the mobile
 *     breakpoint no longer loses the draft mid-edit — the children tree's
 *     position in the DOM is invariant, so React preserves its state.
 *
 * The mobile-only floating panels (MoreSheet, MobileSearchOverlay) only
 * mount in the mobile branch — they have no desktop counterpart and would
 * be wasteful to keep mounted otherwise.
 *
 * SSR safety: the server passes its UA-classified hint via `initialFormFactor`;
 * the first client render uses the same value so hydration matches. After
 * mount, a matchMedia subscription overrides if the viewport disagrees.
 */
export function AppShell({
  portRoles,
  isSuperAdmin,
  user,
  ports,
  portLogoUrls,
  initialFormFactor,
  children,
}: AppShellProps) {
  const [isMobile, setIsMobile] = useState(initialFormFactor === 'mobile');
  const [moreOpen, setMoreOpen] = useState(false);
  const [searchOpen, setSearchOpen] = useState(false);

  useEffect(() => {
    const mq = window.matchMedia(MOBILE_QUERY);
    const update = () => setIsMobile(mq.matches);
    update();
    mq.addEventListener('change', update);
    return () => mq.removeEventListener('change', update);
  }, []);

  // Build the chrome subtree based on form factor; the children's parent
  // chain (MobileLayoutProvider > div > main) is invariant across both
  // branches, so React reconciliation keeps the children subtree mounted
  // when isMobile flips.
  const chrome = isMobile ? (
    <>
      <MobileTopbar />
    </>
  ) : (
    <Sidebar
      portRoles={portRoles}
      isSuperAdmin={isSuperAdmin}
      user={user}
      ports={ports}
      portLogoUrls={portLogoUrls}
    />
  );

  const footer = isMobile ? (
    <>
      <MobileBottomTabs
        onMoreClick={() => setMoreOpen(true)}
        onSearchClick={() => setSearchOpen(true)}
      />
      <MoreSheet open={moreOpen} onOpenChange={setMoreOpen} />
      <MobileSearchOverlay open={searchOpen} onOpenChange={setSearchOpen} />
    </>
  ) : null;

  const desktopTopbar = !isMobile ? <Topbar ports={ports} user={user} /> : null;

  return (
    <MobileLayoutProvider>
      <div
        className={cn(
          'bg-background',
          isMobile ? 'min-h-[100dvh]' : 'flex h-screen overflow-hidden',
        )}
      >
        {chrome}
        <div className={cn(isMobile ? 'contents' : 'flex-1 flex flex-col overflow-hidden min-w-0')}>
          {desktopTopbar}
          <main
            className={cn(
              isMobile
                ? 'px-4 min-h-[100dvh] pt-[calc(56px+env(safe-area-inset-top)+1rem)] pb-[calc(56px+env(safe-area-inset-bottom)+2rem)]'
                : 'flex-1 overflow-y-auto bg-background px-6 pt-3 pb-6',
            )}
          >
            {children}
          </main>
        </div>
        {footer}
      </div>
    </MobileLayoutProvider>
  );
}
