import React, {
  ComponentProps,
  MutableRefObject,
  ReactNode,
  createContext,
  useCallback,
  useContext,
  useEffect,
  useRef,
  useState,
} from 'react';
import { Box, Header, Line, Scroll, Text, as, color, toRem } from 'folds';
import { useAtom } from 'jotai';
import classNames from 'classnames';
import { ContainerColor } from '../../styles/ContainerColor.css';
import * as css from './style.css';
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
import { SIDEBAR_WIDTH_MIN, clampSidebarWidth, sidebarWidthAtom } from '../../state/sidebarWidth';
import {
  VOJO_HORSESHOE_VOID_COLOR,
  VOJO_HORSESHOE_GAP_PX,
  VOJO_HORSESHOE_RADIUS_PX,
} from '../../styles/horseshoe';
import { isNativePlatform } from '../../utils/capacitor';

// Off-switch for the web horseshoe layout. Default `true` (horseshoe
// enabled on web non-mobile). Set to `false` by any container that
// embeds `PageRoot` outside the page level — currently `Modal500` for
// the settings dialogs (Settings / SpaceSettings / RoomSettings), so
// the modal's nav rail doesn't end up with a 12px void column and
// rounded corners inside a fixed 500px shell.
export const HorseshoeEnabledContext = createContext<boolean>(true);

const useHorseshoeEnabled = (): boolean => {
  const screenSize = useScreenSizeContext();
  const isMobile = screenSize === ScreenSize.Mobile;
  const contextEnabled = useContext(HorseshoeEnabledContext);
  return contextEnabled && !isMobile && !isNativePlatform();
};

type PageRootProps = {
  nav: ReactNode;
  children: ReactNode;
};

export function PageRoot({ nav, children }: PageRootProps) {
  const screenSize = useScreenSizeContext();
  const isMobile = screenSize === ScreenSize.Mobile;
  // Horseshoe (gap + rounded chat wrapper) is web-only AND non-mobile-
  // only AND not embedded (the `HorseshoeEnabledContext` lets parents
  // like `Modal500` switch it off — settings dialogs render `PageRoot`
  // inside a fixed-width modal where the void column doesn't belong).
  // Native keeps the original single `<Line>` separator; mobile shows
  // one panel at a time so no separator is needed at all.
  const horseshoe = useHorseshoeEnabled();
  const horseshoeRadius = toRem(VOJO_HORSESHOE_RADIUS_PX);

  if (horseshoe) {
    return (
      <Box grow="Yes" className={ContainerColor({ variant: 'Background' })}>
        {/* Page-nav slot — its right edge stays square (the original
            TR / BR rounding was reverted). The 12px void gap below
            still creates a visible seam between page-nav and the chat
            panel, and the resize handle inside `ResizablePageNav` is
            shifted into that void via the inline style below. */}
        {nav}
        <Box
          shrink="No"
          style={{
            width: toRem(VOJO_HORSESHOE_GAP_PX),
            backgroundColor: VOJO_HORSESHOE_VOID_COLOR,
          }}
        />
        {/* Right-side panel: two layers, both filling the same flex
            slot. Outer paints the void colour; inner re-paints the
            standard Background and clips its content with
            `overflow:hidden + border-radius`, so the inner's rounded
            TL/BL carves expose the outer's void. The explicit
            Background bg on the inner is what keeps the panel's
            apparent colour unchanged for routes whose content has no
            opaque bg of its own — without it the outer void would
            bleed through. */}
        <Box grow="Yes" style={{ minWidth: 0, backgroundColor: VOJO_HORSESHOE_VOID_COLOR }}>
          <Box
            grow="Yes"
            className={ContainerColor({ variant: 'Background' })}
            style={{
              minWidth: 0,
              overflow: 'hidden',
              borderTopLeftRadius: horseshoeRadius,
              borderBottomLeftRadius: horseshoeRadius,
            }}
          >
            {children}
          </Box>
        </Box>
      </Box>
    );
  }

  return (
    <Box grow="Yes" className={ContainerColor({ variant: 'Background' })}>
      {nav}
      {!isMobile && <Line variant="Background" size="300" direction="Vertical" />}
      {children}
    </Box>
  );
}

