"use client";

import * as React from "react";
import {
  Responsive,
  useContainerWidth,
  verticalCompactor,
  type Layout,
  type LayoutItem,
} from "react-grid-layout";
import { GripVertical } from "lucide-react";

import { cn } from "@/lib/utils";
import {
  DASHBOARD_BREAKPOINTS,
  DASHBOARD_COLS,
  DASHBOARD_DRAG_HANDLE,
  GRID_MARGIN,
  GRID_ROW_HEIGHT,
  extractLayoutPart,
  layoutPartOf,
  mergeLayoutPart,
  type LayoutPart,
  cardKeyFromLayoutId,
  isClubbableCardLayoutId,
  isSectionLayoutId,
  type DashboardBreakpoint,
  type DashboardLayoutItem,
  type DashboardLayouts,
} from "@/app/customer/_lib/dashboard-layout";

import "react-grid-layout/css/styles.css";
import "react-resizable/css/styles.css";

export type DashboardGridItem = {
  id: string;
  content: React.ReactNode;
  /** When set on a section item, dropping a metric card here clubs into this section. */
  clubTargetKey?: string;
};

type DashboardLayoutShellProps = {
  layouts: DashboardLayouts;
  onLayoutsChange: (
    layouts: DashboardLayouts,
    options?: { userArranged?: boolean },
  ) => void;
  /** Drop statistic card A onto statistic card B → club (metrics only). */
  onClubCards?: (activeKey: string, overKey: string) => boolean;
  editMode?: boolean;
  items: DashboardGridItem[];
  className?: string;
};

function cloneLayouts(layouts: DashboardLayouts): DashboardLayouts {
  return {
    lg: layouts.lg.map((item) => ({ ...item })),
    md: layouts.md.map((item) => ({ ...item })),
    sm: layouts.sm.map((item) => ({ ...item })),
    xs: layouts.xs.map((item) => ({ ...item })),
    xxs: layouts.xxs.map((item) => ({ ...item })),
  };
}

/** Ignore tiny pointer jitter before a drag starts. */
const DRAG_THRESHOLD_PX = 8;

function eventWantsClub(event: Event | null | undefined): boolean {
  return Boolean(
    event &&
      (event instanceof MouseEvent || event instanceof PointerEvent || event instanceof KeyboardEvent) &&
      event.shiftKey,
  );
}

function toMutableItem(item: LayoutItem): DashboardLayoutItem {
  return {
    i: item.i,
    x: item.x,
    y: item.y,
    w: item.w,
    h: item.h,
    minW: item.minW,
    minH: item.minH,
    maxW: item.maxW,
    maxH: item.maxH,
  };
}

function layoutItemsEqual(a: DashboardLayoutItem[], b: DashboardLayoutItem[]): boolean {
  if (a.length !== b.length) return false;
  const byId = new Map(b.map((item) => [item.i, item]));
  for (const item of a) {
    const other = byId.get(item.i);
    if (!other) return false;
    if (item.x !== other.x || item.y !== other.y || item.w !== other.w || item.h !== other.h) {
      return false;
    }
  }
  return true;
}

function layoutsEqual(a: DashboardLayouts, b: DashboardLayouts): boolean {
  return (Object.keys(DASHBOARD_COLS) as DashboardBreakpoint[]).every((bp) =>
    layoutItemsEqual(a[bp], b[bp]),
  );
}

/** Fold the changes one grid reported (all breakpoints) back into the full layouts. */
function mergeAllBreakpoints(
  allLayouts: Partial<Record<string, Layout>>,
  part: LayoutPart,
  previous: DashboardLayouts,
): DashboardLayouts {
  const byBreakpoint: Partial<Record<DashboardBreakpoint, DashboardLayoutItem[]>> = {};
  for (const bp of Object.keys(DASHBOARD_COLS) as DashboardBreakpoint[]) {
    const layout = allLayouts[bp];
    if (layout) byBreakpoint[bp] = layout.map(toMutableItem);
  }
  return mergeLayoutPart(previous, part, byBreakpoint);
}

