import React, { useEffect, useRef, useState, forwardRef, useImperativeHandle } from "react";
import { createPortal } from "react-dom";
import { GridStack } from "gridstack";
import "gridstack/dist/gridstack.min.css";

const GRID_OPTIONS = {
  column: 12,
  cellHeight: 80,
  margin: 4,
  animate: true,
  float: true,
  resizable: { handles: "e, se, s, sw, w" },
  draggable: { handle: ".widget-drag-handle" },
};

const toolBtnStyle = {
  background: "none",
  border: "1px solid var(--border)",
  borderRadius: 3,
  padding: "1px 5px",
  color: "var(--text-dim)",
  cursor: "pointer",
  fontFamily: "inherit",
  fontSize: 10,
  lineHeight: "16px",
  display: "flex",
  alignItems: "center",
  justifyContent: "center",
  minWidth: 22,
  height: 20,
  flexShrink: 0,
};

const toolBtnActiveStyle = {
  ...toolBtnStyle,
  color: "var(--cyan)",
  borderColor: "var(--cyan)",
  background: "rgba(34, 211, 238, 0.08)",
};

const separatorStyle = {
  color: "var(--border)",
  fontSize: 12,
  margin: "0 2px",
  userSelect: "none",
  flexShrink: 0,
};

const AGGREGATIONS = ["count", "sum", "avg", "min", "max"];

const CHART_TYPES = [
  { key: "hbar", label: "☰", title: "Horizontal bars" },
  { key: "vbar", label: "▥", title: "Vertical bars" },
  { key: "pie", label: "◔", title: "Pie chart" },
  { key: "line", label: "⌇", title: "Line chart" },
  { key: "area", label: "▤", title: "Area chart" },
];

// Panel context menu (kebab menu)
function PanelMenu({ panelId, panel, onCopy, onChartTypeChange, onClose, anchorEl }) {
  const dropdownRef = useRef(null);
  const [pos, setPos] = useState({ top: 0, left: 0 });

  useEffect(() => {
    if (!anchorEl) return;
    const update = () => {
      const rect = anchorEl.getBoundingClientRect();
      const dropdownWidth = 160;
      let left = rect.right - dropdownWidth;
      if (left < 4) left = rect.left;
      if (left + dropdownWidth > window.innerWidth - 4) {
        left = window.innerWidth - dropdownWidth - 4;
      }
      setPos({ top: rect.bottom + 4, left });
    };
    update();
    window.addEventListener("scroll", update, true);
    window.addEventListener("resize", update);
    return () => {
      window.removeEventListener("scroll", update, true);
      window.removeEventListener("resize", update);
    };
  }, [anchorEl]);

  useEffect(() => {
    const handleClick = (e) => {
      if (dropdownRef.current && !dropdownRef.current.contains(e.target) &&
          (!anchorEl || !anchorEl.contains(e.target))) {
        onClose();
      }
    };
    document.addEventListener("mousedown", handleClick);
    return () => document.removeEventListener("mousedown", handleClick);
  }, [onClose, anchorEl]);

  return createPortal(
    <div
      ref={dropdownRef}
      style={{
        position: "fixed",
        top: pos.top,
        left: pos.left,
        background: "var(--panel)",
        border: "1px solid var(--border)",
        borderRadius: 6,
        minWidth: 160,
        zIndex: 10000,
        boxShadow: "0 8px 24px rgba(0,0,0,0.4)",
      }}
      onClick={(e) => e.stopPropagation()}
    >
      <div
        onClick={() => { onCopy(panelId); onClose(); }}
        style={{
          padding: "7px 12px",
          cursor: "pointer",
          fontSize: 12,
          display: "flex",
          justifyContent: "space-between",
          alignItems: "center",
          color: "var(--text)",
        }}
        onMouseEnter={(e) => { e.currentTarget.style.background = "rgba(255,255,255,0.05)"; }}
        onMouseLeave={(e) => { e.currentTarget.style.background = "transparent"; }}
      >
        <span>Copy panel</span>
        <span style={{ fontSize: 10, color: "var(--text-dim)" }}>then ⌘V</span>
      </div>
      {panel?.viewMode === "chart" && (
        <>
          <div style={{ borderTop: "1px solid var(--border)", margin: "4px 0" }} />
          <div style={{ padding: "4px 12px 2px", fontSize: 10, color: "var(--text-dim)", textTransform: "uppercase", letterSpacing: "0.05em" }}>Chart type</div>
          {CHART_TYPES.map((ct) => {
            const active = (panel.chartType || "hbar") === ct.key;
            return (
              <div
                key={ct.key}
                onClick={() => { onChartTypeChange(panelId, ct.key); onClose(); }}
                style={{
                  padding: "6px 12px",
                  cursor: "pointer",
                  fontSize: 12,
                  display: "flex",
                  alignItems: "center",
                  gap: 8,
                  color: active ? "var(--cyan)" : "var(--text)",
                  background: active ? "rgba(34, 211, 238, 0.08)" : "transparent",
                }}
                onMouseEnter={(e) => { if (!active) e.currentTarget.style.background = "rgba(255,255,255,0.05)"; }}
                onMouseLeave={(e) => { e.currentTarget.style.background = active ? "rgba(34, 211, 238, 0.08)" : "transparent"; }}
              >
                <span style={{ width: 18, textAlign: "center" }}>{ct.label}</span>
                <span>{ct.title}</span>
                {active && <span style={{ marginLeft: "auto", fontSize: 11 }}>✓</span>}
              </div>
            );
          })}
        </>
      )}
    </div>,
    document.body
  );
}

