'use client';

import { GripVerticalIcon } from 'lucide-react';
import * as React from 'react';
import { cn } from '../../shared/utils';

// --- Types ---

type PanelData = {
  id: string;
  ref: React.MutableRefObject<HTMLDivElement | null>;
  defaultSize?: number;
  minSize?: number;
  maxSize?: number;
  collapsible?: boolean;
  onCollapse?: () => void;
  onExpand?: () => void;
  onResize?: (size: number) => void;
};

type HandleData = {
  id: string;
  ref: React.MutableRefObject<HTMLDivElement | null>;
};

type ResizableContextType = {
  direction: 'horizontal' | 'vertical';
  registerPanel: (data: PanelData) => void;
  unregisterPanel: (id: string) => void;
  registerHandle: (data: HandleData) => void;
  unregisterHandle: (id: string) => void;
  isDragging: boolean;
  startDragging: (handleId: string, event: React.MouseEvent | React.TouchEvent) => void;
  getPanelStyle: (id: string) => React.CSSProperties;
};

const ResizableContext = React.createContext<ResizableContextType | null>(null);

// --- Components ---

/**
 * Drag-to-resize panel layout (IDE-style split panels).
 *
 * @description
 * Creates resizable panel architectures like IDE layouts (collapsible sidebar vs main window).
 * Custom implementation with full touch and mouse support.
 *
 * @ai-rules
 * 1. Always place `<ResizableHandle />` between sibling `<ResizablePanel>` components — it is what separates them.
 * 2. Configure `defaultSize` as a percentage (not pixels): e.g., `defaultSize={50}` for a 50/50 split.
 * 3. Use `minSize` and `maxSize` (also percentages) to constrain draggable bounds.
 */
