"use client";

import { useRef, useState, useEffect, useCallback, useMemo } from "react";
import { GridStack } from "gridstack";
import "gridstack/dist/gridstack.min.css";
import type { WidgetItem, DashboardAllocation } from "@/types/dashboard";
import { PRIMARY_COLOR } from "@/lib/common";
import { apiGet, apiPost } from "@/utils/api-client";
import { DEFAULT_ALLOCATION } from "./AllocationStateView";
import { AddWidgetModal } from "./AddWidgetModal";
import { GridWidgetItem } from "./WidgetCard";

const LAYOUT_STORAGE_KEY = "dashboard-grid-layout";
const DEFAULT_W = 2;
const DEFAULT_H = 2;
const GRID_COLUMNS = 5;
const GRID_CELL_HEIGHT_PX = 220;
const GRID_MARGIN_PX = 12;
const NON_ALLOCATION_DEFAULT: DashboardAllocation = {
  slices: null,
  totals: null,
  loading: false,
  error: null,
};

export type DashboardGridLayout = Record<
  string,
  { x: number; y: number; w: number; h: number }
>;

type GridSaveNode = {
  id?: string;
  x?: number;
  y?: number;
  w?: number;
  h?: number;
  el?: HTMLElement;
};

function areOrdersEqual(a: string[], b: string[]): boolean {
  if (a.length !== b.length) return false;
  for (let i = 0; i < a.length; i += 1) {
    if (a[i] !== b[i]) return false;
  }
  return true;
}

function loadSavedLayout(): DashboardGridLayout {
  if (typeof window === "undefined") return {};
  try {
    const raw = localStorage.getItem(LAYOUT_STORAGE_KEY);
    if (!raw) return {};
    const parsed = JSON.parse(raw) as DashboardGridLayout;
    return typeof parsed === "object" && parsed !== null ? parsed : {};
  } catch {
    return {};
  }
}

function saveLayoutToStorage(layout: DashboardGridLayout) {
  try {
    localStorage.setItem(LAYOUT_STORAGE_KEY, JSON.stringify(layout));
  } catch {
    // ignore
  }
}

function sanitizeNumber(value: unknown, fallback: number): number {
  const n = Number(value);
  return Number.isFinite(n) ? n : fallback;
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null;
}

function extractNodeId(node: GridSaveNode): string | null {
  return (
    node.id ??
    (node.el && (node.el.getAttribute("gs-id") ?? node.el.getAttribute("data-widget-id"))) ??
    null
  );
}

function normalizeBackendLayout(payload: unknown): DashboardGridLayout {
  const parseEntry = (raw: unknown, fallbackWidgetId?: string) => {
    if (!isRecord(raw)) return null;
    const idRaw = raw.widget_id ?? fallbackWidgetId;
    const widgetId =
      typeof idRaw === "string" ? idRaw.trim() : typeof idRaw === "number" ? String(idRaw) : "";
    if (!widgetId) return null;

    const x = Math.max(0, Math.trunc(sanitizeNumber(raw.x, 0)));
    const y = Math.max(0, Math.trunc(sanitizeNumber(raw.y, 0)));
    const w = Math.max(1, Math.trunc(sanitizeNumber(raw.w ?? raw.width, DEFAULT_W)));
    const h = Math.max(1, Math.trunc(sanitizeNumber(raw.h ?? raw.height, DEFAULT_H)));
    return { widgetId, value: { x, y, w, h } };
  };

  const out: DashboardGridLayout = {};

  if (Array.isArray(payload)) {
    payload.forEach((item) => {
      const parsed = parseEntry(item);
      if (parsed) out[parsed.widgetId] = parsed.value;
    });
    return out;
  }

  if (!isRecord(payload)) return out;

  const nested =
    (Array.isArray(payload.widgets) && payload.widgets) ||
    (Array.isArray(payload.layout) && payload.layout) ||
    null;
  if (nested) {
    nested.forEach((item) => {
      const parsed = parseEntry(item);
      if (parsed) out[parsed.widgetId] = parsed.value;
    });
    return out;
  }

  Object.entries(payload).forEach(([widgetId, item]) => {
    const parsed = parseEntry(item, widgetId);
    if (parsed) out[parsed.widgetId] = parsed.value;
  });
  return out;
}

interface DashboardWidgetGridProps {
  widgets: WidgetItem[];
  allocations: Record<string, DashboardAllocation>;
  currency?: string;
  piePalette: string[];
  allWidgets: WidgetItem[];
  selectedWidgetIds: string[];
  onAddWidget: (widget: WidgetItem) => void;
  onSaveOrder: (orderedWidgetIds: string[]) => Promise<void>;
  onOpenModal?: (widget: WidgetItem) => void;
}