type ClientDrawerLayoutProps = {
  children: ReactNode;
  resizable?: boolean;
  // Round the inner column's right-side corners (TR + BR). Off by
  // default — the page-nav right rounding was intentionally dropped
  // from the standard pattern in commit 74d32eb. Re-enabled only at
  // callsites that explicitly opt in. Currently unused in the tree
  // (the Settings nav tried it and was reverted on product feedback),
  // kept as a primitive for future nested-horseshoe surfaces that
  // want a fully-rounded "island" nav between two voids.
  roundedRight?: boolean;
  // Background surface tone for the inner column. Default
  // `'background'` reads the standard `Background.Container` (Dawn
  // bg2 = #0d0e11) — the deepest surface, what every tab's nav uses.
  // `'surfaceVariant'` swaps to `color.SurfaceVariant.Container`
  // (Dawn bg = #181a20) — the «raised» chat-pane tone used by 1-1
  // chats and the composer; visually a step lighter than the DM
  // list, marking the Settings nav as a distinct surface without
  // jumping outside the Dawn palette.
  surface?: 'background' | 'surfaceVariant';
};
export function PageNav({
  size,
  resizable,
  roundedRight,
  surface,
  children,
}: ClientDrawerLayoutProps & css.PageNavVariants) {
  const screenSize = useScreenSizeContext();
  const isMobile = screenSize === ScreenSize.Mobile;
  const horseshoe = useHorseshoeEnabled();

  if (resizable && !isMobile) {
    // `ResizablePageNav` is a function declaration (hoisted) below — the
    // forward reference is safe at runtime.
    // eslint-disable-next-line no-use-before-define
    return <ResizablePageNav>{children}</ResizablePageNav>;
  }

  const radii = toRem(VOJO_HORSESHOE_RADIUS_PX);
  const roundedRightStyle =
    roundedRight && horseshoe
      ? { borderTopRightRadius: radii, borderBottomRightRadius: radii }
      : undefined;
  // Inline `backgroundColor` overrides whatever `PageNavInnerWebHorseshoe`
  // sets via vanilla-extract — inline style wins on specificity, so
  // we can override the default `Background.Container` without
  // touching the recipe.
  const surfaceStyle =
    surface === 'surfaceVariant' ? { backgroundColor: color.SurfaceVariant.Container } : undefined;

  return (
    <Box
      grow={isMobile ? 'Yes' : undefined}
      className={css.PageNav({ size })}
      shrink={isMobile ? 'Yes' : 'No'}
    >
      <Box
        grow="Yes"
        direction="Column"
        className={horseshoe ? css.PageNavInnerWebHorseshoe : undefined}
        // Top inset for native: `#root` no longer reserves the status-bar
        // height (src/index.css), so the page-nav extends to the screen
        // top. The padding here pushes the page-nav header (workspace
        // tabs, etc.) below the status-bar icons. Applied at the inner
        // column rather than at the `PageNavHeader` recipe because the
        // recipe uses a Folds `<Header size="...">` with a fixed height —
        // padding there would clip the header content. `--vojo-safe-top`
        // is 0 on web and inside Modal500-hosted dialogs.
        style={{
          paddingTop: 'var(--vojo-safe-top, 0px)',
          ...roundedRightStyle,
          ...surfaceStyle,
        }}
      >
        {children}
      </Box>
    </Box>
  );
}