const ResizablePanelGroup = ({
  children,
  className,
  direction = 'horizontal',
  id,
  autoSaveId,
  storage,
  onLayout,
  ...props
}: {
  children: React.ReactNode;
  className?: string;
  direction?: 'horizontal' | 'vertical';
  id?: string;
  autoSaveId?: string;
  storage?: any;
  onLayout?: (sizes: number[]) => void;
} & React.HTMLAttributes<HTMLDivElement>) => {
  const [panels, setPanels] = React.useState<Map<string, PanelData>>(new Map());
  const [handles, setHandles] = React.useState<Map<string, HandleData>>(new Map());
  const [sizes, setSizes] = React.useState<Map<string, number>>(new Map());
  const [isDragging, setIsDragging] = React.useState(false);
  const containerRef = React.useRef<HTMLDivElement>(null);

  // Sorting helpers
  const getSortedItems = React.useCallback(
    <T extends { ref: React.MutableRefObject<HTMLDivElement | null> }>(items: Map<string, T>) => {
      if (!containerRef.current) return [];
      return Array.from(items.values()).sort((a, b) => {
        if (!a.ref.current || !b.ref.current) return 0;
        const position = a.ref.current.compareDocumentPosition(b.ref.current);
        if (position & Node.DOCUMENT_POSITION_FOLLOWING) return -1;
        if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1;
        return 0;
      });
    },
    []
  );

  // Initialize layout
  React.useEffect(() => {
    const sortedPanels = getSortedItems(panels);
    if (sortedPanels.length === 0) return;

    // Check if we need to initialize sizes
    const uninitialized = sortedPanels.some(p => !sizes.has(p.id));
    if (uninitialized) {
      const newSizes = new Map(sizes);
      const remainingSpace = 100;
      const defaultSizeCount = sortedPanels.filter(p => p.defaultSize).length;

      let usedSpace = 0;
      sortedPanels.forEach(p => {
        if (p.defaultSize) usedSpace += p.defaultSize;
      });

      const spaceForOthers = Math.max(0, remainingSpace - usedSpace);
      const countOthers = sortedPanels.length - defaultSizeCount;
      const sizeForOthers = countOthers > 0 ? spaceForOthers / countOthers : 0;

      sortedPanels.forEach(p => {
        if (!newSizes.has(p.id)) {
          newSizes.set(p.id, p.defaultSize ?? sizeForOthers);
        }
      });
      setSizes(newSizes);
    }
  }, [panels, sizes, getSortedItems]);

  const registerPanel = React.useCallback((data: PanelData) => {
    setPanels(prev => {
      const next = new Map(prev);
      next.set(data.id, data);
      return next;
    });
  }, []);

  const unregisterPanel = React.useCallback((id: string) => {
    setPanels(prev => {
      const next = new Map(prev);
      next.delete(id);
      return next;
    });
    setSizes(prev => {
      const next = new Map(prev);
      next.delete(id);
      return next;
    });
  }, []);

  const registerHandle = React.useCallback((data: HandleData) => {
    setHandles(prev => {
      const next = new Map(prev);
      next.set(data.id, data);
      return next;
    });
  }, []);

  const unregisterHandle = React.useCallback((id: string) => {
    setHandles(prev => {
      const next = new Map(prev);
      next.delete(id);
      return next;
    });
  }, []);

  const startDragging = React.useCallback(
    (handleId: string, event: React.MouseEvent | React.TouchEvent) => {
      event.preventDefault();
      setIsDragging(true);

      const sortedPanels = getSortedItems(panels);
      const sortedHandles = getSortedItems(handles);

      const handleIndex = sortedHandles.findIndex(h => h.id === handleId);
      if (handleIndex === -1) return;

      // Handle i usually sits between Panel i and Panel i+1
      const leftPanel = sortedPanels[handleIndex];
      const rightPanel = sortedPanels[handleIndex + 1];

      if (!leftPanel || !rightPanel) return;

      const startX = 'touches' in event ? event.touches[0].clientX : event.clientX;
      const startY = 'touches' in event ? event.touches[0].clientY : event.clientY;

      const startSizeLeft = sizes.get(leftPanel.id) || 0;
      const startSizeRight = sizes.get(rightPanel.id) || 0;

      const containerSize =
        direction === 'horizontal'
          ? containerRef.current?.offsetWidth || 1
          : containerRef.current?.offsetHeight || 1;

      const onMove = (e: MouseEvent | TouchEvent) => {
        const currentX = 'touches' in e ? e.touches[0].clientX : e.clientX;
        const currentY = 'touches' in e ? e.touches[0].clientY : e.clientY;

        const deltaPixels = direction === 'horizontal' ? currentX - startX : currentY - startY;
        const deltaPercent = (deltaPixels / containerSize) * 100;

        // Calculate tentative new sizes
        let finalLeft = startSizeLeft + deltaPercent;
        let finalRight = startSizeRight - deltaPercent;

        // Apply min constraints
        if (leftPanel.minSize !== undefined && finalLeft < leftPanel.minSize) {
          const diff = leftPanel.minSize - finalLeft;
          finalLeft = leftPanel.minSize;
          finalRight -= diff;
        }
        if (rightPanel.minSize !== undefined && finalRight < rightPanel.minSize) {
          const diff = rightPanel.minSize - finalRight;
          finalRight = rightPanel.minSize;
          finalLeft -= diff;
        }

        // Apply max constraints
        if (leftPanel.maxSize !== undefined && finalLeft > leftPanel.maxSize) {
          const diff = finalLeft - leftPanel.maxSize;
          finalLeft = leftPanel.maxSize;
          finalRight += diff;
        }
        if (rightPanel.maxSize !== undefined && finalRight > rightPanel.maxSize) {
          const diff = finalRight - rightPanel.maxSize;
          finalRight = rightPanel.maxSize;
          finalLeft -= diff;
        }

        // Hard clamp to 0-100 just in case
        finalLeft = Math.max(0, Math.min(100, finalLeft));
        finalRight = Math.max(0, Math.min(100, finalRight));

        setSizes(prev => {
          const next = new Map(prev);
          next.set(leftPanel.id, finalLeft);
          next.set(rightPanel.id, finalRight);
          return next;
        });

        // Notify panels
        leftPanel.onResize?.(finalLeft);
        rightPanel.onResize?.(finalRight);
      };

      const onUp = () => {
        setIsDragging(false);
        window.removeEventListener('mousemove', onMove);
        window.removeEventListener('mouseup', onUp);
        window.removeEventListener('touchmove', onMove);
        window.removeEventListener('touchend', onUp);

        // Trigger onLayout callback
        if (onLayout) {
          onLayout(sortedPanels.map(p => sizes.get(p.id) || 0));
        }
      };

      window.addEventListener('mousemove', onMove);
      window.addEventListener('mouseup', onUp);
      window.addEventListener('touchmove', onMove);
      window.addEventListener('touchend', onUp);
    },
    [panels, handles, sizes, direction, getSortedItems, onLayout]
  );

  const getPanelStyle = React.useCallback(
    (id: string) => {
      const size = sizes.get(id);
      if (size === undefined) return { flex: '1 1 0%', overflow: 'hidden' };
      return { flex: `${size} 1 0%`, overflow: 'hidden' };
    },
    [sizes]
  );

  const contextValue = React.useMemo(
    () => ({
      direction,
      registerPanel,
      unregisterPanel,
      registerHandle,
      unregisterHandle,
      isDragging,
      startDragging,
      getPanelStyle,
    }),
    [
      direction,
      registerPanel,
      unregisterPanel,
      registerHandle,
      unregisterHandle,
      isDragging,
      startDragging,
      getPanelStyle,
    ]
  );

  return (
    <ResizableContext.Provider value={contextValue}>
      <div
        ref={containerRef}
        data-slot="resizable-panel-group"
        data-panel-group-direction={direction}
        className={cn(
          'flex h-full w-full data-[panel-group-direction=vertical]:flex-col',
          className
        )}
        {...props}
      >
        {children}
      </div>
    </ResizableContext.Provider>
  );
};