export function DashboardWidgetGrid({
  widgets,
  allocations,
  currency,
  piePalette,
  allWidgets,
  selectedWidgetIds,
  onAddWidget,
  onSaveOrder,
  onOpenModal,
}: DashboardWidgetGridProps) {
  const gridRef = useRef<HTMLDivElement>(null);
  const [grid, setGrid] = useState<GridStack | null>(null);
  const [layout, setLayout] = useState<DashboardGridLayout>(loadSavedLayout);
  const [showAddModal, setShowAddModal] = useState(false);
  const [saving, setSaving] = useState(false);
  const lastSavedOrderRef = useRef<string[]>([]);
  const hasLoadedLayoutFromBackendRef = useRef(false);

  const availableToAdd = allWidgets.filter(
    (w) =>
      w.file_path === "asset_allocation" &&
      !selectedWidgetIds.includes(w.widget_id)
  );

  useEffect(() => {
    if (widgets.length === 0) return;

    const itemsPerRow = Math.max(1, Math.floor(GRID_COLUMNS / DEFAULT_W));
    const next: DashboardGridLayout = {};
    let changed = false;

    widgets.forEach((widget, index) => {
      const defaultRow = Math.floor(index / itemsPerRow);
      const defaultCol = (index % itemsPerRow) * DEFAULT_W;
      const saved = layout[widget.widget_id];

      if (!saved) {
        changed = true;
        next[widget.widget_id] = {
          x: defaultCol,
          y: defaultRow * DEFAULT_H,
          w: DEFAULT_W,
          h: DEFAULT_H,
        };
        return;
      }

      const w = Math.max(1, sanitizeNumber(saved.w, DEFAULT_W));
      const h = Math.max(1, sanitizeNumber(saved.h, DEFAULT_H));
      const maxX = Math.max(0, GRID_COLUMNS - w);
      const x = Math.min(maxX, Math.max(0, sanitizeNumber(saved.x, defaultCol)));
      const y = Math.max(0, sanitizeNumber(saved.y, defaultRow * DEFAULT_H));
      next[widget.widget_id] = { x, y, w, h };

      if (x !== saved.x || y !== saved.y || w !== saved.w || h !== saved.h) {
        changed = true;
      }
    });

    if (Object.keys(layout).length !== widgets.length) changed = true;

    if (changed) {
      setLayout(next);
      saveLayoutToStorage(next);
    }
  }, [widgets, layout]);

  useEffect(() => {
    if (!areOrdersEqual(lastSavedOrderRef.current, selectedWidgetIds)) {
      lastSavedOrderRef.current = selectedWidgetIds;
    }
  }, [selectedWidgetIds]);

  useEffect(() => {
    if (hasLoadedLayoutFromBackendRef.current || widgets.length === 0) return;
    hasLoadedLayoutFromBackendRef.current = true;
    let cancelled = false;

    const loadLayoutFromBackend = async () => {
      try {
        const backendData = await apiGet<unknown>("/api/widget-layout/save");
        const backendLayout = normalizeBackendLayout(backendData);
        if (cancelled || Object.keys(backendLayout).length === 0) return;
        setLayout((prev) => {
          const merged = { ...prev, ...backendLayout };
          saveLayoutToStorage(merged);
          return merged;
        });
      } catch (error) {
        console.warn("[DashboardWidgetGrid] Failed to load persisted layout from backend", error);
      }
    };

    void loadLayoutFromBackend();
    return () => {
      cancelled = true;
    };
  }, [widgets.length]);

  useEffect(() => {
    if (!gridRef.current) return;

    const gridInstance = GridStack.init(
      {
        float: false,
        column: GRID_COLUMNS,
        cellHeight: GRID_CELL_HEIGHT_PX,
        margin: GRID_MARGIN_PX,
        animate: true,
        resizable: { handles: "e,s,se", autoHide: false },
        draggable: { handle: ".grid-stack-item-content", scroll: true },
      },
      gridRef.current
    );
    setGrid(gridInstance);

    const updateResponsive = () => {
      if (typeof window === "undefined") return;
      const isMobile = window.innerWidth < 768;
      gridInstance.column(isMobile ? 1 : GRID_COLUMNS);
      gridInstance.cellHeight(isMobile ? 240 : GRID_CELL_HEIGHT_PX);
    };
    updateResponsive();
    window.addEventListener("resize", updateResponsive);

    return () => {
      window.removeEventListener("resize", updateResponsive);
      try {
        gridInstance.destroy?.(false);
      } catch {
        // ignore
      }
    };
  }, []);

  const persistLayoutToBackend = useCallback(async (next: DashboardGridLayout) => {
    if (Object.keys(next).length === 0) return;
    const widgetsPayload = Object.entries(next).map(([widget_id, value]) => ({
      widget_id,
      x: value.x,
      y: value.y,
      width: value.w,
      height: value.h,
    }));
    try {
      await apiPost("/api/widget-layout/save", { widgets: widgetsPayload });
    } catch (error) {
      console.warn("[DashboardWidgetGrid] Failed to persist full layout", error);
    }
  }, []);

  const persistResizeToBackend = useCallback(async (widgetId: string, node: { x: number; y: number; w: number; h: number }) => {
    try {
      await apiPost("/api/widget-layout/resize", {
        widget_id: widgetId,
        width: node.w,
        height: node.h,
        x: node.x,
        y: node.y,
      });
    } catch (error) {
      console.warn("[DashboardWidgetGrid] Failed to persist widget resize", { widgetId, error });
    }
  }, []);

  const saveLayoutFromGrid = useCallback((persistOrderToBackend: boolean): { layout: DashboardGridLayout; order: string[] } | null => {
    if (!grid) return null;
    const raw = grid.save?.();
    const saved = Array.isArray(raw) ? raw : [];
    const next: DashboardGridLayout = {};
    const order: string[] = [];
    saved.forEach((node: GridSaveNode) => {
      const id = extractNodeId(node) ?? undefined;
      const w = node.w ?? 1;
      const h = node.h ?? 1;
      if (id) {
        order.push(id);
        next[id] = {
          x: node.x ?? 0,
          y: node.y ?? 0,
          w: w >= 1 ? w : DEFAULT_W,
          h: h >= 1 ? h : DEFAULT_H,
        };
      }
    });

    setLayout(next);
    saveLayoutToStorage(next);
    if (!persistOrderToBackend) return { layout: next, order };

    const assetOrder = order.filter((id) => selectedWidgetIds.includes(id));
    if (assetOrder.length > 0 && !areOrdersEqual(assetOrder, lastSavedOrderRef.current)) {
      lastSavedOrderRef.current = assetOrder;
      setSaving(true);
      onSaveOrder(assetOrder).finally(() => setSaving(false));
    }
    return { layout: next, order };
  }, [grid, onSaveOrder, selectedWidgetIds]);

  useEffect(() => {
    if (!grid) return;
    const onLayoutChange = () => {
      saveLayoutFromGrid(false);
    };
    const onResizeStop = (_event: Event, el: unknown) => {
      const result = saveLayoutFromGrid(false);
      if (!result) return;

      const htmlEl =
        el instanceof HTMLElement
          ? el
          : Array.isArray(el) && el[0] instanceof HTMLElement
            ? el[0]
            : null;
      const resizedWidgetId = htmlEl
        ? (htmlEl.getAttribute("gs-id") ?? htmlEl.getAttribute("data-widget-id"))
        : null;

      if (resizedWidgetId) {
        const resizedNode = result.layout[resizedWidgetId];
        if (resizedNode) {
          void persistResizeToBackend(resizedWidgetId, resizedNode);
        }
      }
      void persistLayoutToBackend(result.layout);
    };
    const onDragStop = () => {
      const result = saveLayoutFromGrid(true);
      if (!result) return;
      void persistLayoutToBackend(result.layout);
    };
    grid.on("change", onLayoutChange);
    grid.on("resizestop", onResizeStop);
    grid.on("dragstop", onDragStop);
    return () => {
      try {
        grid.off("change");
        grid.off("resizestop");
        grid.off("dragstop");
      } catch {
        // ignore
      }
    };
  }, [grid, saveLayoutFromGrid, persistResizeToBackend, persistLayoutToBackend]);

  const getLayoutForWidget = useCallback(
    (widgetId: string, index: number): { x: number; y: number; w: number; h: number } => {
      const saved = layout[widgetId];
      if (saved) return saved;
      const itemsPerRow = Math.max(1, Math.floor(GRID_COLUMNS / DEFAULT_W));
      const row = Math.floor(index / itemsPerRow);
      const col = (index % itemsPerRow) * DEFAULT_W;
      return { x: col, y: row * DEFAULT_H, w: DEFAULT_W, h: DEFAULT_H };
    },
    [layout]
  );

  const orderedWidgets = useMemo(
    () =>
      [...widgets].sort((a, b) => {
        const aPriority = Number(a.priority ?? Number.MAX_SAFE_INTEGER);
        const bPriority = Number(b.priority ?? Number.MAX_SAFE_INTEGER);
        if (aPriority !== bPriority) return aPriority - bPriority;
        return a.widget_id.localeCompare(b.widget_id);
      }),
    [widgets]
  );

  if (widgets.length === 0) {
    return (
      <section className="mt-8">
        <div className="flex flex-col items-center justify-center rounded-3xl border-2 border-dashed border-emerald-200/50 bg-gradient-to-br from-emerald-50/30 to-white/50 backdrop-blur-sm py-16 px-6 text-center shadow-lg">
          <div className="mb-4 h-16 w-16 rounded-full bg-gradient-to-br from-emerald-400 to-emerald-600 flex items-center justify-center shadow-lg">
            <span className="text-2xl text-white">📊</span>
          </div>
          <p className="text-slate-700 font-semibold text-lg mb-2">No widgets yet</p>
          <p className="text-sm text-slate-600 mb-6 max-w-md">
            Add asset allocation widgets to build your personalized financial dashboard and gain insights into your portfolio.
          </p>
          <button
            type="button"
            onClick={() => setShowAddModal(true)}
            className="inline-flex items-center gap-2 rounded-xl bg-gradient-to-r from-emerald-500 to-emerald-600 px-6 py-3 text-sm font-semibold text-white shadow-lg transition-all duration-300 hover:from-emerald-600 hover:to-emerald-700 hover:shadow-xl hover:-translate-y-0.5"
          >
            <span className="text-base leading-none">+</span>
            Add Your First Widget
          </button>
        </div>
        {showAddModal && (
          <AddWidgetModal
            available={availableToAdd}
            onSelect={(w) => {
              onAddWidget(w);
              setShowAddModal(false);
            }}
            onClose={() => setShowAddModal(false)}
          />
        )}
      </section>
    );
  }

  const itemsPerRow = Math.max(1, Math.floor(GRID_COLUMNS / DEFAULT_W));
  const rows = Math.max(1, Math.ceil(widgets.length / itemsPerRow));
  const gridMinHeight = rows * (GRID_CELL_HEIGHT_PX + GRID_MARGIN_PX);

  return (
    <section className="mt-8 animate-[fadeIn_0.5s_ease-in-out]">
      <div className="widgets-section-header mb-6">
        <div className="flex flex-wrap items-center justify-between gap-4">
          <div className="space-y-1">
            <h2 className="text-2xl font-bold text-slate-900 bg-gradient-to-r from-slate-900 to-emerald-700 bg-clip-text text-transparent">
              Your Widgets
            </h2>
            <p className="text-sm text-slate-600">Customize your dashboard with interactive widgets</p>
          </div>
          <div className="flex items-center gap-3">
            {saving && (
              <span className="text-xs text-emerald-600 font-medium animate-pulse">Saving…</span>
            )}
            <button
              type="button"
              onClick={() => setShowAddModal(true)}
              className="inline-flex items-center gap-2 rounded-xl bg-gradient-to-r from-emerald-500 to-emerald-600 px-5 py-2.5 text-sm font-semibold text-white shadow-lg transition-all duration-300 hover:from-emerald-600 hover:to-emerald-700 hover:shadow-xl hover:-translate-y-0.5"
            >
              <span className="text-base leading-none">+</span>
              Add Widget
            </button>
          </div>
        </div>
      </div>
      <div className="grid-widget-grid-wrapper">
        <div
          ref={gridRef}
          className="grid-stack grid-stack-responsive"
          style={{ minHeight: `${gridMinHeight}px` }}
        >
        {orderedWidgets.map((widget, index) => {
          const allocation =
            allocations[widget.widget_id] ??
            (widget.file_path === "asset_allocation"
              ? DEFAULT_ALLOCATION
              : NON_ALLOCATION_DEFAULT);
          const layoutItem = getLayoutForWidget(widget.widget_id, index);
          return (
            <GridWidgetItem
              key={widget.widget_id}
              widget={widget}
              allocation={allocation}
              currency={currency}
              piePalette={piePalette}
              layout={layoutItem}
              grid={grid}
              onOpenModal={onOpenModal}
            />
          );
        })}
        </div>
      </div>

      {showAddModal && (
        <AddWidgetModal
          available={availableToAdd}
          onSelect={(w) => {
            onAddWidget(w);
            setShowAddModal(false);
          }}
          onClose={() => setShowAddModal(false)}
        />
      )}
    </section>
  );
}