function ResizablePageNav({ children }: { children: ReactNode }) {
  const navRef = useRef<HTMLDivElement>(null);
  const handleRef = useRef<HTMLDivElement>(null);
  const horseshoe = useHorseshoeEnabled();
  const [savedWidth, setSavedWidth] = useAtom(sidebarWidthAtom);
  const [vw, setVw] = useState<number>(typeof window !== 'undefined' ? window.innerWidth : 1280);
  const [dragging, setDragging] = useState(false);
  // Live width during a drag — kept in component state so we don't write to
  // the localStorage-backed atom on every pointermove (hundreds of sync disk
  // writes per drag). Atom is flushed once on pointerup.
  const [liveWidth, setLiveWidth] = useState<number | null>(null);

  useEffect(() => {
    const onResize = () => setVw(window.innerWidth);
    window.addEventListener('resize', onResize);
    return () => window.removeEventListener('resize', onResize);
  }, []);

  const maxW = Math.max(SIDEBAR_WIDTH_MIN, Math.floor(vw / 3));
  const baseWidth = dragging && liveWidth !== null ? liveWidth : savedWidth;
  const width = clampSidebarWidth(baseWidth, vw);
  // On viewports too narrow for any meaningful range (max == min) we hide the
  // handle entirely: leaving it pointer-active but un-draggable reads as a
  // broken UI. Falls back to the static MIN width.
  const canResize = maxW > SIDEBAR_WIDTH_MIN;
  // During a drag, signal that the user has pushed past a clamp — min
  // squishes the indicator, max stretches it, so the limit feels tactile.
  const atMin = dragging && liveWidth !== null && liveWidth <= SIDEBAR_WIDTH_MIN;
  const atMax = dragging && liveWidth !== null && liveWidth >= maxW;

  // Cleanup body styles when the drag ends OR the component unmounts mid-drag
  // (e.g. mobile breakpoint flip, route change, Alt-Tab without pointerup).
  // Without this the page can get stuck with col-resize cursor and
  // user-select: none on body until the next mutation.
  useEffect(() => {
    if (!dragging) return undefined;
    return () => {
      document.body.style.cursor = '';
      document.body.style.userSelect = '';
    };
  }, [dragging]);

  const beginDrag = useCallback((pointerId: number) => {
    setDragging(true);
    setLiveWidth(null);
    try {
      handleRef.current?.setPointerCapture(pointerId);
    } catch {
      /* setPointerCapture is best-effort */
    }
    document.body.style.cursor = 'col-resize';
    document.body.style.userSelect = 'none';
  }, []);

  const endDrag = useCallback(() => {
    setDragging(false);
    document.body.style.cursor = '';
    document.body.style.userSelect = '';
    setLiveWidth((current) => {
      if (current !== null) {
        setSavedWidth(clampSidebarWidth(current, window.innerWidth));
      }
      return null;
    });
  }, [setSavedWidth]);

  const onPointerDown = useCallback(
    (e: React.PointerEvent<HTMLDivElement>) => {
      if (e.button !== 0 || !canResize) return;
      e.preventDefault();
      beginDrag(e.pointerId);
    },
    [beginDrag, canResize]
  );

  const onPointerMove = useCallback(
    (e: React.PointerEvent<HTMLDivElement>) => {
      if (!dragging || !navRef.current) return;
      const rect = navRef.current.getBoundingClientRect();
      setLiveWidth(clampSidebarWidth(e.clientX - rect.left, window.innerWidth));
    },
    [dragging]
  );

  const onStopPointer = useCallback(
    (e: React.PointerEvent<HTMLDivElement>) => {
      try {
        handleRef.current?.releasePointerCapture(e.pointerId);
      } catch {
        /* releasePointerCapture is best-effort */
      }
      endDrag();
    },
    [endDrag]
  );

  const onKeyDown = useCallback(
    (e: React.KeyboardEvent<HTMLDivElement>) => {
      if (!canResize) return;
      const step = e.shiftKey ? 64 : 16;
      let next: number | null = null;
      if (e.key === 'ArrowLeft') next = savedWidth - step;
      else if (e.key === 'ArrowRight') next = savedWidth + step;
      else if (e.key === 'Home') next = SIDEBAR_WIDTH_MIN;
      else if (e.key === 'End') next = maxW;
      if (next === null) return;
      e.preventDefault();
      setSavedWidth(clampSidebarWidth(next, vw));
    },
    [savedWidth, vw, maxW, canResize, setSavedWidth]
  );

  return (
    <Box ref={navRef} className={css.PageNavResizable} style={{ width }}>
      <Box
        grow="Yes"
        direction="Column"
        className={horseshoe ? css.PageNavInnerWebHorseshoe : undefined}
        // Same native safe-top inset as the regular PageNav above —
        // `var(--vojo-safe-top)` is 0 on web (where resizable is used)
        // but kept here for symmetry / future-proofing.
        style={{ paddingTop: 'var(--vojo-safe-top, 0px)' }}
      >
        {children}
      </Box>
      {canResize && (
        // Canonical WAI-ARIA window-splitter pattern: focusable separator
        // with aria-orientation and current/min/max values. The strict
        // role-supports-aria-props lookup table doesn't model the splitter
        // sub-pattern, but assistive tech does.
        // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/role-supports-aria-props, jsx-a11y/no-noninteractive-tabindex
        <div
          ref={handleRef}
          role="separator"
          aria-orientation="vertical"
          aria-valuenow={width}
          aria-valuemin={SIDEBAR_WIDTH_MIN}
          aria-valuemax={maxW}
          aria-label="Resize sidebar"
          // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
          tabIndex={0}
          className={css.PageNavResizeHandle}
          // On web the page-nav is followed by the horseshoe void gap
          // instead of the original `<Line>` separator — shift the
          // handle so its hit-area fills exactly that void instead of
          // bleeding onto the chat surface. Both dimensions are in
          // `rem` to match the gap Box width (also `toRem(...)`), so
          // they stay aligned across non-default root font-sizes.
          // Native (or any context that disables the horseshoe, e.g.
          // inside `Modal500`) keeps the CSS default which lines up
          // with the Line.
          style={
            horseshoe
              ? {
                  right: `-${toRem(VOJO_HORSESHOE_GAP_PX)}`,
                  width: toRem(VOJO_HORSESHOE_GAP_PX),
                }
              : undefined
          }
          data-dragging={dragging || undefined}
          data-at-min={atMin || undefined}
          data-at-max={atMax || undefined}
          onPointerDown={onPointerDown}
          onPointerMove={onPointerMove}
          onPointerUp={onStopPointer}
          onPointerCancel={onStopPointer}
          onLostPointerCapture={onStopPointer}
          onKeyDown={onKeyDown}
        />
      )}
    </Box>
  );
}

