"use client";

import type { DashboardWidgetConfig, DashboardWidgetType } from "@capakraken/shared/types";
import { verticalCompactor, horizontalCompactor, type Compactor } from "react-grid-layout";

// Runs vertical compaction first (float up), then horizontal (float left).
const bothCompactor: Compactor = {
  type: "vertical",
  allowOverlap: false,
  compact(layout, cols) {
    const afterVertical = verticalCompactor.compact(layout, cols);
    return horizontalCompactor.compact(afterVertical, cols);
  },
};
import dynamic from "next/dynamic";
import { Suspense, useState, useRef, useEffect, useMemo } from "react";
import { useDashboardLayout } from "~/hooks/useDashboardLayout.js";
import { WidgetContainer } from "./WidgetContainer.js";
import { AddWidgetModal } from "./AddWidgetModal.js";
import { getWidget } from "./widget-registry.js";
import { SuccessToast } from "~/components/ui/SuccessToast.js";

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

function WidgetFallback() {
  return (
    <div className="h-full w-full flex flex-col gap-3 p-4">
      <div className="h-3 w-32 shimmer-skeleton rounded" />
      <div className="h-3 w-full shimmer-skeleton rounded" />
      <div className="h-3 w-4/5 shimmer-skeleton rounded" />
      <div className="h-3 w-3/5 shimmer-skeleton rounded" />
    </div>
  );
}

function GridLayoutSkeleton() {
  return (
    <div className="grid grid-cols-2 gap-3 p-3">
      {Array.from({ length: 4 }).map((_, i) => (
        <div key={i} className="h-48 shimmer-skeleton rounded-2xl" />
      ))}
    </div>
  );
}

// Dynamic import — no WidthProvider (uses findDOMNode, broken in React 18 strict mode).
// We measure container width ourselves via ResizeObserver and pass it as a prop.
const GridLayout = dynamic(() => import("react-grid-layout").then((m) => m.Responsive), {
  ssr: false,
  loading: () => <GridLayoutSkeleton />,
});

function renderWidget(
  type: DashboardWidgetType,
  config: DashboardWidgetConfig,
  onConfigChange: (u: Record<string, unknown>) => void,
) {
  const widget = getWidget(type);
  const Component = widget.component;

  return (
    <Suspense fallback={<WidgetFallback />}>
      <Component config={config as Record<string, unknown>} onConfigChange={onConfigChange} />
    </Suspense>
  );
}

function DeferredWidgetFallback() {
  return (
    <div className="flex h-full min-h-32 flex-col gap-3 p-1">
      <div className="h-3 w-32 shimmer-skeleton rounded" />
      <div className="h-20 w-full shimmer-skeleton rounded-2xl" />
      <div className="h-3 w-4/5 shimmer-skeleton rounded" />
      <div className="h-3 w-3/5 shimmer-skeleton rounded" />
    </div>
  );
}

function DeferredWidgetBody({
  type,
  config,
  activationRank,
  isPriority,
  onConfigChange,
}: {
  type: DashboardWidgetType;
  config: DashboardWidgetConfig;
  activationRank: number;
  isPriority: boolean;
  onConfigChange: (u: Record<string, unknown>) => void;
}) {
  const containerRef = useRef<HTMLDivElement>(null);
  const [isActive, setIsActive] = useState(isPriority);

  useEffect(() => {
    if (isPriority) {
      setIsActive(true);
    }
  }, [isPriority]);

  useEffect(() => {
    if (isActive) return;

    const element = containerRef.current;
    if (!element) return;

    const observer = new IntersectionObserver(
      ([entry]) => {
        if (!entry?.isIntersecting) return;
        setIsActive(true);
        observer.disconnect();
      },
      { rootMargin: "320px 0px", threshold: 0.05 },
    );

    observer.observe(element);
    return () => observer.disconnect();
  }, [isActive]);

  useEffect(() => {
    if (isActive || isPriority || typeof window === "undefined") return;

    const activationDelayMs = 900 + Math.min(activationRank, 6) * 180;
    let timeoutId: number | null = null;
    let idleId: number | null = null;
    const browserWindow = window as Window &
      typeof globalThis & {
        requestIdleCallback?: (
          callback: IdleRequestCallback,
          options?: IdleRequestOptions,
        ) => number;
        cancelIdleCallback?: (handle: number) => void;
      };

    const activate = () => setIsActive(true);

    if (typeof browserWindow.requestIdleCallback === "function") {
      idleId = browserWindow.requestIdleCallback(activate, { timeout: activationDelayMs });
    } else {
      timeoutId = browserWindow.setTimeout(activate, activationDelayMs);
    }

    return () => {
      if (idleId !== null && typeof browserWindow.cancelIdleCallback === "function") {
        browserWindow.cancelIdleCallback(idleId);
      }
      if (timeoutId !== null) {
        browserWindow.clearTimeout(timeoutId);
      }
    };
  }, [activationRank, isActive, isPriority]);

  return <div ref={containerRef} className="h-full">{isActive ? renderWidget(type, config, onConfigChange) : <DeferredWidgetFallback />}</div>;
}