// Summary dropdown for PostgreSQL panels
function formatAggLabel(config, mode) {
  if (!config) return null;
  const agg = config.aggregation?.toUpperCase() || "";
  if (mode === "chart" || config.groupByColumn) {
    const val = config.valueColumn ? `${agg}(${config.valueColumn})` : agg;
    return `${val} by ${config.groupByColumn}`;
  }
  return config.column ? `${agg}(${config.column})` : agg;
}

function SummaryDropdown({ panel, columns, onSelect, onClose, anchorEl }) {
  const [stage, setStage] = useState("main"); // main | pickColumn | groupByCol | groupByAgg | groupByValCol
  const [pendingAgg, setPendingAgg] = useState(null);
  const [groupByCol, setGroupByCol] = useState(null);
  const [pendingGroupByAgg, setPendingGroupByAgg] = useState(null);
  const dropdownRef = useRef(null);
  const [pos, setPos] = useState({ top: 0, left: 0 });

  // Position the dropdown relative to the anchor button
  useEffect(() => {
    if (!anchorEl) return;
    const update = () => {
      const rect = anchorEl.getBoundingClientRect();
      const dropdownWidth = 220;
      // Default: align right edge with button right edge
      let left = rect.right - dropdownWidth;
      // If that goes off the left edge, align to button left edge instead
      if (left < 4) left = rect.left;
      // If that still goes off the right edge, clamp
      if (left + dropdownWidth > window.innerWidth - 4) {
        left = window.innerWidth - dropdownWidth - 4;
      }
      setPos({ top: rect.bottom + 4, left });
    };
    update();
    window.addEventListener("scroll", update, true);
    window.addEventListener("resize", update);
    return () => {
      window.removeEventListener("scroll", update, true);
      window.removeEventListener("resize", update);
    };
  }, [anchorEl]);

  useEffect(() => {
    const handleClick = (e) => {
      if (dropdownRef.current && !dropdownRef.current.contains(e.target) &&
          (!anchorEl || !anchorEl.contains(e.target))) {
        onClose();
      }
    };
    document.addEventListener("mousedown", handleClick);
    return () => document.removeEventListener("mousedown", handleClick);
  }, [onClose, anchorEl]);

  const menuItemStyle = {
    padding: "7px 12px",
    cursor: "pointer",
    fontSize: 12,
    display: "flex",
    justifyContent: "space-between",
    alignItems: "center",
    color: "var(--text)",
  };

  const handleAggClick = (agg) => {
    if (agg === "count") {
      onSelect("number", { aggregation: "count", column: null });
    } else {
      setPendingAgg(agg);
      setStage("pickColumn");
    }
  };

  const handleColumnPick = (col) => {
    onSelect("number", { aggregation: pendingAgg, column: col });
  };

  const handleGroupByColPick = (col) => {
    setGroupByCol(col);
    setStage("groupByAgg");
  };

  const handleGroupByAggPick = (agg) => {
    if (agg === "count") {
      onSelect("chart", { groupByColumn: groupByCol, valueColumn: null, aggregation: "count" });
    } else {
      setPendingGroupByAgg(agg);
      setStage("groupByValCol");
    }
  };

  const handleGroupByValColPick = (colName) => {
    onSelect("chart", { groupByColumn: groupByCol, valueColumn: colName, aggregation: pendingGroupByAgg });
  };

  return createPortal(
    <div
      ref={dropdownRef}
      style={{
        position: "fixed",
        top: pos.top,
        left: pos.left,
        background: "var(--panel)",
        border: "1px solid var(--border)",
        borderRadius: 6,
        minWidth: 220,
        maxHeight: 320,
        overflowY: "auto",
        zIndex: 10000,
        boxShadow: "0 8px 24px rgba(0,0,0,0.4)",
      }}
      onClick={(e) => e.stopPropagation()}
    >
      {stage === "main" && (
        <>
          {panel.savedViewConfig && (
            <>
              <div
                onClick={() => onSelect(panel.savedViewMode || "number", panel.savedViewConfig)}
                style={{
                  padding: "8px 12px",
                  cursor: "pointer",
                  fontSize: 12,
                  display: "flex",
                  justifyContent: "space-between",
                  alignItems: "center",
                  color: "var(--cyan)",
                  background: "rgba(34, 211, 238, 0.05)",
                }}
                onMouseEnter={(e) => { e.currentTarget.style.background = "rgba(34, 211, 238, 0.1)"; }}
                onMouseLeave={(e) => { e.currentTarget.style.background = "rgba(34, 211, 238, 0.05)"; }}
              >
                <span>{formatAggLabel(panel.savedViewConfig, panel.savedViewMode)}</span>
                <span style={{ fontSize: 10 }}>↻</span>
              </div>
              <div style={{ borderTop: "1px solid var(--border)", margin: "2px 0" }} />
            </>
          )}
          <div style={{ padding: "6px 12px", fontSize: 10, color: "var(--text-dim)", textTransform: "uppercase", letterSpacing: "0.05em" }}>
            Single value
          </div>
          {AGGREGATIONS.map((agg) => (
            <div
              key={agg}
              onClick={() => handleAggClick(agg)}
              style={menuItemStyle}
              onMouseEnter={(e) => { e.currentTarget.style.background = "rgba(255,255,255,0.05)"; }}
              onMouseLeave={(e) => { e.currentTarget.style.background = "transparent"; }}
            >
              <span>{agg.toUpperCase()}</span>
              {agg !== "count" && <span style={{ fontSize: 10, color: "var(--text-dim)" }}>▶</span>}
            </div>
          ))}
          <div style={{ borderTop: "1px solid var(--border)", margin: "2px 0" }} />
          <div style={{ padding: "6px 12px", fontSize: 10, color: "var(--text-dim)", textTransform: "uppercase", letterSpacing: "0.05em" }}>
            Grouped
          </div>
          <div
            onClick={() => setStage("groupByCol")}
            style={menuItemStyle}
            onMouseEnter={(e) => { e.currentTarget.style.background = "rgba(255,255,255,0.05)"; }}
            onMouseLeave={(e) => { e.currentTarget.style.background = "transparent"; }}
          >
            <span>Group By...</span>
            <span style={{ fontSize: 10, color: "var(--text-dim)" }}>▶</span>
          </div>
        </>
      )}

      {stage === "pickColumn" && (
        <>
          <div
            onClick={() => { setStage("main"); setPendingAgg(null); }}
            style={{ padding: "6px 12px", cursor: "pointer", fontSize: 11, color: "var(--text-dim)", borderBottom: "1px solid var(--border)" }}
            onMouseEnter={(e) => { e.currentTarget.style.background = "rgba(255,255,255,0.05)"; }}
            onMouseLeave={(e) => { e.currentTarget.style.background = "transparent"; }}
          >
            ◀ {pendingAgg?.toUpperCase()} — pick column
          </div>
          {columns.map((col) => (
            <div
              key={col.name}
              onClick={() => handleColumnPick(col.name)}
              style={menuItemStyle}
              onMouseEnter={(e) => { e.currentTarget.style.background = "rgba(255,255,255,0.05)"; }}
              onMouseLeave={(e) => { e.currentTarget.style.background = "transparent"; }}
            >
              <span>{col.name}</span>
              <span style={{ fontSize: 10, color: "var(--text-dim)" }}>{col.type}</span>
            </div>
          ))}
        </>
      )}

      {stage === "groupByCol" && (
        <>
          <div
            onClick={() => { setStage("main"); setGroupByCol(null); }}
            style={{ padding: "6px 12px", cursor: "pointer", fontSize: 11, color: "var(--text-dim)", borderBottom: "1px solid var(--border)" }}
            onMouseEnter={(e) => { e.currentTarget.style.background = "rgba(255,255,255,0.05)"; }}
            onMouseLeave={(e) => { e.currentTarget.style.background = "transparent"; }}
          >
            ◀ Group By — pick column
          </div>
          {columns.map((col) => (
            <div
              key={col.name}
              onClick={() => handleGroupByColPick(col.name)}
              style={menuItemStyle}
              onMouseEnter={(e) => { e.currentTarget.style.background = "rgba(255,255,255,0.05)"; }}
              onMouseLeave={(e) => { e.currentTarget.style.background = "transparent"; }}
            >
              <span>{col.name}</span>
              <span style={{ fontSize: 10, color: "var(--text-dim)" }}>{col.type}</span>
            </div>
          ))}
        </>
      )}

      {stage === "groupByAgg" && (
        <>
          <div
            onClick={() => { setStage("groupByCol"); }}
            style={{ padding: "6px 12px", cursor: "pointer", fontSize: 11, color: "var(--text-dim)", borderBottom: "1px solid var(--border)" }}
            onMouseEnter={(e) => { e.currentTarget.style.background = "rgba(255,255,255,0.05)"; }}
            onMouseLeave={(e) => { e.currentTarget.style.background = "transparent"; }}
          >
            ◀ Group by {groupByCol} — pick aggregation
          </div>
          {AGGREGATIONS.map((agg) => (
            <div
              key={agg}
              onClick={() => handleGroupByAggPick(agg)}
              style={menuItemStyle}
              onMouseEnter={(e) => { e.currentTarget.style.background = "rgba(255,255,255,0.05)"; }}
              onMouseLeave={(e) => { e.currentTarget.style.background = "transparent"; }}
            >
              <span>{agg.toUpperCase()}</span>
              {agg !== "count" && <span style={{ fontSize: 10, color: "var(--text-dim)" }}>▶</span>}
            </div>
          ))}
        </>
      )}

      {stage === "groupByValCol" && (
        <>
          <div
            onClick={() => { setStage("groupByAgg"); setPendingGroupByAgg(null); }}
            style={{ padding: "6px 12px", cursor: "pointer", fontSize: 11, color: "var(--text-dim)", borderBottom: "1px solid var(--border)" }}
            onMouseEnter={(e) => { e.currentTarget.style.background = "rgba(255,255,255,0.05)"; }}
            onMouseLeave={(e) => { e.currentTarget.style.background = "transparent"; }}
          >
            ◀ {pendingGroupByAgg?.toUpperCase()} — pick column
          </div>
          {columns.filter((c) => c.name !== groupByCol).map((col) => (
            <div
              key={col.name}
              onClick={() => handleGroupByValColPick(col.name)}
              style={menuItemStyle}
              onMouseEnter={(e) => { e.currentTarget.style.background = "rgba(255,255,255,0.05)"; }}
              onMouseLeave={(e) => { e.currentTarget.style.background = "transparent"; }}
            >
              <span>{col.name}</span>
              <span style={{ fontSize: 10, color: "var(--text-dim)" }}>{col.type}</span>
            </div>
          ))}
        </>
      )}
    </div>,
    document.body
  );
}