const ResizablePanel = ({
  className,
  defaultSize,
  minSize = 0,
  maxSize = 100,
  collapsible,
  collapsedSize,
  onCollapse,
  onExpand,
  onResize,
  order,
  tagName,
  id: propId,
  children,
  ...props
}: {
  defaultSize?: number;
  minSize?: number;
  maxSize?: number;
  collapsible?: boolean;
  collapsedSize?: number;
  onCollapse?: () => void;
  onExpand?: () => void;
  onResize?: (size: number) => void;
  order?: number;
  tagName?: string;
} & React.HTMLAttributes<HTMLDivElement>) => {
  const context = React.useContext(ResizableContext);
  const ref = React.useRef<HTMLDivElement>(null);
  const [generatedId] = React.useState(() => Math.random().toString(36).substr(2, 9));
  const id = propId || generatedId;

  // We destructure only stable functions for the dependency array to avoid infinite loops
  const registerPanel = context?.registerPanel;
  const unregisterPanel = context?.unregisterPanel;

  React.useLayoutEffect(() => {
    if (!registerPanel || !unregisterPanel) return;
    registerPanel({
      id,
      ref,
      defaultSize,
      minSize,
      maxSize,
      collapsible,
      onCollapse,
      onExpand,
      onResize,
    });
    return () => unregisterPanel(id);
  }, [
    registerPanel,
    unregisterPanel,
    id,
    defaultSize,
    minSize,
    maxSize,
    collapsible,
    onCollapse,
    onExpand,
    onResize,
  ]);

  if (!context) {
    return (
      <div className={cn('flex-1', className)} {...props}>
        {children}
      </div>
    );
  }

  return (
    <div
      ref={ref}
      data-slot="resizable-panel"
      className={cn('relative transition-[flex-grow] duration-0', className)}
      style={context.getPanelStyle(id)}
      {...props}
    >
      {children}
    </div>
  );
};

const ResizableHandle = ({
  withHandle,
  className,
  id: propId,
  tagName,
  ...props
}: {
  withHandle?: boolean;
  tagName?: string;
} & React.HTMLAttributes<HTMLDivElement>) => {
  const context = React.useContext(ResizableContext);
  const ref = React.useRef<HTMLDivElement>(null);
  const [generatedId] = React.useState(() => Math.random().toString(36).substr(2, 9));
  const id = propId || generatedId;

  // We destructure only stable functions for the dependency array
  const registerHandle = context?.registerHandle;
  const unregisterHandle = context?.unregisterHandle;

  React.useLayoutEffect(() => {
    if (!registerHandle || !unregisterHandle) return;
    registerHandle({ id, ref });
    return () => unregisterHandle(id);
  }, [registerHandle, unregisterHandle, id]);

  const handleMouseDown = (e: React.MouseEvent | React.TouchEvent) => {
    if (context) {
      context.startDragging(id, e);
    }
  };

  if (!context) return null;

  return (
    <div
      ref={ref}
      data-slot="resizable-handle"
      className={cn(
        'bg-border relative flex items-center justify-center focus-visible:outline-hidden',
        'touch-none select-none',
        context.direction === 'vertical'
          ? 'h-px w-full cursor-row-resize after:left-0 after:h-1 after:w-full after:-translate-y-1/2 hover:after:h-4'
          : 'w-px h-full cursor-col-resize after:top-0 after:w-1 after:h-full after:-translate-x-1/2 hover:after:w-4',
        'after:absolute after:z-10',
        className
      )}
      onMouseDown={handleMouseDown}
      onTouchStart={handleMouseDown}
      {...props}
    >
      {withHandle && (
        <div className="bg-border z-10 flex h-4 w-3 items-center justify-center rounded-xs border">
          <GripVerticalIcon className="size-2.5" />
        </div>
      )}
    </div>
  );
};

export { ResizablePanelGroup, ResizablePanel, ResizableHandle };
