import type { PointerEvent as ReactPointerEvent } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useClient } from 'hooks/useClient';
import { useAuth } from 'hooks/useAuth';
import { Plus, X, RefreshCw } from 'lucide-react';
import { useWidgetRegistry } from '../../WidgetRegistry';
import { WidgetPicker } from './WidgetPicker';

type Position = { x: number; y: number }; // percentages (0–100)

type WidgetInstance = {
  id: string;
  widgetType: string;
  position: Position;
};

type WidgetPanelConfig = {
  instances: WidgetInstance[];
  nextId: number;
};

const USER_STATE_KEY = ['USER_STATE'];

function useWidgetPanelState(panelId: string) {
  const client = useClient();
  const { isAuthenticated } = useAuth();
  const queryClient = useQueryClient();
  const clientRef = useRef(client);
  clientRef.current = client;

  const stateKey = `widget-panel:${panelId}`;

  const { data: state = {} } = useQuery<Record<string, unknown>>({
    queryKey: USER_STATE_KEY,
    enabled: isAuthenticated,
    queryFn: () => client.get('/user/state'),
    staleTime: Infinity,
  });

  const config = (state[stateKey] as WidgetPanelConfig | undefined) ?? { instances: [], nextId: 0 };

  const setConfig = useCallback(
    (update: WidgetPanelConfig | ((prev: WidgetPanelConfig) => WidgetPanelConfig)) => {
      const currentState = queryClient.getQueryData<Record<string, unknown>>(USER_STATE_KEY) ?? {};
      const current = (currentState[stateKey] as WidgetPanelConfig | undefined) ?? { instances: [], nextId: 0 };
      const next = typeof update === 'function' ? update(current) : update;

      queryClient.setQueryData(USER_STATE_KEY, { ...currentState, [stateKey]: next });
      clientRef.current.patch('/user/state', { [stateKey]: next }).catch(() => {});
    },
    [stateKey, queryClient],
  );

  return [config, setConfig] as const;
}

// --- DraggableWidget ---

type DraggableWidgetProps = {
  instance: WidgetInstance;
  panelRef: React.RefObject<HTMLDivElement | null>;
  onMove: (pos: Position) => void;
  onRemove: () => void;
};

type DragState = {
  startX: number;
  startY: number;
  originX: number;
  originY: number;
  widgetWPct: number;
  widgetHPct: number;
  panelW: number;
  panelH: number;
};

const DraggableWidget = ({ instance, panelRef, onMove, onRemove }: DraggableWidgetProps) => {
  const containerRef = useRef<HTMLDivElement>(null);
  const dragRef = useRef<DragState | null>(null);
  const { registry } = useWidgetRegistry();

  const onPointerDown = useCallback(
    (ev: ReactPointerEvent) => {
      const target = ev.target as HTMLElement;
      if (!target.closest('[data-widget-header]') || target.closest('button')) return;
      const widgetRect = containerRef.current?.getBoundingClientRect();
      const panelRect = panelRef.current?.getBoundingClientRect();
      if (!widgetRect || !panelRect) return;
      dragRef.current = {
        startX: ev.clientX,
        startY: ev.clientY,
        originX: instance.position.x,
        originY: instance.position.y,
        widgetWPct: (widgetRect.width / panelRect.width) * 100,
        widgetHPct: (widgetRect.height / panelRect.height) * 100,
        panelW: panelRect.width,
        panelH: panelRect.height,
      };
      containerRef.current?.setPointerCapture(ev.pointerId);
    },
    [instance.position, panelRef],
  );

  const endDrag = useCallback(
    (ev: ReactPointerEvent) => {
      if (!dragRef.current) return;
      dragRef.current = null;
      containerRef.current?.releasePointerCapture(ev.pointerId);
    },
    [],
  );

  const onPointerMove = useCallback(
    (ev: ReactPointerEvent) => {
      if (!dragRef.current) return;
      const panelRect = panelRef.current?.getBoundingClientRect();
      if (panelRect && (ev.clientX < panelRect.left || ev.clientX > panelRect.right || ev.clientY < panelRect.top || ev.clientY > panelRect.bottom)) {
        endDrag(ev);
        return;
      }
      const d = dragRef.current;
      const deltaXPct = ((ev.clientX - d.startX) / d.panelW) * 100;
      const deltaYPct = ((ev.clientY - d.startY) / d.panelH) * 100;
      onMove({
        x: Math.max(0, Math.min(d.originX + deltaXPct, 100 - d.widgetWPct)),
        y: Math.max(0, Math.min(d.originY + deltaYPct, 100 - d.widgetHPct)),
      });
    },
    [onMove, panelRef, endDrag],
  );

  const onPointerUp = useCallback(
    (ev: ReactPointerEvent) => {
      endDrag(ev);
    },
    [endDrag],
  );

  const entry = registry[instance.widgetType];
  if (!entry) return null;
  const WidgetComponent = entry.component;

  return (
    <div
      ref={containerRef}
      className="absolute group"
      style={{ left: `${instance.position.x}%`, top: `${instance.position.y}%` }}
      onPointerDown={onPointerDown}
      onPointerMove={onPointerMove}
      onPointerUp={onPointerUp}
    >
      <button
        type="button"
        className="absolute -top-2 -right-2 z-10 flex h-5 w-5 items-center justify-center rounded-full bg-destructive text-destructive-foreground opacity-0 shadow transition-opacity cursor-pointer group-hover:opacity-100"
        onClick={onRemove}
      >
        <X size={12} />
      </button>
      <WidgetComponent />
    </div>
  );
};