export function DashboardClient() {
  const [addModalOpen, setAddModalOpen] = useState(false);
  const { config, isHydrated, saveStatus, addWidget, removeWidget, updateWidgetConfig, onLayoutChange, resetLayout } =
    useDashboardLayout();

  // Measure grid container width so Responsive knows the column size.
  // We can't use WidthProvider (uses findDOMNode, deprecated in React 18).
  const containerRef = useRef<HTMLDivElement>(null);
  const resizeFrameRef = useRef<number | null>(null);
  const lastMeasuredWidthRef = useRef(0);
  const [gridWidth, setGridWidth] = useState(1200);
  // Re-run when isHydrated flips: on first mount the containerRef div doesn't
  // exist yet (skeleton is shown), so the effect returns early. Once hydration
  // completes the real container div is rendered and we need to measure it.
  useEffect(() => {
    const el = containerRef.current;
    if (!el) return;
    const updateWidth = (width: number) => {
      const roundedWidth = Math.max(0, Math.round(width));
      if (roundedWidth === lastMeasuredWidthRef.current) return;
      lastMeasuredWidthRef.current = roundedWidth;
      setGridWidth((currentWidth) => (currentWidth === roundedWidth ? currentWidth : roundedWidth));
    };
    const ro = new ResizeObserver(([entry]) => {
      if (!entry) return;
      if (resizeFrameRef.current !== null) cancelAnimationFrame(resizeFrameRef.current);
      resizeFrameRef.current = requestAnimationFrame(() => {
        resizeFrameRef.current = null;
        updateWidth(entry.contentRect.width);
      });
    });
    ro.observe(el);
    updateWidth(el.getBoundingClientRect().width);
    return () => {
      ro.disconnect();
      if (resizeFrameRef.current !== null) cancelAnimationFrame(resizeFrameRef.current);
    };
  }, [isHydrated]);

  const layouts = useMemo(
    () => ({
      lg: config.widgets.map((w) => ({
        i: w.id,
        x: w.x,
        y: w.y,
        w: w.w,
        h: w.h,
        minW: w.minW ?? 2,
        minH: w.minH ?? 2,
      })),
    }),
    [config.widgets],
  );

  const renderedWidgets = useMemo(
    () =>
      config.widgets.map((widget) => {
        const widgetDefinition = getWidget(widget.type);
        const isPriorityWidget = widget.y < 3;
        return (
          <div key={widget.id}>
            <WidgetContainer
              title={widget.title ?? widgetDefinition.label}
              description={widgetDefinition.description}
              showDetails={widget.config.showDetails === true}
              onToggleDetails={() =>
                updateWidgetConfig(widget.id, {
                  showDetails: widget.config.showDetails !== true,
                })
              }
              onRemove={() => removeWidget(widget.id)}
            >
              <DeferredWidgetBody
                type={widget.type}
                config={widget.config}
                activationRank={widget.y * 12 + widget.x}
                isPriority={isPriorityWidget}
                onConfigChange={(update) => updateWidgetConfig(widget.id, update)}
              />
            </WidgetContainer>
          </div>
        );
      }),
    [config.widgets, removeWidget, updateWidgetConfig],
  );

  // Show a skeleton while hydration is in-flight (avoids flashing the 1-widget default
  // layout before the user's real layout is loaded from localStorage or the DB).
  if (!isHydrated) {
    return (
      <div className="app-page space-y-6">
        <div className="app-page-header gap-4">
          <div>
            <h1 className="app-page-title">Dashboard</h1>
            <p className="app-page-subtitle mt-1">
              Drag widgets to rearrange them and resize from the corners.
            </p>
          </div>
        </div>
        <div className="app-surface overflow-hidden p-3">
          <GridLayoutSkeleton />
        </div>
      </div>
    );
  }

  return (
    <div className="app-page space-y-6">
      <div className="app-page-header gap-4">
        <div>
          <h1 className="app-page-title">Dashboard</h1>
          <p className="app-page-subtitle mt-1">
            Drag widgets to rearrange them and resize from the corners.
          </p>
        </div>
        <div className="flex items-center gap-3">
          <button
            type="button"
            onClick={resetLayout}
            className="rounded-xl border border-gray-300 px-3 py-2 text-sm font-medium text-gray-600 transition hover:border-gray-400 hover:bg-gray-50 dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-800"
          >
            Reset
          </button>
          <button
            type="button"
            onClick={() => setAddModalOpen(true)}
            className="inline-flex items-center gap-2 rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white transition hover:bg-brand-700"
          >
            <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path
                strokeLinecap="round"
                strokeLinejoin="round"
                strokeWidth={2}
                d="M12 4v16m8-8H4"
              />
            </svg>
            Add Widget
          </button>
        </div>
      </div>

      {config.widgets.length === 0 ? (
        <div className="app-surface-strong flex flex-col items-center justify-center border-dashed py-24 text-center">
          <p className="text-sm font-medium text-gray-700 dark:text-gray-200">
            No widgets on this dashboard yet.
          </p>
          <p className="mt-2 max-w-md text-sm text-gray-500">
            Start with a widget and build a view that matches how your team actually plans and
            monitors work.
          </p>
          <button
            type="button"
            onClick={() => setAddModalOpen(true)}
            className="mt-5 rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white transition hover:bg-brand-700"
          >
            Add Widget
          </button>
        </div>
      ) : (
        <div ref={containerRef} className="app-surface overflow-hidden p-3">
          {(() => {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            const AnyGridLayout = GridLayout as any;
            return (
              <AnyGridLayout
                className="layout"
                layouts={layouts}
                width={gridWidth}
                breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }}
                cols={{ lg: 16, md: 12, sm: 8, xs: 4, xxs: 2 }}
                rowHeight={80}
                compactor={bothCompactor}
                onLayoutChange={(
                  _: unknown,
                  allLayouts: Record<
                    string,
                    { i: string; x: number; y: number; w: number; h: number }[]
                  >,
                ) => onLayoutChange(allLayouts["lg"] ?? [])}
                draggableHandle=".widget-drag-handle"
                margin={[12, 12]}
              >
                {renderedWidgets}
              </AnyGridLayout>
            );
          })()}
        </div>
      )}

      {addModalOpen && <AddWidgetModal onAdd={addWidget} onClose={() => setAddModalOpen(false)} />}
      <SuccessToast show={saveStatus === "saved"} message="Layout saved" variant="info" />
    </div>
  );
}