export const PageNavHeader = as<'header', css.PageNavHeaderVariants>(
  ({ className, outlined, ...props }, ref) => (
    <Header
      className={classNames(css.PageNavHeader({ outlined }), className)}
      variant="Background"
      size="600"
      {...props}
      ref={ref}
    />
  )
);

export function PageNavContent({
  scrollRef,
  children,
}: {
  children: ReactNode;
  scrollRef?: MutableRefObject<HTMLDivElement | null>;
}) {
  return (
    // `minHeight: 0` is the canonical flexbox fix for a scroll child inside
    // a flex column: without it the Scroll's intrinsic content height pushes
    // the wrapper to that height, the column overflows the viewport, and
    // every sibling with default `flex-shrink: 1` (header / footer rows)
    // gets squashed below its natural height.
    <Box grow="Yes" direction="Column" style={{ minHeight: 0 }}>
      <Scroll
        ref={scrollRef}
        variant="Background"
        direction="Vertical"
        size="300"
        hideTrack
        visibility="Hover"
        // folds `direction="Vertical"` sets `overflow-y: scroll`, which makes
        // this a *user-scrollable* container even when the list fits (scroll
        // range 0, permanently pinned at its boundary). On Android any
        // touch-drag on such a boundary-pinned scroller fires the elastic
        // overscroll stretch — the list "bounces" though it has nothing to
        // scroll. `overflow-y: auto` is user-scrollable only when content
        // actually overflows: short lists become inert (no overscroll), long
        // lists still scroll normally. Inline style wins over the recipe class.
        style={{ overflowY: 'auto' }}
      >
        {/* `css.PageNavContent` carries a 32px (`S700`) bottom padding for
            breathing room below the last row. That padding is part of the
            scroll content, so once the list grows to within 32px of the
            viewport it overflows by however much of the padding doesn't fit —
            the list visually "fits" (a gap shows below it) yet you can still
            scroll a few px into the leftover padding. On native the curtain
            list already has its own bottom boundary (the pinned DirectSelfRow /
            WorkspaceFooter, or the screen edge), so the in-scroll breather is
            redundant; dropping it there means a list that fits has zero scroll
            range. Desktop keeps the breather (mouse-scrolling into it is
            harmless and the last row wants the air at the panel bottom). */}
        <div
          className={css.PageNavContent}
          style={isNativePlatform() ? { paddingBottom: 0 } : undefined}
        >
          {children}
        </div>
      </Scroll>
    </Box>
  );
}

