"use client"

import * as React from 'react';
import { Drawer as DrawerPrimitive } from 'vaul';

import { cn } from '../../../lib/utils';
import { useIsMobile } from '../../../hooks/media/useMobile';

// Direction context lets <DrawerContent /> inherit the Root's direction without
// callers having to pass it twice (a frequent rebase / refactor footgun).
const DrawerDirectionContext = React.createContext<'bottom' | 'right' | 'left' | 'top' | undefined>(undefined);

/** @internal — used by DrawerContent to inherit `direction` from the Root. */
export const useDrawerDirection = () => React.useContext(DrawerDirectionContext);

const Drawer = ({
  shouldScaleBackground,
  direction = 'bottom',
  ...props
}: React.ComponentProps<typeof DrawerPrimitive.Root> & { direction?: 'bottom' | 'right' | 'left' | 'top' }) => {
  // vaul's body-scale animation is a mobile-bottom-sheet effect by design.
  // Applying it to side drawers (right/left) keeps the <body> transformed for
  // ~300ms during open, which on heavy pages stalls the main thread *before*
  // any data fetch the consumer kicks off in response to opening — so the
  // network panel shows the request appearing only after the scale finishes.
  // Default to enabled only for bottom sheets; callers can still override.
  const resolvedScale = shouldScaleBackground ?? direction === 'bottom';

  return (
    <DrawerDirectionContext.Provider value={direction}>
      <DrawerPrimitive.Root
        shouldScaleBackground={resolvedScale}
        direction={direction}
        {...props}
      />
    </DrawerDirectionContext.Provider>
  );
};
Drawer.displayName = "Drawer"

const DrawerTrigger = DrawerPrimitive.Trigger

const DrawerPortal = DrawerPrimitive.Portal

const DrawerClose = DrawerPrimitive.Close

const DrawerOverlay = React.forwardRef<
  React.ElementRef<typeof DrawerPrimitive.Overlay>,
  React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
>(({ className, style, ...props }, ref) => (
  <DrawerPrimitive.Overlay
    ref={ref}
    className={cn("fixed inset-0 z-500 bg-overlay transition-opacity duration-200", className)}
    style={style}
    {...props}
  />
))
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName

/** Drawer size preset. Maps to width for left/right, height for top/bottom. */
export type DrawerSize = 'sm' | 'md' | 'lg' | 'xl' | 'full'

const horizontalSizePresets: Record<DrawerSize, string> = {
  sm: 'min(100vw, 360px)',
  md: 'min(100vw, 480px)',
  lg: 'min(100vw, 640px)',
  xl: 'min(100vw, 800px)',
  full: '100vw',
};

const verticalSizePresets: Record<DrawerSize, string> = {
  sm: 'min(100vh, 240px)',
  md: 'min(100vh, 360px)',
  lg: 'min(100vh, 480px)',
  xl: 'min(100vh, 640px)',
  full: '100vh',
};

// Numeric fallbacks used as initial size when resize starts and the
// user has not provided an explicit width/height. We can't read the
// CSS `min(100vw, …px)` value reliably before the first paint, so we
// take the px portion as a baseline.
const horizontalSizePx: Record<DrawerSize, number> = {
  sm: 360,
  md: 480,
  lg: 640,
  xl: 800,
  full: 1200,
};

const verticalSizePx: Record<DrawerSize, number> = {
  sm: 240,
  md: 360,
  lg: 480,
  xl: 640,
  full: 900,
};

const directionStyles = {
  bottom: "inset-x-0 bottom-0 mt-24 rounded-t-lg border-t",
  top: "inset-x-0 top-0 mb-24 rounded-b-lg border-b",
  right: "inset-y-0 right-0 h-full border-l",
  left: "inset-y-0 left-0 h-full border-r",
} as const;

const toCssLength = (value: string | number | undefined): string | undefined => {
  if (value == null) return undefined;
  return typeof value === 'number' ? `${value}px` : value;
};

const parseInitialPx = (value: string | number | undefined, fallback: number): number => {
  if (typeof value === 'number') return value;
  if (typeof value === 'string') {
    const match = value.match(/(-?\d+(?:\.\d+)?)/);
    if (match) return Number(match[1]);
  }
  return fallback;
};

const clamp = (value: number, min: number, max: number) =>
  Math.min(Math.max(value, min), max);