/** Fold one grid's layout at a single breakpoint back into the full layouts. */
function mergeOneBreakpoint(
  previous: DashboardLayouts,
  part: LayoutPart,
  breakpoint: DashboardBreakpoint,
  layout: Layout,
): DashboardLayouts {
  return mergeLayoutPart(previous, part, { [breakpoint]: layout.map(toMutableItem) });
}

function resolveClubTarget(
  clientX: number,
  clientY: number,
  activeLayoutId: string,
): string | null {
  if (!isClubbableCardLayoutId(activeLayoutId)) return null;
  const activeKey = cardKeyFromLayoutId(activeLayoutId);
  if (!activeKey) return null;

  const stack = document.elementsFromPoint(clientX, clientY);
  for (const node of stack) {
    if (!(node instanceof HTMLElement)) continue;

    const cardHost = node.closest<HTMLElement>("[data-dashboard-card]");
    if (cardHost && cardHost.dataset.nestedMetric !== "true") {
      const key = cardHost.dataset.dashboardCard ?? "";
      if (key && key !== activeKey && key !== "total") {
        return key;
      }
    }

    const sectionHost = node.closest<HTMLElement>("[data-club-target-key]");
    if (sectionHost) {
      const key = sectionHost.dataset.clubTargetKey ?? "";
      if (key && key !== activeKey && key !== "total") {
        return key;
      }
    }
  }
  return null;
}