// Link editor dropdown
function LinkEditor({ panel, panels, onSave, onClose, anchorEl, SOURCE_CONFIG, panelDisplayName }) {
  const dropdownRef = useRef(null);
  const [pos, setPos] = useState({ top: 0, left: 0 });
  const existingLink = (panel.links || [])[0] || null;
  const [parentId, setParentId] = useState(existingLink?.parentPanelId ?? null);
  const [mappings, setMappings] = useState(existingLink?.mappings || []);
  const [addingMapping, setAddingMapping] = useState(false);

  // Candidate parents: all PG/BQ panels except self (and not panels that link to us — cycle check)
  const getAncestors = (pid, visited = new Set()) => {
    if (visited.has(pid)) return visited;
    visited.add(pid);
    const p = panels.find((pp) => pp.id === pid);
    for (const link of p?.links || []) {
      getAncestors(link.parentPanelId, visited);
    }
    return visited;
  };
  const descendants = new Set();
  // Find all panels that eventually link to this panel (would create cycle)
  const findDescendants = (pid) => {
    for (const p of panels) {
      if (descendants.has(p.id)) continue;
      for (const link of p.links || []) {
        if (link.parentPanelId === pid) {
          descendants.add(p.id);
          findDescendants(p.id);
        }
      }
    }
  };
  findDescendants(panel.id);

  const candidateParents = panels.filter((p) =>
    p.id !== panel.id &&
    (p.sourceType === "postgres" || p.sourceType === "bigquery") &&
    !descendants.has(p.id)
  );

  const selectedParent = panels.find((p) => p.id === parentId);
  const parentColumns = selectedParent?.columns || [];
  const childColumns = panel.columns || [];

  // Auto-suggest matching column names
  const unmappedMatches = parentColumns.filter((pc) =>
    childColumns.some((cc) => cc.name === pc.name) &&
    !mappings.some((m) => m.parentColumn === pc.name && m.childColumn === pc.name)
  );

  useEffect(() => {
    if (!anchorEl) return;
    const update = () => {
      const rect = anchorEl.getBoundingClientRect();
      const dropdownWidth = 280;
      let left = rect.right - dropdownWidth;
      if (left < 4) left = rect.left;
      if (left + dropdownWidth > window.innerWidth - 4) left = window.innerWidth - dropdownWidth - 4;
      setPos({ top: rect.bottom + 4, left });
    };
    update();
    window.addEventListener("scroll", update, true);
    window.addEventListener("resize", update);
    return () => {
      window.removeEventListener("scroll", update, true);
      window.removeEventListener("resize", update);
    };
  }, [anchorEl]);

  useEffect(() => {
    const handleClick = (e) => {
      if (dropdownRef.current && !dropdownRef.current.contains(e.target) &&
          (!anchorEl || !anchorEl.contains(e.target))) {
        onClose();
      }
    };
    document.addEventListener("mousedown", handleClick);
    return () => document.removeEventListener("mousedown", handleClick);
  }, [onClose, anchorEl]);

  const handleSave = () => {
    if (!parentId || mappings.length === 0) {
      onSave([]);
    } else {
      onSave([{ parentPanelId: parentId, mappings }]);
    }
    onClose();
  };

  const handleRemoveLink = () => {
    onSave([]);
    onClose();
  };

  const handleSelectParent = (pid) => {
    setParentId(pid);
    // Auto-populate matching columns
    const parent = panels.find((p) => p.id === pid);
    const pCols = parent?.columns || [];
    const auto = pCols
      .filter((pc) => childColumns.some((cc) => cc.name === pc.name))
      .map((pc) => ({ parentColumn: pc.name, childColumn: pc.name }));
    setMappings(auto);
  };

  const addMapping = (parentCol, childCol) => {
    setMappings((prev) => [...prev, { parentColumn: parentCol, childColumn: childCol }]);
    setAddingMapping(false);
  };

  const removeMapping = (idx) => {
    setMappings((prev) => prev.filter((_, i) => i !== idx));
  };

  const menuItemStyle = {
    padding: "7px 12px",
    cursor: "pointer",
    fontSize: 12,
    display: "flex",
    justifyContent: "space-between",
    alignItems: "center",
    color: "var(--text)",
  };

  return createPortal(
    <div
      ref={dropdownRef}
      style={{
        position: "fixed",
        top: pos.top,
        left: pos.left,
        background: "var(--panel)",
        border: "1px solid var(--border)",
        borderRadius: 6,
        minWidth: 280,
        maxHeight: 400,
        overflowY: "auto",
        zIndex: 10000,
        boxShadow: "0 8px 24px rgba(0,0,0,0.4)",
      }}
      onClick={(e) => e.stopPropagation()}
    >
      <div style={{ padding: "8px 12px", fontSize: 10, color: "var(--text-dim)", textTransform: "uppercase", letterSpacing: "0.05em", borderBottom: "1px solid var(--border)" }}>
        Link Panel
      </div>

      {/* Parent selection */}
      <div style={{ padding: "8px 12px" }}>
        <div style={{ fontSize: 10, color: "var(--text-dim)", marginBottom: 4 }}>Parent panel</div>
        {candidateParents.length === 0 ? (
          <div style={{ fontSize: 11, color: "var(--text-dim)", fontStyle: "italic" }}>No other panels available</div>
        ) : (
          <select
            value={parentId || ""}
            onChange={(e) => handleSelectParent(Number(e.target.value))}
            style={{
              width: "100%",
              background: "var(--bg)",
              border: "1px solid var(--border)",
              borderRadius: 4,
              padding: "5px 8px",
              color: "var(--text)",
              fontFamily: "inherit",
              fontSize: 12,
              outline: "none",
            }}
          >
            <option value="">Select parent...</option>
            {candidateParents.map((p) => (
              <option key={p.id} value={p.id}>{panelDisplayName(p)}</option>
            ))}
          </select>
        )}
      </div>

      {/* Column mappings */}
      {parentId && (
        <div style={{ padding: "0 12px 8px" }}>
          <div style={{ fontSize: 10, color: "var(--text-dim)", marginBottom: 4 }}>Column mappings</div>
          {mappings.map((m, i) => (
            <div key={i} style={{ display: "flex", alignItems: "center", gap: 4, marginBottom: 4, fontSize: 11 }}>
              <span style={{ color: "var(--text)", background: "var(--bg)", padding: "2px 6px", borderRadius: 3, border: "1px solid var(--border)" }}>{m.parentColumn}</span>
              <span style={{ color: "var(--text-dim)" }}>=</span>
              <span style={{ color: "var(--text)", background: "var(--bg)", padding: "2px 6px", borderRadius: 3, border: "1px solid var(--border)" }}>{m.childColumn}</span>
              <button
                onClick={() => removeMapping(i)}
                style={{ background: "none", border: "none", color: "var(--text-dim)", cursor: "pointer", fontSize: 11, padding: "0 4px" }}
                onMouseEnter={(e) => { e.currentTarget.style.color = "var(--red)"; }}
                onMouseLeave={(e) => { e.currentTarget.style.color = "var(--text-dim)"; }}
              >
                x
              </button>
            </div>
          ))}

          {/* Auto-suggest matching columns */}
          {!addingMapping && unmappedMatches.length > 0 && mappings.length === 0 && (
            <div style={{ fontSize: 10, color: "var(--text-dim)", fontStyle: "italic", marginBottom: 4 }}>
              Auto-matched {unmappedMatches.length} column{unmappedMatches.length > 1 ? "s" : ""}
            </div>
          )}

          {/* Add mapping */}
          {addingMapping ? (
            <div style={{ display: "flex", gap: 4, alignItems: "center", fontSize: 11 }}>
              <select
                id="link-parent-col"
                style={{ flex: 1, background: "var(--bg)", border: "1px solid var(--border)", borderRadius: 3, padding: "3px 4px", color: "var(--text)", fontFamily: "inherit", fontSize: 11 }}
              >
                {parentColumns.filter((pc) => !mappings.some((m) => m.parentColumn === pc.name)).map((pc) => (
                  <option key={pc.name} value={pc.name}>{pc.name}</option>
                ))}
              </select>
              <span style={{ color: "var(--text-dim)" }}>=</span>
              <select
                id="link-child-col"
                style={{ flex: 1, background: "var(--bg)", border: "1px solid var(--border)", borderRadius: 3, padding: "3px 4px", color: "var(--text)", fontFamily: "inherit", fontSize: 11 }}
              >
                {childColumns.filter((cc) => !mappings.some((m) => m.childColumn === cc.name)).map((cc) => (
                  <option key={cc.name} value={cc.name}>{cc.name}</option>
                ))}
              </select>
              <button
                onClick={() => {
                  const pc = document.getElementById("link-parent-col")?.value;
                  const cc = document.getElementById("link-child-col")?.value;
                  if (pc && cc) addMapping(pc, cc);
                }}
                style={{ background: "none", border: "1px solid var(--border)", borderRadius: 3, padding: "2px 6px", color: "var(--cyan)", cursor: "pointer", fontFamily: "inherit", fontSize: 10 }}
              >
                Add
              </button>
              <button
                onClick={() => setAddingMapping(false)}
                style={{ background: "none", border: "none", color: "var(--text-dim)", cursor: "pointer", fontSize: 11 }}
              >
                x
              </button>
            </div>
          ) : (
            <button
              onClick={() => setAddingMapping(true)}
              style={{ background: "none", border: "none", color: "var(--cyan)", cursor: "pointer", fontFamily: "inherit", fontSize: 11, padding: 0 }}
            >
              + Add column mapping
            </button>
          )}
        </div>
      )}

      {/* Actions */}
      <div style={{ borderTop: "1px solid var(--border)", padding: "8px 12px", display: "flex", gap: 8 }}>
        <button
          onClick={handleSave}
          disabled={parentId && mappings.length === 0}
          style={{
            flex: 1,
            background: parentId && mappings.length > 0 ? "var(--cyan)" : "var(--border)",
            border: "none",
            borderRadius: 4,
            padding: "5px 10px",
            color: parentId && mappings.length > 0 ? "#000" : "var(--text-dim)",
            fontFamily: "inherit",
            fontSize: 11,
            fontWeight: 600,
            cursor: parentId && mappings.length > 0 ? "pointer" : "default",
          }}
        >
          Save
        </button>
        {existingLink && (
          <button
            onClick={handleRemoveLink}
            style={{
              background: "none",
              border: "1px solid var(--red)",
              borderRadius: 4,
              padding: "5px 10px",
              color: "var(--red)",
              fontFamily: "inherit",
              fontSize: 11,
              cursor: "pointer",
            }}
          >
            Remove
          </button>
        )}
      </div>
    </div>,
    document.body
  );
}