type PageVariantProps = {
  // Background surface tone. Default `'Surface'` (Dawn bg2, #0d0e11)
  // — the deepest tone used by every sub-page elsewhere in the app.
  // `'SurfaceVariant'` (Dawn bg, #181a20) is one notch lighter and
  // used by the Settings sub-pages so they read on the same surface
  // tone as the Settings menu (which itself uses
  // `surface="surfaceVariant"` on its PageNav). Other variants
  // (`'Background'`, `'Primary'`, etc.) pass through unchanged in
  // case a future surface needs a different tone — see folds tokens
  // for the full set.
  variant?: 'Background' | 'Surface' | 'SurfaceVariant' | 'Primary' | 'Secondary';
};
export const Page = as<'div', PageVariantProps>(
  ({ className, variant = 'Surface', ...props }, ref) => (
    <Box
      grow="Yes"
      direction="Column"
      className={classNames(ContainerColor({ variant }), className)}
      {...props}
      ref={ref}
    />
  )
);

export const PageHeader = as<'div', css.PageHeaderVariants>(
  ({ className, outlined, balance, ...props }, ref) => (
    <Header
      as="header"
      size="600"
      className={classNames(css.PageHeader({ balance, outlined }), className)}
      {...props}
      ref={ref}
    />
  )
);

export const PageContent = as<'div'>(({ className, ...props }, ref) => (
  <div className={classNames(css.PageContent, className)} {...props} ref={ref} />
));

export function PageHeroEmpty({ children }: { children: ReactNode }) {
  return (
    <Box
      className={classNames(ContainerColor({ variant: 'SurfaceVariant' }), css.PageHeroEmpty)}
      direction="Column"
      alignItems="Center"
      justifyContent="Center"
      gap="200"
    >
      {children}
    </Box>
  );
}

export const PageHeroSection = as<'div', ComponentProps<typeof Box>>(
  ({ className, ...props }, ref) => (
    <Box
      direction="Column"
      className={classNames(css.PageHeroSection, className)}
      {...props}
      ref={ref}
    />
  )
);

export function PageHero({
  icon,
  title,
  subTitle,
  children,
}: {
  icon?: ReactNode;
  title: ReactNode;
  subTitle?: ReactNode;
  children?: ReactNode;
}) {
  return (
    <Box direction="Column" gap="400">
      {icon && (
        <Box direction="Column" alignItems="Center" gap="200">
          {icon}
        </Box>
      )}
      <Box as="h2" direction="Column" gap="200" alignItems="Center">
        <Text align="Center" size="H2">
          {title}
        </Text>
        {subTitle && (
          <Text align="Center" priority="400">
            {subTitle}
          </Text>
        )}
      </Box>
      {children}
    </Box>
  );
}

export const PageContentCenter = as<'div'>(({ className, ...props }, ref) => (
  <div className={classNames(css.PageContentCenter, className)} {...props} ref={ref} />
));