export interface DrawerContentProps
  extends React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content> {
  direction?: 'bottom' | 'right' | 'left' | 'top';
  /** Preset size (default `'md'`). Width for horizontal, height for vertical. */
  size?: DrawerSize;
  /** CSS length for width — applies to left/right drawers. Overrides `size`. */
  width?: string | number;
  /** CSS length for height — applies to top/bottom drawers. Overrides `size`. */
  height?: string | number;
  /** Allow user to resize the drawer by dragging its inner edge. */
  resizable?: boolean;
  /** Disable resize on mobile viewports (< 768px). Default `true`. */
  resizableOnDesktopOnly?: boolean;
  /** Min size in px when `resizable`. Width for horizontal, height for vertical. */
  minSize?: number;
  /** Max size in px when `resizable`. Width for horizontal, height for vertical. */
  maxSize?: number;
  /**
   * Controlled current size in px. Pair with `onSizeChange` for external
   * persistence (see `useDrawerSize` hook). When `undefined`, the drawer
   * is uncontrolled and size lives in local state.
   */
  resizedSize?: number;
  /** Called once on pointer-up with the final resized size in px. */
  onSizeChange?: (size: number) => void;
}

const DrawerContent = React.forwardRef<
  React.ElementRef<typeof DrawerPrimitive.Content>,
  DrawerContentProps
>(({
  className,
  children,
  direction: directionProp,
  size = 'md',
  width,
  height,
  style,
  resizable = false,
  resizableOnDesktopOnly = true,
  minSize,
  maxSize,
  resizedSize,
  onSizeChange,
  ...props
}, ref) => {
  // Inherit direction from the Drawer Root when not explicitly overridden.
  // This avoids the common bug where `<Drawer direction="right">` is paired
  // with a `<DrawerContent />` that silently falls back to "bottom".
  const inherited = useDrawerDirection();
  const direction = directionProp ?? inherited ?? 'bottom';
  const isVertical = direction === 'bottom' || direction === 'top';
  const isMobile = useIsMobile();
  const resizeEnabled = resizable && (!resizableOnDesktopOnly || !isMobile);

  const defaultMin = isVertical ? 200 : 280;
  const defaultMax = isVertical ? 800 : 960;
  const minPx = minSize ?? defaultMin;
  const maxPx = maxSize ?? defaultMax;

  const presetPx = isVertical
    ? parseInitialPx(height, verticalSizePx[size])
    : parseInitialPx(width, horizontalSizePx[size]);

  // Uncontrolled: track size locally. Controlled: caller owns it via `resizedSize`.
  const isControlled = resizedSize !== undefined;
  const [internalPx, setInternalPx] = React.useState<number | null>(null);
  const currentPx = isControlled ? clamp(resizedSize!, minPx, maxPx) : internalPx;

  // Reset uncontrolled state if direction or enabled flag flips, so we
  // don't carry a horizontal width into a vertical drawer.
  React.useEffect(() => {
    if (!isControlled) setInternalPx(null);
  }, [direction, resizeEnabled, isControlled]);

  const dragStateRef = React.useRef<{
    startCoord: number;
    startSize: number;
    lastSize: number;
  } | null>(null);

  const handlePointerDown = React.useCallback((e: React.PointerEvent<HTMLDivElement>) => {
    if (!resizeEnabled) return;
    e.preventDefault();
    e.stopPropagation();
    (e.currentTarget as HTMLElement).setPointerCapture?.(e.pointerId);
    const startSize = currentPx ?? presetPx;
    dragStateRef.current = {
      startCoord: isVertical ? e.clientY : e.clientX,
      startSize,
      lastSize: startSize,
    };
  }, [resizeEnabled, isVertical, currentPx, presetPx]);

  const handlePointerMove = React.useCallback((e: React.PointerEvent<HTMLDivElement>) => {
    const drag = dragStateRef.current;
    if (!drag) return;
    e.preventDefault();
    const current = isVertical ? e.clientY : e.clientX;
    const delta = current - drag.startCoord;
    const sign = direction === 'right' || direction === 'bottom' ? -1 : 1;
    const next = clamp(drag.startSize + sign * delta, minPx, maxPx);
    drag.lastSize = next;
    if (!isControlled) setInternalPx(next);
    // Note: onSizeChange fires on pointer-up only — avoids hammering
    // localStorage / external stores during drag. For live feedback
    // during drag, render from `currentPx` (uncontrolled) or from your
    // own state synced separately.
  }, [direction, isVertical, minPx, maxPx, isControlled]);

  const handlePointerUp = React.useCallback((e: React.PointerEvent<HTMLDivElement>) => {
    const drag = dragStateRef.current;
    if (!drag) return;
    (e.currentTarget as HTMLElement).releasePointerCapture?.(e.pointerId);
    dragStateRef.current = null;
    onSizeChange?.(drag.lastSize);
  }, [onSizeChange]);

  // Viewport clamp — keeps the drawer inside the window on narrow screens.
  // Without this, a fixed `width: 720px` on a 600px-wide window paints the
  // drawer 120px past the right edge (off-screen for `direction="right"`).
  // Applied to BOTH the resized px value and the initial preset so user-
  // dragged widths can't overflow either. CSS `min()` is cheap and the
  // browser re-evaluates on every viewport change, so window resize works
  // for free.
  const rawWidth = !isVertical
    ? (currentPx != null ? `${currentPx}px` : (toCssLength(width) ?? horizontalSizePresets[size]))
    : undefined;
  const rawHeight = isVertical
    ? (currentPx != null ? `${currentPx}px` : (toCssLength(height) ?? verticalSizePresets[size]))
    : undefined;
  const resolvedWidth = rawWidth ? `min(100vw, ${rawWidth})` : undefined;
  const resolvedHeight = rawHeight ? `min(100vh, ${rawHeight})` : undefined;

  const handlePosition: Record<typeof direction, string> = {
    right: 'left-0 top-0 h-full w-1.5 -translate-x-1/2 cursor-ew-resize',
    left: 'right-0 top-0 h-full w-1.5 translate-x-1/2 cursor-ew-resize',
    bottom: 'top-0 left-0 w-full h-1.5 -translate-y-1/2 cursor-ns-resize',
    top: 'bottom-0 left-0 w-full h-1.5 translate-y-1/2 cursor-ns-resize',
  };

  return (
    <DrawerPortal>
      <DrawerOverlay />
      <DrawerPrimitive.Content
        ref={ref}
        aria-describedby={undefined}
        className={cn(
          "fixed z-500 flex flex-col bg-background",
          directionStyles[direction],
          className
        )}
        style={{
          transition: dragStateRef.current
            ? 'none'
            : 'transform 300ms cubic-bezier(0.32, 0.72, 0, 1)',
          ...(resolvedWidth ? { width: resolvedWidth } : {}),
          ...(resolvedHeight ? { height: resolvedHeight } : {}),
          ...style,
        }}
        {...props}
      >
        {resizeEnabled && (
          <div
            role="separator"
            aria-orientation={isVertical ? 'horizontal' : 'vertical'}
            onPointerDown={handlePointerDown}
            onPointerMove={handlePointerMove}
            onPointerUp={handlePointerUp}
            onPointerCancel={handlePointerUp}
            className={cn(
              "absolute z-10 select-none touch-none bg-transparent hover:bg-border/60 transition-colors",
              handlePosition[direction]
            )}
          />
        )}
        {isVertical && <div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />}
        {children}
      </DrawerPrimitive.Content>
    </DrawerPortal>
  );
})
DrawerContent.displayName = "DrawerContent"