export function DashboardLayoutShell({
  layouts,
  onLayoutsChange,
  onClubCards,
  editMode = false,
  items,
  className,
}: DashboardLayoutShellProps) {
  const { width, containerRef, mounted } = useContainerWidth({
    measureBeforeMount: true,
    initialWidth: 1280,
  });

  const layoutsRef = React.useRef(layouts);
  const onLayoutsChangeRef = React.useRef(onLayoutsChange);
  const onClubCardsRef = React.useRef(onClubCards);
  const interactingRef = React.useRef(false);
  const breakpointRef = React.useRef<DashboardBreakpoint>("lg");
  const dragLayoutIdRef = React.useRef<string | null>(null);
  const dragStartLayoutsRef = React.useRef<DashboardLayouts | null>(null);
  const shiftHeldRef = React.useRef(false);
  const [clubHintKey, setClubHintKey] = React.useState<string | null>(null);

  React.useEffect(() => {
    layoutsRef.current = layouts;
  }, [layouts]);

  React.useEffect(() => {
    onLayoutsChangeRef.current = onLayoutsChange;
  }, [onLayoutsChange]);

  React.useEffect(() => {
    onClubCardsRef.current = onClubCards;
  }, [onClubCards]);

  const clearClubHint = React.useCallback(() => {
    setClubHintKey(null);
  }, []);

  const commitLayouts = React.useCallback(
    (next: DashboardLayouts, options?: { userArranged?: boolean }) => {
      if (layoutsEqual(next, layoutsRef.current)) return;
      layoutsRef.current = next;
      onLayoutsChangeRef.current(next, options);
    },
    [],
  );

  const handleBreakpointChange = React.useCallback((bp: string) => {
    if (bp in DASHBOARD_COLS) {
      breakpointRef.current = bp as DashboardBreakpoint;
    }
  }, []);

  const handleDragStart = React.useCallback(
    (
      _layout: Layout,
      _oldItem: LayoutItem | null,
      newItem: LayoutItem | null,
    ) => {
      interactingRef.current = true;
      dragLayoutIdRef.current = newItem?.i ?? null;
      dragStartLayoutsRef.current = cloneLayouts(layoutsRef.current);
      shiftHeldRef.current = false;
      clearClubHint();
    },
    [clearClubHint],
  );

  const handleResizeStart = React.useCallback(
    (
      _layout: Layout,
      _oldItem: LayoutItem | null,
      newItem: LayoutItem | null,
    ) => {
      interactingRef.current = true;
      dragLayoutIdRef.current = newItem?.i ?? null;
      clearClubHint();
    },
    [clearClubHint],
  );

  const handleDrag = React.useCallback(
    (
      _layout: Layout,
      _oldItem: LayoutItem | null,
      newItem: LayoutItem | null,
      _placeholder: LayoutItem | null,
      event: Event,
    ) => {
      const activeId = newItem?.i ?? dragLayoutIdRef.current;
      const wantsClub = eventWantsClub(event);
      shiftHeldRef.current = wantsClub;

      if (!wantsClub || !activeId || !isClubbableCardLayoutId(activeId)) {
        clearClubHint();
        return;
      }
      if (!(event instanceof MouseEvent) && !(event instanceof PointerEvent)) {
        return;
      }
      // Visual hint only — does not freeze or change layout.
      setClubHintKey(resolveClubTarget(event.clientX, event.clientY, activeId));
    },
    [clearClubHint],
  );

  const handleInteractionStop = React.useCallback(
    (
      part: LayoutPart,
      layout: Layout,
      _oldItem: LayoutItem | null,
      newItem: LayoutItem | null,
      _placeholder: LayoutItem | null,
      event: Event,
    ) => {
      const activeId = newItem?.i ?? dragLayoutIdRef.current;
      const bp = breakpointRef.current;
      let clubbed = false;

      // Club only with Shift+drop onto another statistic card / section.
      const wantsClub = eventWantsClub(event) || shiftHeldRef.current;
      if (
        wantsClub &&
        activeId &&
        isClubbableCardLayoutId(activeId) &&
        onClubCardsRef.current &&
        (event instanceof MouseEvent || event instanceof PointerEvent)
      ) {
        const overKey = resolveClubTarget(event.clientX, event.clientY, activeId);
        const activeKey = cardKeyFromLayoutId(activeId);
        if (activeKey && overKey) {
          if (dragStartLayoutsRef.current) {
            layoutsRef.current = dragStartLayoutsRef.current;
            // Undo drag only — do not mark as a user arrangement.
            onLayoutsChangeRef.current(dragStartLayoutsRef.current, {
              userArranged: false,
            });
          }
          clubbed = onClubCardsRef.current(activeKey, overKey);
        }
      }

      clearClubHint();
      dragLayoutIdRef.current = null;
      interactingRef.current = false;
      dragStartLayoutsRef.current = null;
      shiftHeldRef.current = false;

      if (clubbed) {
        return;
      }

      commitLayouts(mergeOneBreakpoint(layoutsRef.current, part, bp, layout), {
        userArranged: true,
      });
    },
    [clearClubHint, commitLayouts],
  );

  const handleResizeStop = React.useCallback(
    (part: LayoutPart, layout: Layout) => {
      clearClubHint();
      dragLayoutIdRef.current = null;
      const bp = breakpointRef.current;
      commitLayouts(mergeOneBreakpoint(layoutsRef.current, part, bp, layout), {
        userArranged: true,
      });
      interactingRef.current = false;
    },
    [clearClubHint, commitLayouts],
  );

  // Each grid reports only its own items, so every handler is told which part it came from.
  const handleLayoutChange = React.useCallback(
    (part: LayoutPart, allLayouts: Partial<Record<string, Layout>>) => {
      if (!interactingRef.current) return;
      commitLayouts(mergeAllBreakpoints(allLayouts, part, layoutsRef.current));
    },
    [commitLayouts],
  );

  const partLayouts = React.useMemo(
    () => ({
      metrics: extractLayoutPart(layouts, "metrics"),
      widgets: extractLayoutPart(layouts, "widgets"),
    }),
    [layouts],
  );

  const renderItem = (item: DashboardGridItem) => {
            const cardKey = cardKeyFromLayoutId(item.id);
            const isSection = isSectionLayoutId(item.id);
            const sectionClubKey = isSection ? item.clubTargetKey : undefined;
            const isClubHint = Boolean(
              clubHintKey &&
                ((cardKey && clubHintKey === cardKey) ||
                  (sectionClubKey && clubHintKey === sectionClubKey)),
            );

            return (
              <div
                key={item.id}
                className={cn(
                  "group relative flex h-full min-h-0 flex-col overflow-hidden rounded-lg border border-border/60 bg-card",
                  editMode && "ring-1 ring-primary/30 ring-offset-1 ring-offset-background",
                  isClubHint && "ring-2 ring-primary ring-offset-2",
                  isSection && "bg-muted/30",
                )}
                data-dashboard-card={cardKey ?? undefined}
                data-dashboard-section={isSection ? item.id.slice("section:".length) : undefined}
                data-club-target-key={sectionClubKey}
              >
                {editMode ? (
                  <button
                    type="button"
                    className={cn(
                      DASHBOARD_DRAG_HANDLE,
                      "absolute top-1.5 left-1.5 z-20 inline-flex size-7 cursor-grab touch-none items-center justify-center rounded-md border bg-background/95 text-muted-foreground",
                      "opacity-100 hover:text-foreground active:cursor-grabbing",
                    )}
                    aria-label="Drag to move"
                    title={
                      cardKey && cardKey !== "total"
                        ? "Drag to move. Hold Shift and drop on another statistic card to club"
                        : "Drag to move"
                    }
                  >
                    <GripVertical className="size-3.5" />
                  </button>
                ) : null}
                {isClubHint ? (
                  <div className="pointer-events-none absolute inset-0 z-30 flex items-center justify-center rounded-[inherit] bg-background/80 text-xs font-semibold text-primary">
                    Shift + drop to club
                  </div>
                ) : null}
                <div className="min-h-0 flex-1 overflow-hidden [&>*]:h-full [&>*]:border-0 [&>*]:shadow-none">
                  {item.content}
                </div>
              </div>
            );
  };

  const metricItems = items.filter((item) => layoutPartOf(item.id) === "metrics");
  const widgetItems = items.filter((item) => layoutPartOf(item.id) === "widgets");

  const renderGrid = (part: LayoutPart, partItems: DashboardGridItem[]) => (
    <Responsive
      key={part}
      className={cn("dashboard-rgl", editMode && "dashboard-rgl-editing")}
      width={width}
      layouts={partLayouts[part]}
      breakpoints={DASHBOARD_BREAKPOINTS}
      cols={DASHBOARD_COLS}
      rowHeight={GRID_ROW_HEIGHT}
      margin={[GRID_MARGIN, GRID_MARGIN] as const}
      containerPadding={[0, 0] as const}
      compactor={verticalCompactor}
      dragConfig={{
        enabled: editMode,
        handle: `.${DASHBOARD_DRAG_HANDLE}`,
        threshold: DRAG_THRESHOLD_PX,
      }}
      resizeConfig={{
        enabled: editMode,
        handles: ["se"],
      }}
      onLayoutChange={(_current, allLayouts) => handleLayoutChange(part, allLayouts)}
      onBreakpointChange={handleBreakpointChange}
      onDragStart={handleDragStart}
      onResizeStart={handleResizeStart}
      onDrag={handleDrag}
      onDragStop={(layout, oldItem, newItem, placeholder, event) =>
        handleInteractionStop(part, layout, oldItem, newItem, placeholder, event)
      }
      onResizeStop={(layout) => handleResizeStop(part, layout)}
    >
      {partItems.map(renderItem)}
    </Responsive>
  );

  return (
    <div ref={containerRef} className={cn("w-full min-w-0", className)}>
      {mounted ? (
        <div className="flex flex-col" style={{ gap: GRID_MARGIN }}>
          {metricItems.length > 0 ? renderGrid("metrics", metricItems) : null}
          {widgetItems.length > 0 ? renderGrid("widgets", widgetItems) : null}
        </div>
      ) : (
        <div className="min-h-[200px] w-full" />
      )}
      <style>{`
        .dashboard-rgl .react-resizable-handle {
          z-index: 30;
          width: 16px;
          height: 16px;
          opacity: 0.55;
        }
        .dashboard-rgl .react-grid-item:hover .react-resizable-handle,
        .dashboard-rgl-editing .react-resizable-handle {
          opacity: 1;
        }
        .dashboard-rgl .react-resizable-handle-se {
          bottom: 4px;
          right: 4px;
        }
        .dashboard-rgl .react-resizable-handle::after {
          right: 3px;
          bottom: 3px;
          width: 8px;
          height: 8px;
          border-right-width: 2px;
          border-bottom-width: 2px;
          border-color: hsl(var(--primary) / 0.75);
        }
        .dashboard-rgl .react-grid-item.react-grid-placeholder {
          background: hsl(var(--primary) / 0.12);
          border-radius: 0.75rem;
        }
      `}</style>
    </div>
  );
}

export type { DashboardBreakpoint };