// --- WidgetPanel ---

export const WidgetPanel = ({ panelId }: { panelId: string }) => {
  const [config, setConfig] = useWidgetPanelState(panelId);
  const [instances, setInstances] = useState<WidgetInstance[]>(config.instances);
  const [showPicker, setShowPicker] = useState(false);
  const panelRef = useRef<HTMLDivElement>(null);
  const nextId = useRef(config.nextId);
  const initialized = useRef(false);

  // Sync from persisted state on first load
  useEffect(() => {
    if (initialized.current) return;
    if (config.instances.length > 0 || config.nextId > 0) {
      setInstances(config.instances);
      nextId.current = config.nextId;
      initialized.current = true;
    }
  }, [config]);

  // Persist whenever instances change (skip the initial mount)
  const mounted = useRef(false);
  useEffect(() => {
    if (!mounted.current) {
      mounted.current = true;
      return;
    }
    setConfig({ instances, nextId: nextId.current });
  }, [instances, setConfig]);

  const addWidget = useCallback((widgetType: string) => {
    nextId.current++;
    const id = `widget-${nextId.current}`;
    const offset = (nextId.current % 5) * 5;
    setInstances((prev) => [...prev, { id, widgetType, position: { x: 2 + offset, y: 2 + offset } }]);
    setShowPicker(false);
  }, []);

  const removeWidget = useCallback((id: string) => {
    setInstances((prev) => prev.filter((w) => w.id !== id));
  }, []);

  const updatePosition = useCallback((id: string, pos: Position) => {
    setInstances((prev) => prev.map((w) => (w.id === id ? { ...w, position: pos } : w)));
  }, []);

  return (
    <div ref={panelRef} className="relative h-full w-full overflow-hidden">
      {instances.map((instance) => (
        <DraggableWidget
          key={instance.id}
          instance={instance}
          panelRef={panelRef}
          onMove={(pos) => updatePosition(instance.id, pos)}
          onRemove={() => removeWidget(instance.id)}
        />
      ))}

      <div className={`absolute bottom-4 right-4 flex flex-col items-end transition-opacity ${showPicker ? 'opacity-100' : 'opacity-0 group-hover/panel:opacity-100'}`}>
        {showPicker && (
          <div className="mb-2 rounded-xl border border-border bg-card p-3 shadow-lg">
            <WidgetPicker onSelect={addWidget} />
          </div>
        )}
        <div className="flex items-center gap-2">
          <button
            type="button"
            title="Resync widgets (reload to pick up newly-deployed widgets)"
            className="flex h-10 w-10 items-center justify-center rounded-full border border-border bg-card text-muted-foreground shadow-lg transition-colors hover:text-foreground cursor-pointer"
            onClick={() => window.location.reload()}
          >
            <RefreshCw size={18} />
          </button>
          <button
            type="button"
            className="flex h-10 w-10 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-lg hover:bg-primary/90 transition-colors cursor-pointer"
            onClick={() => setShowPicker((v) => !v)}
          >
            <Plus size={20} />
          </button>
        </div>
      </div>
    </div>
  );
};