const DrawerHeader = ({
  className,
  ...props
}: React.HTMLAttributes<HTMLDivElement>) => (
  <div
    className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
    {...props}
  />
)
DrawerHeader.displayName = "DrawerHeader"

const DrawerFooter = ({
  className,
  ...props
}: React.HTMLAttributes<HTMLDivElement>) => (
  <div
    className={cn("mt-auto flex flex-col gap-2 p-4", className)}
    {...props}
  />
)
DrawerFooter.displayName = "DrawerFooter"

const DrawerTitle = React.forwardRef<
  React.ElementRef<typeof DrawerPrimitive.Title>,
  React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
>(({ className, ...props }, ref) => (
  <DrawerPrimitive.Title
    ref={ref}
    className={cn(
      "text-lg font-semibold leading-none tracking-tight",
      className
    )}
    {...props}
  />
))
DrawerTitle.displayName = DrawerPrimitive.Title.displayName

const DrawerDescription = React.forwardRef<
  React.ElementRef<typeof DrawerPrimitive.Description>,
  React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
>(({ className, ...props }, ref) => (
  <DrawerPrimitive.Description
    ref={ref}
    className={cn("text-sm text-muted-foreground", className)}
    {...props}
  />
))
DrawerDescription.displayName = DrawerPrimitive.Description.displayName

export {
  Drawer,
  DrawerPortal,
  DrawerOverlay,
  DrawerTrigger,
  DrawerClose,
  DrawerContent,
  DrawerHeader,
  DrawerFooter,
  DrawerTitle,
  DrawerDescription,
}

export { useDrawerSize } from './useDrawerSize';
export type { UseDrawerSizeOptions, UseDrawerSizeResult } from './useDrawerSize';