const GridLayout = forwardRef(function GridLayout(
  { panels, locked, renderPanel, onPanelClose, onToggleFilters, onLayoutChange, onViewModeChange, onCopyPanel, onChartTypeChange, onUpdatePanelLinks, focusPanelId, focusCounter, SOURCE_CONFIG, panelDisplayName },
  ref
) {
  const gridRef = useRef(null);
  const gridElRef = useRef(null);
  const widgetRefs = useRef({}); // panelId -> { el, portalRoot }
  const onLayoutChangeRef = useRef(onLayoutChange);
  const [mounted, setMounted] = useState(false);
  const [syncKey, setSyncKey] = useState(0);
  const [openSummary, setOpenSummary] = useState(null); // panelId with open summary dropdown
  const summaryAnchorRef = useRef(null); // DOM element of the Σ button that opened the dropdown
  const [openMenu, setOpenMenu] = useState(null); // panelId with open kebab menu
  const menuAnchorRef = useRef(null);
  const [openLinkEditor, setOpenLinkEditor] = useState(null); // panelId with open link editor
  const linkAnchorRef = useRef(null);
  const savedWidths = useRef({}); // panelId -> saved width before full-row
  const savedHeights = useRef({}); // panelId -> saved height before full-column

  // Keep callback ref fresh
  useEffect(() => { onLayoutChangeRef.current = onLayoutChange; }, [onLayoutChange]);

  // Expose resizePanel to parent via ref
  useImperativeHandle(ref, () => ({
    resizePanel: (panelId, w, h) => {
      const grid = gridRef.current;
      const widgetRef = widgetRefs.current[String(panelId)];
      if (grid && widgetRef) {
        grid.update(widgetRef.el, { w, h });
      }
    },
    getPanelSize: (panelId) => {
      const widgetRef = widgetRefs.current[String(panelId)];
      if (widgetRef) {
        const node = widgetRef.el.gridstackNode;
        if (node) return { w: node.w, h: node.h };
      }
      return null;
    },
  }), []);

  // Initialize grid
  useEffect(() => {
    if (!gridElRef.current) return;
    const grid = GridStack.init(GRID_OPTIONS, gridElRef.current);
    gridRef.current = grid;
    setMounted(true);

    const reportPositions = (_event, items) => {
      if (!onLayoutChangeRef.current || !items) return;
      const positions = {};
      for (const item of items) {
        if (item.id) {
          positions[item.id] = { x: item.x, y: item.y, w: item.w, h: item.h };
        }
      }
      onLayoutChangeRef.current(positions);
    };
    grid.on("change", reportPositions);
    grid.on("added", reportPositions);

    return () => {
      grid.off("change");
      grid.off("added");
      grid.destroy(false);
      gridRef.current = null;
      widgetRefs.current = {};
      setMounted(false);
    };
  }, []);

  // Sync panels -> widgets
  useEffect(() => {
    if (!gridRef.current || !mounted) return;
    const grid = gridRef.current;
    const currentPanelIds = new Set(panels.map((p) => String(p.id)));
    const existingIds = new Set(Object.keys(widgetRefs.current));
    let changed = false;

    for (const panel of panels) {
      const panelId = String(panel.id);
      if (!existingIds.has(panelId)) {
        const pos = panel.gridPos;
        const el = document.createElement("div");
        el.className = "grid-stack-item";
        el.setAttribute("gs-id", panelId);
        el.setAttribute("gs-w", String(pos?.w ?? 6));
        el.setAttribute("gs-h", String(pos?.h ?? 5));
        if (pos && pos.x != null && pos.y != null) {
          el.setAttribute("gs-x", String(pos.x));
          el.setAttribute("gs-y", String(pos.y));
        } else {
          el.setAttribute("gs-auto-position", "true");
        }

        const content = document.createElement("div");
        content.className = "grid-stack-item-content widget-content-wrapper";
        el.appendChild(content);

        const portalRoot = document.createElement("div");
        portalRoot.className = "widget-portal-root";
        content.appendChild(portalRoot);

        gridElRef.current.appendChild(el);
        grid.makeWidget(el);
        widgetRefs.current[panelId] = { el, portalRoot };
        changed = true;
      }
    }

    for (const id of existingIds) {
      if (!currentPanelIds.has(id)) {
        const { el } = widgetRefs.current[id];
        grid.removeWidget(el, true);
        delete widgetRefs.current[id];
        changed = true;
      }
    }

    if (changed) {
      setSyncKey((k) => k + 1);
    }
  }, [panels, mounted]);

  // Lock/unlock grid
  useEffect(() => {
    if (!gridRef.current || !mounted) return;
    gridRef.current.setStatic(!!locked);
  }, [locked, mounted]);

  // Focus widget
  useEffect(() => {
    if (!focusPanelId || !widgetRefs.current[String(focusPanelId)]) return;
    const { el } = widgetRefs.current[String(focusPanelId)];
    el.scrollIntoView({ behavior: "smooth", block: "nearest" });
    el.classList.add("widget-focused");
    const timer = setTimeout(() => el.classList.remove("widget-focused"), 1000);
    return () => clearTimeout(timer);
  }, [focusPanelId, focusCounter]);

  const handleFullRow = (panelId) => {
    const grid = gridRef.current;
    const widgetRef = widgetRefs.current[String(panelId)];
    if (!grid || !widgetRef) return;
    const node = widgetRef.el.gridstackNode;
    if (!node) return;
    const key = String(panelId);
    if (node.w === 12 && savedWidths.current[key]) {
      grid.update(widgetRef.el, { w: savedWidths.current[key] });
      delete savedWidths.current[key];
    } else {
      savedWidths.current[key] = node.w;
      grid.update(widgetRef.el, { w: 12 });
    }
  };

  const handleFullColumn = (panelId) => {
    const grid = gridRef.current;
    const widgetRef = widgetRefs.current[String(panelId)];
    if (!grid || !widgetRef) return;
    const node = widgetRef.el.gridstackNode;
    if (!node) return;
    const key = String(panelId);
    if (node.h === 12 && savedHeights.current[key]) {
      grid.update(widgetRef.el, { h: savedHeights.current[key] });
      delete savedHeights.current[key];
    } else {
      savedHeights.current[key] = node.h;
      grid.update(widgetRef.el, { h: 12 });
    }
  };

  const handleSummarySelect = (panelId, mode, config) => {
    setOpenSummary(null);
    if (onViewModeChange) onViewModeChange(panelId, mode, config);
  };

  const handleBackToTable = (panelId) => {
    setOpenSummary(null);
    if (onViewModeChange) onViewModeChange(panelId, "table", null);
  };

  return (
    <div style={{ flex: 1, overflow: "auto", padding: 4 }}>
      <div ref={gridElRef} className="grid-stack" />
      {mounted &&
        panels.map((panel) => {
          const widgetRef = widgetRefs.current[String(panel.id)];
          if (!widgetRef) return null;
          const cfg = SOURCE_CONFIG[panel.sourceType];
          const filtersOn = panel.filtersVisible !== false;
          const isPg = panel.sourceType === "postgres";
          const viewMode = panel.viewMode || "table";
          return createPortal(
            <div key={panel.id} style={{ display: "flex", flexDirection: "column", height: "100%", overflow: "hidden" }}>
              {/* Widget header - hidden when locked */}
              {!locked && (
              <div
                className="widget-drag-handle"
                style={{
                  display: "flex",
                  alignItems: "center",
                  gap: 4,
                  padding: "4px 8px",
                  background: "var(--panel)",
                  borderBottom: "1px solid var(--border)",
                  cursor: "grab",
                  flexShrink: 0,
                  minHeight: 28,
                }}
              >
                <span style={{ color: "var(--text-dim)", fontSize: 10, cursor: "grab", userSelect: "none" }}>⠿</span>
                <span
                  style={{
                    fontSize: 9,
                    fontWeight: 700,
                    color: cfg.color,
                    background: cfg.color + "20",
                    padding: "1px 4px",
                    borderRadius: 3,
                    letterSpacing: "0.05em",
                  }}
                >
                  {cfg.label}
                </span>
                <span style={{ fontSize: 11, color: "var(--text)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", marginRight: 4 }}>
                  {panelDisplayName(panel)}
                </span>
                {panel.links?.length > 0 && (() => {
                  const parentPanel = panels.find((p) => p.id === panel.links[0].parentPanelId);
                  const hasActiveFilter = parentPanel?.selectedRow != null;
                  return (
                    <span
                      style={{
                        fontSize: 9,
                        color: hasActiveFilter ? "var(--cyan)" : "var(--text-dim)",
                        background: hasActiveFilter ? "rgba(34, 211, 238, 0.10)" : "rgba(255,255,255,0.06)",
                        padding: "1px 5px",
                        borderRadius: 3,
                        whiteSpace: "nowrap",
                        flexShrink: 0,
                      }}
                    >
                      {"⛓"} {parentPanel ? panelDisplayName(parentPanel) : "?"}
                    </span>
                  );
                })()}
                {panel.loading && (
                  <span style={{ color: "var(--yellow)", fontSize: 10, flexShrink: 0 }}>...</span>
                )}

                <div style={{ flex: 1 }} />

                {/* Toolbar */}
                <div style={{ display: "flex", alignItems: "center", gap: 2, flexShrink: 0 }}>
                  {/* Kebab menu */}
                  <button
                    onClick={(e) => {
                      e.stopPropagation();
                      menuAnchorRef.current = e.currentTarget;
                      setOpenMenu(openMenu === panel.id ? null : panel.id);
                    }}
                    title="Panel options"
                    style={toolBtnStyle}
                    onMouseEnter={(e) => { e.currentTarget.style.borderColor = "var(--text-dim)"; }}
                    onMouseLeave={(e) => { e.currentTarget.style.borderColor = "var(--border)"; }}
                  >
                    ⋮
                  </button>
                  {openMenu === panel.id && (
                    <PanelMenu
                      panelId={panel.id}
                      panel={panel}
                      onCopy={onCopyPanel}
                      onChartTypeChange={onChartTypeChange}
                      onClose={() => setOpenMenu(null)}
                      anchorEl={menuAnchorRef.current}
                    />
                  )}

                  {/* Link button (PG/BQ panels only) */}
                  {isPg && (
                    <>
                      <button
                        onClick={(e) => {
                          e.stopPropagation();
                          linkAnchorRef.current = e.currentTarget;
                          setOpenLinkEditor(openLinkEditor === panel.id ? null : panel.id);
                        }}
                        title={panel.links?.length > 0 ? `Linked to ${panelDisplayName(panels.find((p) => p.id === panel.links[0]?.parentPanelId) || {})}` : "Link to parent panel"}
                        style={panel.links?.length > 0 ? toolBtnActiveStyle : toolBtnStyle}
                        onMouseEnter={(e) => { if (!panel.links?.length) e.currentTarget.style.borderColor = "var(--text-dim)"; }}
                        onMouseLeave={(e) => { if (!panel.links?.length) e.currentTarget.style.borderColor = "var(--border)"; }}
                      >
                        {panel.links?.length > 0 ? "\u{1F517}" : "\u{1F517}"}
                      </button>
                      {openLinkEditor === panel.id && (
                        <LinkEditor
                          panel={panel}
                          panels={panels}
                          onSave={(links) => onUpdatePanelLinks(panel.id, links)}
                          onClose={() => setOpenLinkEditor(null)}
                          anchorEl={linkAnchorRef.current}
                          SOURCE_CONFIG={SOURCE_CONFIG}
                          panelDisplayName={panelDisplayName}
                        />
                      )}
                    </>
                  )}

                  {/* Filters toggle */}
                  <button
                    onClick={(e) => { e.stopPropagation(); onToggleFilters(panel.id); }}
                    title={filtersOn ? "Hide filters" : "Show filters"}
                    style={{
                      ...toolBtnStyle,
                      color: filtersOn ? "var(--text)" : "var(--text-dim)",
                      background: filtersOn ? "rgba(255,255,255,0.04)" : "none",
                      gap: 3,
                      paddingLeft: 6,
                      paddingRight: 7,
                      minWidth: "auto",
                    }}
                    onMouseEnter={(e) => { e.currentTarget.style.borderColor = "var(--text-dim)"; }}
                    onMouseLeave={(e) => { e.currentTarget.style.borderColor = "var(--border)"; }}
                  >
                    <span style={{ fontSize: 8 }}>{filtersOn ? "\u25BE" : "\u25B8"}</span>
                    <span>filters</span>
                  </button>

                  {/* PostgreSQL: Table + Summary buttons */}
                  {isPg && (
                    <>
                      {/* Table view button */}
                      <button
                        onClick={(e) => { e.stopPropagation(); handleBackToTable(panel.id); }}
                        title="Table view"
                        style={viewMode === "table" ? toolBtnActiveStyle : toolBtnStyle}
                        onMouseEnter={(e) => { if (viewMode !== "table") e.currentTarget.style.borderColor = "var(--text-dim)"; }}
                        onMouseLeave={(e) => { if (viewMode !== "table") e.currentTarget.style.borderColor = "var(--border)"; }}
                      >
                        ⊞
                      </button>

                      {/* Summary button: direct restore if saved config exists, otherwise open dropdown */}
                      <button
                        onClick={(e) => {
                          e.stopPropagation();
                          if (viewMode === "table" && panel.savedViewConfig) {
                            // Directly restore saved aggregation
                            handleSummarySelect(panel.id, panel.savedViewMode || "number", panel.savedViewConfig);
                          } else {
                            // Open dropdown to configure (or change) aggregation
                            summaryAnchorRef.current = e.currentTarget;
                            setOpenSummary(openSummary === panel.id ? null : panel.id);
                          }
                        }}
                        title={viewMode === "table" && panel.savedViewConfig
                          ? `Restore: ${formatAggLabel(panel.savedViewConfig, panel.savedViewMode)}`
                          : "Summary view"}
                        style={viewMode !== "table" ? toolBtnActiveStyle : panel.savedViewConfig ? { ...toolBtnStyle, color: "var(--cyan)" } : toolBtnStyle}
                        onMouseEnter={(e) => { if (viewMode === "table") e.currentTarget.style.borderColor = "var(--text-dim)"; }}
                        onMouseLeave={(e) => { if (viewMode === "table") e.currentTarget.style.borderColor = "var(--border)"; }}
                      >
                        Σ
                      </button>
                      {openSummary === panel.id && (
                        <SummaryDropdown
                          panel={panel}
                          columns={panel.columns || []}
                          onSelect={(mode, config) => handleSummarySelect(panel.id, mode, config)}
                          onClose={() => setOpenSummary(null)}
                          anchorEl={summaryAnchorRef.current}
                        />
                      )}
                    </>
                  )}

                  {(() => {
                    const isFullRow = panel.gridPos?.w === 12;
                    const isFullCol = panel.gridPos?.h === 12;
                    return (
                    <>
                      <span style={separatorStyle}>|</span>

                      <button
                        onClick={(e) => { e.stopPropagation(); handleFullRow(panel.id); }}
                        title={isFullRow ? "Restore width" : "Full row"}
                        style={isFullRow ? toolBtnActiveStyle : toolBtnStyle}
                        onMouseEnter={(e) => { if (!isFullRow) e.currentTarget.style.borderColor = "var(--text-dim)"; }}
                        onMouseLeave={(e) => { if (!isFullRow) e.currentTarget.style.borderColor = "var(--border)"; }}
                      >
                        ⇔
                      </button>

                      <button
                        onClick={(e) => { e.stopPropagation(); handleFullColumn(panel.id); }}
                        title={isFullCol ? "Restore height" : "Full column"}
                        style={isFullCol ? toolBtnActiveStyle : toolBtnStyle}
                        onMouseEnter={(e) => { if (!isFullCol) e.currentTarget.style.borderColor = "var(--text-dim)"; }}
                        onMouseLeave={(e) => { if (!isFullCol) e.currentTarget.style.borderColor = "var(--border)"; }}
                      >
                        ⇕
                      </button>

                      <button
                        onClick={(e) => { e.stopPropagation(); onPanelClose(panel.id); }}
                        style={{
                          ...toolBtnStyle,
                          border: "none",
                          color: "var(--text-dim)",
                          fontSize: 12,
                          minWidth: 18,
                          padding: "0 2px",
                        }}
                        onMouseEnter={(e) => { e.currentTarget.style.color = "var(--red)"; e.currentTarget.style.background = "var(--red-bg)"; }}
                        onMouseLeave={(e) => { e.currentTarget.style.color = "var(--text-dim)"; e.currentTarget.style.background = "none"; }}
                      >
                        ×
                      </button>
                    </>
                    );
                  })()}
                </div>
              </div>
              )}
              {/* Widget body */}
              <div style={{ flex: 1, overflow: "hidden", display: "flex", flexDirection: "column" }}>
                {renderPanel(panel)}
              </div>
            </div>,
            widgetRef.portalRoot
          );
        })}
    </div>
  );
});

export default GridLayout;
