import React, { useState, useEffect, useRef, useCallback } from "react";
import PostgresTabContent from "./PostgresTabContent";
import RedisTabContent from "./RedisTabContent";
import S3TabContent from "./S3TabContent";
import GridLayout from "./GridLayout";
import {
  checkHealth,
  pgConnect, pgFetchTables,
  bigqueryConnect,
  redisConnect,
  s3Connect, s3FetchBuckets,
} from "./api";

const DEFAULT_PAGE_SIZE = 100;
const STORAGE_KEY = "mdash_state";

const SOURCE_CONFIG = {
  postgres: { color: "var(--purple)", label: "PG" },
  bigquery: { color: "var(--green)", label: "BQ" },
  redis: { color: "var(--red)", label: "RD" },
  s3: { color: "var(--orange)", label: "S3" },
};

let nextDashboardId = 1;
let nextPanelId = 1;
let copiedPanelData = null;

// Strip transient/fetched data before saving to localStorage
function stripTransient(dashboards) {
  return dashboards.map((d) => ({
    ...d,
    panels: d.panels.map((p) => {
      const { results, loading, columns, tokens, nextToken, selectedRow, ...rest } = p;
      return rest;
    }),
  }));
}

function loadSavedState() {
  try {
    const raw = localStorage.getItem(STORAGE_KEY);
    if (!raw) return null;
    const saved = JSON.parse(raw);
    if (!saved.dashboards?.length) return null;
    // Restore ID counters to avoid collisions
    let maxDash = 0;
    let maxPanel = 0;
    for (const d of saved.dashboards) {
      if (d.id > maxDash) maxDash = d.id;
      for (const p of d.panels) {
        if (p.id > maxPanel) maxPanel = p.id;
      }
    }
    nextDashboardId = maxDash + 1;
    nextPanelId = maxPanel + 1;
    // Re-add transient defaults to panels
    const dashboards = saved.dashboards.map((d) => ({
      ...d,
      locked: d.locked ?? false,
      panels: d.panels.map((p) => ({
        results: null,
        loading: false,
        selectedRow: null,
        ...(p.sourceType === "postgres" || p.sourceType === "bigquery" ? { columns: [], links: p.links ?? [] } : {}),
        ...(p.sourceType === "s3" ? { tokens: [null], nextToken: null } : {}),
        ...p,
      })),
    }));
    return { dashboards, activeDashboardId: saved.activeDashboardId, autoRefreshInterval: saved.autoRefreshInterval ?? null };
  } catch {
    return null;
  }
}

const DEFAULT_VIEW_SIZES = {
  table: { w: 6, h: 5 },
  number: { w: 3, h: 2 },
  chart: { w: 6, h: 5 },
};

function makePgPanel(instanceId, instanceLabel, schema, name, sourceType = "postgres") {
  return {
    id: nextPanelId++,
    sourceType,
    instanceId,
    instanceLabel,
    schema,
    name,
    displayName: `${schema}.${name}`,
    filters: { include: "", exclude: "", highlight: "", columns: "" },
    highlightOptions: { caseSensitive: false, wholeWord: false, regex: false },
    page: 0,
    columns: [],
    results: null,
    loading: false,
    filtersVisible: true,
    viewMode: "table",
    viewConfig: null,
    savedViewConfig: null,
    savedViewMode: null,
    viewSizes: {},
    chartType: "hbar",
    links: [],
    selectedRow: null,
  };
}

function makeRedisPanel(instanceId, instanceLabel) {
  return {
    id: nextPanelId++,
    sourceType: "redis",
    instanceId,
    instanceLabel,
    displayName: "keys",
    filter: "",
    page: 0,
    results: null,
    loading: false,
    filtersVisible: true,
  };
}

function makeS3Panel(instanceId, instanceLabel, bucket) {
  return {
    id: nextPanelId++,
    sourceType: "s3",
    instanceId,
    instanceLabel,
    bucket,
    displayName: bucket,
    filter: "",
    page: 0,
    tokens: [null],
    nextToken: null,
    results: null,
    loading: false,
    filtersVisible: true,
  };
}

function makeDashboard(name) {
  return { id: nextDashboardId++, name, panels: [], locked: false };
}

function exportToFile(data, filename) {
  const json = JSON.stringify(data, null, 2);
  const blob = new Blob([json], { type: "application/json" });
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = filename;
  a.click();
  URL.revokeObjectURL(url);
}

function validateImportData(data) {
  if (!data || typeof data !== "object") return null;
  // Accept either { dashboards: [...] } or { dashboard: {...} }
  let dashboards;
  if (Array.isArray(data.dashboards)) {
    dashboards = data.dashboards;
  } else if (data.dashboard && typeof data.dashboard === "object") {
    dashboards = [data.dashboard];
  } else {
    return null;
  }
  for (const d of dashboards) {
    if (typeof d.name !== "string" || !Array.isArray(d.panels)) return null;
    for (const p of d.panels) {
      if (!p.sourceType || typeof p.sourceType !== "string") return null;
    }
  }
  return dashboards;
}

function remapIds(dashboards) {
  return dashboards.map((d) => {
    // Build old→new panel ID mapping for link remapping
    const idMap = {};
    const panels = d.panels.map((p) => {
      const newId = nextPanelId++;
      idMap[p.id] = newId;
      return {
        results: null,
        loading: false,
        selectedRow: null,
        ...(p.sourceType === "postgres" || p.sourceType === "bigquery" ? { columns: [], links: p.links ?? [] } : {}),
        ...(p.sourceType === "s3" ? { tokens: [null], nextToken: null } : {}),
        ...p,
        id: newId,
      };
    });
    // Remap parentPanelId in links
    for (const panel of panels) {
      if (panel.links) {
        panel.links = panel.links
          .map((link) => ({ ...link, parentPanelId: idMap[link.parentPanelId] }))
          .filter((link) => link.parentPanelId != null);
      }
    }
    return { ...d, id: nextDashboardId++, panels };
  });
}

export default function App() {
  const [instances, setInstances] = useState([]);
  const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
  const [error, setError] = useState(null);
  const [appVersion, setAppVersion] = useState(null);

  // Dashboard state (restore from localStorage if available)
  const [savedState] = useState(() => loadSavedState());
  const [dashboards, setDashboards] = useState(() => savedState ? savedState.dashboards : [makeDashboard("Dashboard 1")]);
  const [activeDashboardId, setActiveDashboardId] = useState(() => savedState ? savedState.activeDashboardId : 1);
  const [focusPanelId, setFocusPanelId] = useState(null);
  const [focusCounter, setFocusCounter] = useState(0);
  const [refreshKey, setRefreshKey] = useState(0);
  const [editingDashboardId, setEditingDashboardId] = useState(null);

  // Auto-refresh state
  const [autoRefreshInterval, setAutoRefreshInterval] = useState(() => savedState?.autoRefreshInterval ?? null); // null = off, number = minutes
  const [showRefreshMenu, setShowRefreshMenu] = useState(false);
  const [customMinutes, setCustomMinutes] = useState("");
  const autoRefreshTimerRef = useRef(null);
  const refreshMenuRef = useRef(null);

  // Grid layout ref (for programmatic resizing)
  const gridLayoutRef = useRef(null);

  // Source picker (for adding panels)
  const [showSourcePicker, setShowSourcePicker] = useState(false);
  const [pickerStage, setPickerStage] = useState("type");
  const [pickerType, setPickerType] = useState(null);
  const [pickerInstance, setPickerInstance] = useState(null);
  const pickerRef = useRef(null);

  // Connection forms
  const [showConnectForm, setShowConnectForm] = useState(null);

  // Caches
  const [pgTablesCache, setPgTablesCache] = useState({});
  const [s3BucketsCache, setS3BucketsCache] = useState({});

  // Connection form state
  const [pgConnStr, setPgConnStr] = useState("");
  const [pgLabel, setPgLabel] = useState("");
  const [redisConnStr, setRedisConnStr] = useState("");
  const [redisLabel, setRedisLabel] = useState("");
  const [s3Endpoint, setS3Endpoint] = useState("");
  const [s3AccessKey, setS3AccessKey] = useState("");
  const [s3SecretKey, setS3SecretKey] = useState("");
  const [s3Region, setS3Region] = useState("us-east-1");
  const [s3Label, setS3Label] = useState("");
  const [bqProject, setBqProject] = useState("");
  const [bqDataset, setBqDataset] = useState("");
  const [bqCredentials, setBqCredentials] = useState("");
  const [bqLabel, setBqLabel] = useState("");
  const [connecting, setConnecting] = useState(false);

  // Derived
  const activeDashboard = dashboards.find((d) => d.id === activeDashboardId) || null;
  const activePanels = activeDashboard?.panels || [];

  const instancesOfType = (type) => instances.filter((i) => i.type === type && i.status === "connected");
  const multipleInstances = (type) => instancesOfType(type).length > 1;
  const hasAnyInstances = instances.some((i) => i.status === "connected");

  // Update a panel's state (searches all dashboards by panelId)
  const updatePanel = useCallback((panelId, patch) => {
    setDashboards((prev) =>
      prev.map((d) => ({
        ...d,
        panels: d.panels.map((p) => (p.id === panelId ? { ...p, ...patch } : p)),
      }))
    );
  }, []);

  // Dashboard management
  const addDashboard = () => {
    const d = makeDashboard(`Dashboard ${nextDashboardId}`);
    setDashboards((prev) => [...prev, d]);
    setActiveDashboardId(d.id);
  };

  const renameDashboard = (dashId, newName) => {
    const trimmed = newName.trim();
    if (!trimmed) return;
    setDashboards((prev) =>
      prev.map((d) => (d.id === dashId ? { ...d, name: trimmed } : d))
    );
  };

  const closeDashboard = (dashId, e) => {
    if (e) e.stopPropagation();
    setDashboards((prev) => {
      const next = prev.filter((d) => d.id !== dashId);
      if (activeDashboardId === dashId) {
        const idx = prev.findIndex((d) => d.id === dashId);
        const newActive = next[Math.min(idx, next.length - 1)];
        setActiveDashboardId(newActive ? newActive.id : null);
      }
      return next;
    });
  };

  // Panel management — adds to active dashboard
  const addPanelToActive = (panel) => {
    setDashboards((prev) =>
      prev.map((d) =>
        d.id === activeDashboardId ? { ...d, panels: [...d.panels, panel] } : d
      )
    );
    setFocusPanelId(panel.id);
    setFocusCounter((c) => c + 1);
  };

  const closePanel = (panelId) => {
    setDashboards((prev) =>
      prev.map((d) => ({
        ...d,
        panels: d.panels.filter((p) => p.id !== panelId),
      }))
    );
  };

  const toggleDashboardLock = (dashId) => {
    setDashboards((prev) =>
      prev.map((d) => (d.id === dashId ? { ...d, locked: !d.locked } : d))
    );
  };

  const updatePanelLinks = useCallback((panelId, links) => {
    setDashboards((prev) =>
      prev.map((d) => ({
        ...d,
        panels: d.panels.map((p) =>
          p.id === panelId ? { ...p, links, selectedRow: null } : p
        ),
      }))
    );
  }, []);

  const togglePanelFilters = useCallback((panelId) => {
    setDashboards((prev) =>
      prev.map((d) => ({
        ...d,
        panels: d.panels.map((p) =>
          p.id === panelId ? { ...p, filtersVisible: !p.filtersVisible } : p
        ),
      }))
    );
  }, []);

  const handleViewModeChange = useCallback((panelId, newMode, newConfig) => {
    // Get current panel and its size from the grid
    const currentSize = gridLayoutRef.current?.getPanelSize(panelId);
    setDashboards((prev) =>
      prev.map((d) => ({
        ...d,
        panels: d.panels.map((p) => {
          if (p.id !== panelId) return p;
          const currentMode = p.viewMode || "table";
          const effectiveNewConfig = newMode === "table" ? null : newConfig;
          // No-op if mode and config haven't changed
          if (currentMode === newMode && JSON.stringify(p.viewConfig) === JSON.stringify(effectiveNewConfig)) {
            return p;
          }
          // Save current size for current mode
          const updatedSizes = { ...p.viewSizes };
          if (currentSize) {
            updatedSizes[currentMode] = { w: currentSize.w, h: currentSize.h };
          }
          // Save aggregation config so it persists across view switches
          const savedViewConfig = newMode === "table"
            ? (p.viewConfig || p.savedViewConfig)
            : newConfig;
          const savedViewMode = newMode === "table"
            ? (p.viewMode !== "table" ? p.viewMode : p.savedViewMode)
            : newMode;
          return {
            ...p,
            viewMode: newMode,
            viewConfig: effectiveNewConfig,
            savedViewConfig,
            savedViewMode,
            viewSizes: updatedSizes,
            results: null,
          };
        }),
      }))
    );
    // Resize to target size after state update
    requestAnimationFrame(() => {
      const panel = activePanels.find((p) => p.id === panelId);
      const savedSizes = panel?.viewSizes || {};
      const currentMode = panel?.viewMode || "table";
      // Save current size into savedSizes for lookup
      if (currentSize) savedSizes[currentMode] = { w: currentSize.w, h: currentSize.h };
      const target = savedSizes[newMode] || DEFAULT_VIEW_SIZES[newMode] || { w: 6, h: 5 };
      gridLayoutRef.current?.resizePanel(panelId, target.w, target.h);
    });
  }, [activePanels]);

  const handleChartTypeChange = useCallback((panelId, chartType) => {
    setDashboards((prev) =>
      prev.map((d) => ({
        ...d,
        panels: d.panels.map((p) => p.id === panelId ? { ...p, chartType } : p),
      }))
    );
  }, []);

  // Copy/paste panel
  const copyPanel = useCallback((panelId) => {
    const panel = activePanels.find((p) => p.id === panelId);
    if (!panel) return;
    const { results, loading, columns, tokens, nextToken, id, gridPos, selectedRow, ...rest } = panel;
    copiedPanelData = rest;
  }, [activePanels]);

  const pastePanel = useCallback(() => {
    if (!copiedPanelData) return;
    const newPanel = {
      ...copiedPanelData,
      id: nextPanelId++,
      results: null,
      loading: false,
      ...(copiedPanelData.sourceType === "postgres" || copiedPanelData.sourceType === "bigquery" ? { columns: [] } : {}),
      ...(copiedPanelData.sourceType === "s3" ? { tokens: [null], nextToken: null } : {}),
    };
    addPanelToActive(newPanel);
  }, [addPanelToActive]);

  // Import/export state
  const [showConfigMenu, setShowConfigMenu] = useState(false);
  const configMenuRef = useRef(null);
  const importInputRef = useRef(null);

  const exportCurrentDashboard = useCallback(() => {
    if (!activeDashboard) return;
    const stripped = stripTransient([activeDashboard])[0];
    const ts = new Date().toISOString().slice(0, 10);
    exportToFile({ dashboard: stripped }, `mdash-${stripped.name.replace(/\s+/g, "-").toLowerCase()}-${ts}.json`);
    setShowConfigMenu(false);
  }, [activeDashboard]);

  const exportAllDashboards = useCallback(() => {
    const stripped = stripTransient(dashboards);
    const ts = new Date().toISOString().slice(0, 10);
    exportToFile({ dashboards: stripped, activeDashboardId, autoRefreshInterval }, `mdash-all-dashboards-${ts}.json`);
    setShowConfigMenu(false);
  }, [dashboards, activeDashboardId, autoRefreshInterval]);

  const handleImportFile = useCallback((e) => {
    const file = e.target.files?.[0];
    if (!file) return;
    const reader = new FileReader();
    reader.onload = (ev) => {
      try {
        const data = JSON.parse(ev.target.result);
        const imported = validateImportData(data);
        if (!imported) {
          setError("Invalid dashboard config file. Expected JSON with 'dashboard' or 'dashboards' key.");
          return;
        }
        const remapped = remapIds(imported);
        setDashboards((prev) => [...prev, ...remapped]);
        setActiveDashboardId(remapped[0].id);
      } catch {
        setError("Failed to parse JSON file.");
      }
    };
    reader.readAsText(file);
    // Reset input so the same file can be re-imported
    e.target.value = "";
    setShowConfigMenu(false);
  }, []);

  useEffect(() => {
    const handleKeyDown = (e) => {
      if ((e.metaKey || e.ctrlKey) && e.key === "v") {
        const tag = document.activeElement?.tagName;
        if (tag === "INPUT" || tag === "TEXTAREA" || document.activeElement?.isContentEditable) return;
        if (!copiedPanelData) return;
        e.preventDefault();
        pastePanel();
      }
    };
    document.addEventListener("keydown", handleKeyDown);
    return () => document.removeEventListener("keydown", handleKeyDown);
  }, [pastePanel]);

  const addPgPanel = (instanceId, instanceLabel, schema, name, sourceType = "postgres") => {
    const existing = activePanels.find(
      (p) => (p.sourceType === "postgres" || p.sourceType === "bigquery") && p.instanceId === instanceId && p.schema === schema && p.name === name
    );
    if (existing) {
      setFocusPanelId(existing.id);
      setFocusCounter((c) => c + 1);
    } else {
      addPanelToActive(makePgPanel(instanceId, instanceLabel, schema, name, sourceType));
    }
    resetPicker();
  };

  const addRedisPanel = (instanceId, instanceLabel) => {
    const existing = activePanels.find((p) => p.sourceType === "redis" && p.instanceId === instanceId);
    if (existing) {
      setFocusPanelId(existing.id);
      setFocusCounter((c) => c + 1);
    } else {
      addPanelToActive(makeRedisPanel(instanceId, instanceLabel));
    }
    resetPicker();
  };

  const addS3Panel = (instanceId, instanceLabel, bucket) => {
    const existing = activePanels.find(
      (p) => p.sourceType === "s3" && p.instanceId === instanceId && p.bucket === bucket
    );
    if (existing) {
      setFocusPanelId(existing.id);
      setFocusCounter((c) => c + 1);
    } else {
      addPanelToActive(makeS3Panel(instanceId, instanceLabel, bucket));
    }
    resetPicker();
  };

  // Check health on mount
  useEffect(() => {
    checkHealth()
      .then((data) => {
        if (data.version) setAppVersion(data.version);
        if (data.page_size) setPageSize(data.page_size);
        const all = [];
        for (const inst of data.sources?.postgres || []) {
          all.push({ id: inst.id, type: "postgres", label: inst.label, status: inst.status });
        }
        for (const inst of data.sources?.bigquery || []) {
          all.push({ id: inst.id, type: "bigquery", label: inst.label, status: inst.status });
        }
        for (const inst of data.sources?.redis || []) {
          all.push({ id: inst.id, type: "redis", label: inst.label, status: inst.status });
        }
        for (const inst of data.sources?.s3 || []) {
          all.push({ id: inst.id, type: "s3", label: inst.label, status: inst.status });
        }
        setInstances(all);
        for (const inst of all) {
          if (inst.status !== "connected") continue;
          if (inst.type === "postgres" || inst.type === "bigquery") {
            pgFetchTables(inst.id)
              .then((d) => setPgTablesCache((prev) => ({ ...prev, [inst.id]: d.tables })))
              .catch(() => {});
          }
          if (inst.type === "s3") {
            s3FetchBuckets(inst.id)
              .then((d) => setS3BucketsCache((prev) => ({ ...prev, [inst.id]: d.buckets })))
              .catch(() => {});
          }
        }
      })
      .catch(() => setInstances([]));
  }, []);

  // Connection handlers
  const handlePgConnect = () => {
    setConnecting(true);
    setError(null);
    pgConnect(pgConnStr, pgLabel)
      .then((data) => {
        const inst = { id: data.instance_id, type: "postgres", label: data.label || data.instance_id, status: "connected" };
        setInstances((prev) => [...prev, inst]);
        setShowConnectForm(null);
        setPgConnStr("");
        setPgLabel("");
        return pgFetchTables(data.instance_id).then((d) => {
          setPgTablesCache((prev) => ({ ...prev, [data.instance_id]: d.tables }));
        });
      })
      .catch((e) => setError(e.message))
      .finally(() => setConnecting(false));
  };

  const handleRedisConnect = () => {
    setConnecting(true);
    setError(null);
    redisConnect(redisConnStr, redisLabel)
      .then((data) => {
        const inst = { id: data.instance_id, type: "redis", label: data.label || data.instance_id, status: "connected" };
        setInstances((prev) => [...prev, inst]);
        setShowConnectForm(null);
        setRedisConnStr("");
        setRedisLabel("");
      })
      .catch((e) => setError(e.message))
      .finally(() => setConnecting(false));
  };

  const handleS3Connect = () => {
    setConnecting(true);
    setError(null);
    s3Connect({ endpointUrl: s3Endpoint, accessKey: s3AccessKey, secretKey: s3SecretKey, region: s3Region, label: s3Label })
      .then((data) => {
        const inst = { id: data.instance_id, type: "s3", label: data.label || data.instance_id, status: "connected" };
        setInstances((prev) => [...prev, inst]);
        setShowConnectForm(null);
        setS3Endpoint("");
        setS3AccessKey("");
        setS3SecretKey("");
        setS3Region("us-east-1");
        setS3Label("");
        return s3FetchBuckets(data.instance_id).then((d) => {
          setS3BucketsCache((prev) => ({ ...prev, [data.instance_id]: d.buckets }));
        });
      })
      .catch((e) => setError(e.message))
      .finally(() => setConnecting(false));
  };

  const handleBqConnect = () => {
    setConnecting(true);
    setError(null);
    bigqueryConnect({ project: bqProject, dataset: bqDataset, credentialsPath: bqCredentials, label: bqLabel })
      .then((data) => {
        const inst = { id: data.instance_id, type: "bigquery", label: data.label || data.instance_id, status: "connected" };
        setInstances((prev) => [...prev, inst]);
        setShowConnectForm(null);
        setBqProject("");
        setBqDataset("");
        setBqCredentials("");
        setBqLabel("");
        return pgFetchTables(data.instance_id).then((d) => {
          setPgTablesCache((prev) => ({ ...prev, [data.instance_id]: d.tables }));
        });
      })
      .catch((e) => setError(e.message))
      .finally(() => setConnecting(false));
  };

  // Source picker logic
  const handlePickType = (type) => {
    const connected = instancesOfType(type);
    if (connected.length === 0) {
      setShowSourcePicker(false);
      setShowConnectForm(type);
      return;
    }
    if (connected.length === 1) {
      handlePickInstance(connected[0], type);
      return;
    }
    setPickerType(type);
    setPickerStage("instance");
  };

  const handlePickInstance = (inst, type) => {
    const t = type || pickerType;
    if (t === "redis") {
      addRedisPanel(inst.id, inst.label);
      return;
    }
    setPickerInstance(inst.id);
    setPickerType(t);
    setPickerStage("target");
    if ((t === "postgres" || t === "bigquery") && !pgTablesCache[inst.id]) {
      pgFetchTables(inst.id)
        .then((d) => setPgTablesCache((prev) => ({ ...prev, [inst.id]: d.tables })))
        .catch((e) => setError(e.message));
    }
    if (t === "s3" && !s3BucketsCache[inst.id]) {
      s3FetchBuckets(inst.id)
        .then((d) => setS3BucketsCache((prev) => ({ ...prev, [inst.id]: d.buckets })))
        .catch((e) => setError(e.message));
    }
  };

  const resetPicker = () => {
    setShowSourcePicker(false);
    setPickerStage("type");
    setPickerType(null);
    setPickerInstance(null);
  };

  // Close picker on outside click
  useEffect(() => {
    const handleClick = (e) => {
      if (pickerRef.current && !pickerRef.current.contains(e.target)) {
        resetPicker();
      }
      if (refreshMenuRef.current && !refreshMenuRef.current.contains(e.target)) {
        setShowRefreshMenu(false);
      }
      if (configMenuRef.current && !configMenuRef.current.contains(e.target)) {
        setShowConfigMenu(false);
      }
    };
    document.addEventListener("mousedown", handleClick);
    return () => document.removeEventListener("mousedown", handleClick);
  }, []);

  // Trigger a refresh of all panels in the active dashboard
  const triggerRefresh = useCallback(() => {
    setDashboards((prev) =>
      prev.map((d) =>
        d.id === activeDashboardId
          ? { ...d, panels: d.panels.map((p) => ({ ...p, results: null })) }
          : d
      )
    );
    setRefreshKey((k) => k + 1);
  }, [activeDashboardId]);

  // Auto-refresh interval management
  useEffect(() => {
    if (autoRefreshTimerRef.current) {
      clearInterval(autoRefreshTimerRef.current);
      autoRefreshTimerRef.current = null;
    }
    if (autoRefreshInterval && activePanels.length > 0) {
      autoRefreshTimerRef.current = setInterval(() => {
        triggerRefresh();
      }, autoRefreshInterval * 60 * 1000);
    }
    return () => {
      if (autoRefreshTimerRef.current) {
        clearInterval(autoRefreshTimerRef.current);
      }
    };
  }, [autoRefreshInterval, activePanels.length, triggerRefresh]);

  // Save dashboards to localStorage on changes
  useEffect(() => {
    try {
      localStorage.setItem(STORAGE_KEY, JSON.stringify({
        dashboards: stripTransient(dashboards),
        activeDashboardId,
        autoRefreshInterval,
      }));
    } catch {}
  }, [dashboards, activeDashboardId, autoRefreshInterval]);

  // GridStack reports position changes for active dashboard panels
  const handleLayoutChange = useCallback((positions) => {
    setDashboards((prev) =>
      prev.map((d) =>
        d.id === activeDashboardId
          ? {
              ...d,
              panels: d.panels.map((p) =>
                positions[String(p.id)]
                  ? { ...p, gridPos: positions[String(p.id)] }
                  : p
              ),
            }
          : d
      )
    );
  }, [activeDashboardId]);

  const panelDisplayName = (panel) => {
    const hasMultiple = multipleInstances(panel.sourceType);
    const prefix = hasMultiple ? `${panel.instanceLabel} ` : "";
    return prefix + panel.displayName;
  };

  const inputStyle = {
    background: "var(--bg)",
    border: "1px solid var(--border)",
    borderRadius: 6,
    padding: "10px 12px",
    color: "var(--text)",
    fontFamily: "inherit",
    fontSize: 13,
    outline: "none",
    width: "100%",
  };

  return (
    <div style={{ display: "flex", flexDirection: "column", height: "100%", background: "var(--bg)" }}>
      {/* Header with dashboard tabs */}
      <div
        style={{
          display: "flex",
          alignItems: "stretch",
          borderBottom: "1px solid var(--border)",
          background: "var(--panel)",
          minHeight: 40,
        }}
      >
        {/* Logo */}
        <div
          style={{
            display: "flex",
            alignItems: "center",
            padding: "0 16px",
            borderRight: "1px solid var(--border)",
            flexShrink: 0,
          }}
        >
          <span style={{ fontWeight: 700, fontSize: 14, color: "var(--cyan)" }}>mdash</span>
          {appVersion && <span style={{ fontSize: 11, color: "var(--text-dim)", marginLeft: 6 }}>v{appVersion}</span>}
        </div>

        {/* Dashboard tabs */}
        <div style={{ display: "flex", alignItems: "stretch", overflowX: "auto" }}>
          {dashboards.map((dash) => (
            <div
              key={dash.id}
              onClick={() => setActiveDashboardId(dash.id)}
              style={{
                display: "flex",
                alignItems: "center",
                gap: 6,
                padding: "0 14px",
                cursor: "pointer",
                fontSize: 12,
                borderRight: "1px solid var(--border)",
                background: dash.id === activeDashboardId ? "var(--bg)" : "transparent",
                borderBottom: dash.id === activeDashboardId ? "2px solid var(--cyan)" : "2px solid transparent",
                color: dash.id === activeDashboardId ? "var(--text)" : "var(--text-dim)",
                transition: "all 0.1s",
                whiteSpace: "nowrap",
                flexShrink: 0,
              }}
            >
              {editingDashboardId === dash.id ? (
                <input
                  autoFocus
                  defaultValue={dash.name}
                  onClick={(e) => e.stopPropagation()}
                  onFocus={(e) => e.target.select()}
                  onBlur={(e) => {
                    renameDashboard(dash.id, e.target.value);
                    setEditingDashboardId(null);
                  }}
                  onKeyDown={(e) => {
                    if (e.key === "Enter") {
                      renameDashboard(dash.id, e.target.value);
                      setEditingDashboardId(null);
                    } else if (e.key === "Escape") {
                      setEditingDashboardId(null);
                    }
                  }}
                  style={{
                    background: "transparent",
                    border: "1px solid var(--cyan)",
                    borderRadius: 3,
                    color: "inherit",
                    fontFamily: "inherit",
                    fontSize: "inherit",
                    padding: "1px 4px",
                    outline: "none",
                    width: Math.max(60, dash.name.length * 7.5),
                  }}
                />
              ) : (
                <span onDoubleClick={(e) => { e.stopPropagation(); setEditingDashboardId(dash.id); }}>
                  {dash.name}
                </span>
              )}
              {dash.panels.length > 0 && (
                <span style={{ fontSize: 9, color: "var(--text-dim)", background: "rgba(255,255,255,0.06)", padding: "1px 4px", borderRadius: 3 }}>
                  {dash.panels.length}
                </span>
              )}
              {dashboards.length > 1 && (
                <button
                  onClick={(e) => closeDashboard(dash.id, e)}
                  style={{
                    background: "none",
                    border: "none",
                    color: "var(--text-dim)",
                    cursor: "pointer",
                    fontFamily: "inherit",
                    fontSize: 13,
                    padding: "0 2px",
                    lineHeight: 1,
                    borderRadius: 3,
                  }}
                  onMouseEnter={(e) => { e.target.style.color = "var(--red)"; e.target.style.background = "var(--red-bg)"; }}
                  onMouseLeave={(e) => { e.target.style.color = "var(--text-dim)"; e.target.style.background = "none"; }}
                >
                  x
                </button>
              )}
            </div>
          ))}
        </div>

        {/* New dashboard button */}
        <button
          onClick={addDashboard}
          style={{
            background: "none",
            border: "none",
            borderLeft: "1px solid var(--border)",
            color: "var(--text-dim)",
            cursor: "pointer",
            fontFamily: "inherit",
            fontSize: 16,
            padding: "0 12px",
            flexShrink: 0,
            lineHeight: 1,
          }}
          onMouseEnter={(e) => { e.target.style.color = "var(--text)"; }}
          onMouseLeave={(e) => { e.target.style.color = "var(--text-dim)"; }}
          title="New dashboard"
        >
          +
        </button>

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

        {/* Freeze / Edit toggle */}
        {activeDashboard && activePanels.length > 0 && (
          <div style={{ display: "flex", alignItems: "center", paddingLeft: 10, flexShrink: 0 }}>
            <button
              onClick={() => toggleDashboardLock(activeDashboardId)}
              title={activeDashboard.locked ? "Unlock layout (allow moving panels)" : "Lock layout (prevent moving panels)"}
              style={{
                background: activeDashboard.locked ? "rgba(250, 204, 21, 0.08)" : "none",
                border: "1px solid",
                borderColor: activeDashboard.locked ? "var(--yellow)" : "var(--border)",
                borderRadius: 4,
                color: activeDashboard.locked ? "var(--yellow)" : "var(--text-dim)",
                cursor: "pointer",
                fontFamily: "inherit",
                fontSize: 11,
                padding: "3px 10px",
                lineHeight: 1,
                display: "flex",
                alignItems: "center",
                gap: 5,
                transition: "all 0.15s",
              }}
              onMouseEnter={(e) => {
                if (!activeDashboard.locked) {
                  e.currentTarget.style.color = "var(--text)";
                  e.currentTarget.style.borderColor = "var(--text-dim)";
                }
              }}
              onMouseLeave={(e) => {
                if (!activeDashboard.locked) {
                  e.currentTarget.style.color = "var(--text-dim)";
                  e.currentTarget.style.borderColor = "var(--border)";
                }
              }}
            >
              <span style={{ fontSize: 12 }}>{activeDashboard.locked ? "🔒" : "🔓"}</span>
              {activeDashboard.locked ? "Locked" : "Unlocked"}
            </button>
          </div>
        )}

        {/* Import/Export config menu */}
        <div style={{ position: "relative", display: "flex", alignItems: "center", flexShrink: 0 }} ref={configMenuRef}>
          <input
            ref={importInputRef}
            type="file"
            accept=".json"
            style={{ display: "none" }}
            onChange={handleImportFile}
          />
          <button
            onClick={() => setShowConfigMenu((v) => !v)}
            title="Import / Export dashboards"
            style={{
              background: "none",
              border: "1px solid var(--border)",
              borderRadius: 4,
              color: "var(--text-dim)",
              cursor: "pointer",
              fontFamily: "inherit",
              fontSize: 11,
              padding: "3px 10px",
              lineHeight: 1,
              display: "flex",
              alignItems: "center",
              gap: 4,
            }}
            onMouseEnter={(e) => { e.currentTarget.style.color = "var(--text)"; e.currentTarget.style.borderColor = "var(--text-dim)"; }}
            onMouseLeave={(e) => { e.currentTarget.style.color = "var(--text-dim)"; e.currentTarget.style.borderColor = "var(--border)"; }}
          >
            Config
          </button>
          {showConfigMenu && (
            <div
              style={{
                position: "absolute",
                top: "100%",
                right: 0,
                marginTop: 4,
                background: "var(--panel)",
                border: "1px solid var(--border)",
                borderRadius: 6,
                minWidth: 200,
                zIndex: 200,
                boxShadow: "0 8px 24px rgba(0,0,0,0.4)",
              }}
            >
              {activeDashboard && (
                <ConfigMenuItem
                  label={`Export "${activeDashboard.name}"`}
                  onClick={exportCurrentDashboard}
                />
              )}
              {dashboards.length > 0 && (
                <ConfigMenuItem
                  label="Export all dashboards"
                  onClick={exportAllDashboards}
                />
              )}
              <div style={{ borderTop: "1px solid var(--border)", margin: "2px 0" }} />
              <ConfigMenuItem
                label="Import from file..."
                onClick={() => importInputRef.current?.click()}
              />
            </div>
          )}
        </div>

        {/* Add panel button + source picker */}
        {activeDashboard && (
          <div style={{ position: "relative", display: "flex", alignItems: "center", flexShrink: 0 }} ref={pickerRef}>
            <button
              onClick={() => { setShowSourcePicker(!showSourcePicker); setPickerStage("type"); setPickerType(null); setPickerInstance(null); }}
              style={{
                background: "none",
                border: "1px solid var(--border)",
                borderRadius: 4,
                color: "var(--text-dim)",
                cursor: "pointer",
                fontFamily: "inherit",
                fontSize: 11,
                padding: "3px 10px",
                lineHeight: 1,
                display: "flex",
                alignItems: "center",
                gap: 4,
              }}
              onMouseEnter={(e) => { e.currentTarget.style.color = "var(--text)"; e.currentTarget.style.borderColor = "var(--text-dim)"; }}
              onMouseLeave={(e) => { e.currentTarget.style.color = "var(--text-dim)"; e.currentTarget.style.borderColor = "var(--border)"; }}
            >
              + Panel
            </button>

            {showSourcePicker && (
              <SourcePickerDropdown
                pickerStage={pickerStage}
                pickerType={pickerType}
                pickerInstance={pickerInstance}
                instancesOfType={instancesOfType}
                handlePickType={handlePickType}
                handlePickInstance={handlePickInstance}
                setShowSourcePicker={setShowSourcePicker}
                setShowConnectForm={setShowConnectForm}
                setPickerStage={setPickerStage}
                setPickerType={setPickerType}
                setPickerInstance={setPickerInstance}
                pgTablesCache={pgTablesCache}
                s3BucketsCache={s3BucketsCache}
                instances={instances}
                activePanels={activePanels}
                addPgPanel={addPgPanel}
                addS3Panel={addS3Panel}
              />
            )}
          </div>
        )}

        {/* Refresh All */}
        <div style={{ display: "flex", alignItems: "center", gap: 10, paddingRight: 16, paddingLeft: 10, flexShrink: 0 }} ref={refreshMenuRef}>
          {activePanels.some((p) => p.loading) && (
            <span style={{ color: "var(--yellow)", fontSize: 11 }}>loading...</span>
          )}
          {activePanels.length > 0 && (
            <div style={{ position: "relative", display: "flex", alignItems: "center", gap: 0 }}>
              {/* Refresh now button */}
              <button
                onClick={triggerRefresh}
                title="Refresh all panels"
                style={{
                  background: autoRefreshInterval ? "rgba(34, 211, 238, 0.08)" : "none",
                  border: "1px solid",
                  borderColor: autoRefreshInterval ? "var(--cyan)" : "var(--border)",
                  borderRadius: "4px 0 0 4px",
                  padding: "3px 8px",
                  color: autoRefreshInterval ? "var(--cyan)" : "var(--text-dim)",
                  cursor: "pointer",
                  fontFamily: "inherit",
                  fontSize: 11,
                  lineHeight: 1,
                  display: "flex",
                  alignItems: "center",
                  gap: 4,
                }}
                onMouseEnter={(e) => {
                  if (!autoRefreshInterval) {
                    e.currentTarget.style.color = "var(--text)";
                    e.currentTarget.style.borderColor = "var(--text-dim)";
                  }
                }}
                onMouseLeave={(e) => {
                  if (!autoRefreshInterval) {
                    e.currentTarget.style.color = "var(--text-dim)";
                    e.currentTarget.style.borderColor = "var(--border)";
                  }
                }}
              >
                <span style={{ fontSize: 13, ...(autoRefreshInterval ? { animation: "spin 2s linear infinite" } : {}) }}>↻</span>
                {autoRefreshInterval ? `${autoRefreshInterval}m` : "all"}
              </button>
              {/* Dropdown arrow */}
              <button
                onClick={() => setShowRefreshMenu((v) => !v)}
                title="Auto-refresh options"
                style={{
                  background: autoRefreshInterval ? "rgba(34, 211, 238, 0.08)" : "none",
                  border: "1px solid",
                  borderColor: autoRefreshInterval ? "var(--cyan)" : "var(--border)",
                  borderLeft: "none",
                  borderRadius: "0 4px 4px 0",
                  padding: "3px 5px",
                  color: autoRefreshInterval ? "var(--cyan)" : "var(--text-dim)",
                  cursor: "pointer",
                  fontFamily: "inherit",
                  fontSize: 8,
                  lineHeight: 1,
                  display: "flex",
                  alignItems: "center",
                }}
                onMouseEnter={(e) => {
                  if (!autoRefreshInterval) {
                    e.currentTarget.style.color = "var(--text)";
                    e.currentTarget.style.borderColor = "var(--text-dim)";
                  }
                }}
                onMouseLeave={(e) => {
                  if (!autoRefreshInterval) {
                    e.currentTarget.style.color = "var(--text-dim)";
                    e.currentTarget.style.borderColor = "var(--border)";
                  }
                }}
              >
                ▼
              </button>
              {/* Dropdown menu */}
              {showRefreshMenu && (
                <div
                  style={{
                    position: "absolute",
                    top: "100%",
                    right: 0,
                    marginTop: 4,
                    background: "var(--panel)",
                    border: "1px solid var(--border)",
                    borderRadius: 6,
                    minWidth: 180,
                    zIndex: 200,
                    boxShadow: "0 8px 24px rgba(0,0,0,0.4)",
                  }}
                >
                  <RefreshMenuItem
                    label="Refresh now"
                    hint="↻"
                    onClick={() => { triggerRefresh(); setShowRefreshMenu(false); }}
                  />
                  <div style={{ borderTop: "1px solid var(--border)", margin: "2px 0" }} />
                  <RefreshMenuItem
                    label="Every 1 min"
                    active={autoRefreshInterval === 1}
                    onClick={() => { setAutoRefreshInterval(autoRefreshInterval === 1 ? null : 1); setShowRefreshMenu(false); }}
                  />
                  <RefreshMenuItem
                    label="Every 5 min"
                    active={autoRefreshInterval === 5}
                    onClick={() => { setAutoRefreshInterval(autoRefreshInterval === 5 ? null : 5); setShowRefreshMenu(false); }}
                  />
                  <div style={{ borderTop: "1px solid var(--border)", margin: "2px 0" }} />
                  <div style={{ padding: "6px 12px", display: "flex", alignItems: "center", gap: 6 }}>
                    <span style={{ fontSize: 11, color: "var(--text-dim)", whiteSpace: "nowrap" }}>Every</span>
                    <input
                      type="number"
                      min="1"
                      value={customMinutes}
                      onChange={(e) => setCustomMinutes(e.target.value)}
                      onKeyDown={(e) => {
                        if (e.key === "Enter") {
                          const val = parseInt(customMinutes, 10);
                          if (val > 0) {
                            setAutoRefreshInterval(val);
                            setShowRefreshMenu(false);
                            setCustomMinutes("");
                          }
                        }
                      }}
                      placeholder="N"
                      style={{
                        background: "var(--bg)",
                        border: "1px solid var(--border)",
                        borderRadius: 3,
                        padding: "3px 6px",
                        color: "var(--text)",
                        fontFamily: "inherit",
                        fontSize: 11,
                        outline: "none",
                        width: 44,
                        textAlign: "center",
                      }}
                      autoFocus
                    />
                    <span style={{ fontSize: 11, color: "var(--text-dim)" }}>min</span>
                    <button
                      onClick={() => {
                        const val = parseInt(customMinutes, 10);
                        if (val > 0) {
                          setAutoRefreshInterval(val);
                          setShowRefreshMenu(false);
                          setCustomMinutes("");
                        }
                      }}
                      style={{
                        background: "none",
                        border: "1px solid var(--border)",
                        borderRadius: 3,
                        padding: "2px 8px",
                        color: "var(--text-dim)",
                        cursor: "pointer",
                        fontFamily: "inherit",
                        fontSize: 10,
                      }}
                    >
                      Set
                    </button>
                  </div>
                  {autoRefreshInterval && (
                    <>
                      <div style={{ borderTop: "1px solid var(--border)", margin: "2px 0" }} />
                      <RefreshMenuItem
                        label="Turn off auto-refresh"
                        hint="✕"
                        onClick={() => { setAutoRefreshInterval(null); setShowRefreshMenu(false); setCustomMinutes(""); }}
                      />
                    </>
                  )}
                </div>
              )}
            </div>
          )}
        </div>
      </div>

      {/* Error banner */}
      {error && (
        <div
          style={{
            padding: "8px 20px",
            background: "var(--red-bg)",
            borderBottom: "1px solid var(--red)",
            color: "var(--red)",
            fontSize: 12,
            display: "flex",
            justifyContent: "space-between",
            alignItems: "center",
          }}
        >
          <span>{error}</span>
          <button
            onClick={() => setError(null)}
            style={{ background: "none", border: "none", color: "var(--red)", cursor: "pointer", fontFamily: "inherit", fontSize: 14 }}
          >
            x
          </button>
        </div>
      )}

      {/* Main content */}
      <div style={{ flex: 1, display: "flex", flexDirection: "column", overflow: "hidden" }}>
        {/* Connection form overlay */}
        {showConnectForm && (
          <div style={{ flex: 1, display: "flex", alignItems: "center", justifyContent: "center" }}>
            <div
              style={{
                background: "var(--panel)",
                border: "1px solid var(--border)",
                borderRadius: 8,
                padding: "32px 40px",
                display: "flex",
                flexDirection: "column",
                gap: 12,
                minWidth: 420,
              }}
            >
              {showConnectForm === "postgres" && (
                <>
                  <span style={{ color: "var(--text)", fontSize: 14, fontWeight: 600 }}>Connect to PostgreSQL</span>
                  <input type="text" value={pgConnStr} onChange={(e) => setPgConnStr(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter" && pgConnStr.trim()) handlePgConnect(); }} placeholder="postgresql://user:password@host:5432/dbname" style={inputStyle} autoFocus />
                  <input type="text" value={pgLabel} onChange={(e) => setPgLabel(e.target.value)} placeholder="Label (optional, e.g. prod, staging)" style={inputStyle} />
                  <div style={{ display: "flex", gap: 8 }}>
                    <button onClick={handlePgConnect} disabled={connecting || !pgConnStr.trim()} style={{ background: "var(--purple)", border: "none", borderRadius: 6, padding: "10px 16px", color: "#fff", fontFamily: "inherit", fontSize: 13, fontWeight: 600, cursor: connecting ? "wait" : "pointer", opacity: connecting || !pgConnStr.trim() ? 0.5 : 1, flex: 1 }}>{connecting ? "Connecting..." : "Connect"}</button>
                    <button onClick={() => setShowConnectForm(null)} style={{ background: "none", border: "1px solid var(--border)", borderRadius: 6, padding: "10px 16px", color: "var(--text-dim)", fontFamily: "inherit", fontSize: 13, cursor: "pointer" }}>Cancel</button>
                  </div>
                </>
              )}
              {showConnectForm === "redis" && (
                <>
                  <span style={{ color: "var(--text)", fontSize: 14, fontWeight: 600 }}>Connect to Redis</span>
                  <input type="text" value={redisConnStr} onChange={(e) => setRedisConnStr(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter" && redisConnStr.trim()) handleRedisConnect(); }} placeholder="redis://localhost:6379" style={inputStyle} autoFocus />
                  <input type="text" value={redisLabel} onChange={(e) => setRedisLabel(e.target.value)} placeholder="Label (optional, e.g. cache, sessions)" style={inputStyle} />
                  <div style={{ display: "flex", gap: 8 }}>
                    <button onClick={handleRedisConnect} disabled={connecting || !redisConnStr.trim()} style={{ background: "var(--red)", border: "none", borderRadius: 6, padding: "10px 16px", color: "#fff", fontFamily: "inherit", fontSize: 13, fontWeight: 600, cursor: connecting ? "wait" : "pointer", opacity: connecting || !redisConnStr.trim() ? 0.5 : 1, flex: 1 }}>{connecting ? "Connecting..." : "Connect"}</button>
                    <button onClick={() => setShowConnectForm(null)} style={{ background: "none", border: "1px solid var(--border)", borderRadius: 6, padding: "10px 16px", color: "var(--text-dim)", fontFamily: "inherit", fontSize: 13, cursor: "pointer" }}>Cancel</button>
                  </div>
                </>
              )}
              {showConnectForm === "bigquery" && (
                <>
                  <span style={{ color: "var(--text)", fontSize: 14, fontWeight: 600 }}>Connect to BigQuery</span>
                  <input type="text" value={bqProject} onChange={(e) => setBqProject(e.target.value)} placeholder="GCP Project ID (e.g. my-project)" style={inputStyle} autoFocus />
                  <input type="text" value={bqDataset} onChange={(e) => setBqDataset(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter" && bqProject.trim() && bqDataset.trim()) handleBqConnect(); }} placeholder="Dataset (e.g. my_dataset)" style={inputStyle} />
                  <input type="text" value={bqCredentials} onChange={(e) => setBqCredentials(e.target.value)} placeholder="Service account JSON path (optional)" style={inputStyle} />
                  <input type="text" value={bqLabel} onChange={(e) => setBqLabel(e.target.value)} placeholder="Label (optional)" style={inputStyle} />
                  <div style={{ display: "flex", gap: 8 }}>
                    <button onClick={handleBqConnect} disabled={connecting || !bqProject.trim() || !bqDataset.trim()} style={{ background: "var(--green)", border: "none", borderRadius: 6, padding: "10px 16px", color: "#fff", fontFamily: "inherit", fontSize: 13, fontWeight: 600, cursor: connecting ? "wait" : "pointer", opacity: connecting || !bqProject.trim() || !bqDataset.trim() ? 0.5 : 1, flex: 1 }}>{connecting ? "Connecting..." : "Connect"}</button>
                    <button onClick={() => setShowConnectForm(null)} style={{ background: "none", border: "1px solid var(--border)", borderRadius: 6, padding: "10px 16px", color: "var(--text-dim)", fontFamily: "inherit", fontSize: 13, cursor: "pointer" }}>Cancel</button>
                  </div>
                </>
              )}
              {showConnectForm === "s3" && (
                <>
                  <span style={{ color: "var(--text)", fontSize: 14, fontWeight: 600 }}>Connect to S3</span>
                  <input type="text" value={s3Endpoint} onChange={(e) => setS3Endpoint(e.target.value)} placeholder="Endpoint URL (e.g. http://localhost:9000)" style={inputStyle} autoFocus />
                  <input type="text" value={s3AccessKey} onChange={(e) => setS3AccessKey(e.target.value)} placeholder="Access Key ID" style={inputStyle} />
                  <input type="password" value={s3SecretKey} onChange={(e) => setS3SecretKey(e.target.value)} placeholder="Secret Access Key" style={inputStyle} />
                  <input type="text" value={s3Region} onChange={(e) => setS3Region(e.target.value)} placeholder="Region (default: us-east-1)" style={inputStyle} />
                  <input type="text" value={s3Label} onChange={(e) => setS3Label(e.target.value)} placeholder="Label (optional, e.g. prod, logs)" style={inputStyle} />
                  <div style={{ display: "flex", gap: 8 }}>
                    <button onClick={handleS3Connect} disabled={connecting} style={{ background: "var(--orange)", border: "none", borderRadius: 6, padding: "10px 16px", color: "#fff", fontFamily: "inherit", fontSize: 13, fontWeight: 600, cursor: connecting ? "wait" : "pointer", opacity: connecting ? 0.5 : 1, flex: 1 }}>{connecting ? "Connecting..." : "Connect"}</button>
                    <button onClick={() => setShowConnectForm(null)} style={{ background: "none", border: "1px solid var(--border)", borderRadius: 6, padding: "10px 16px", color: "var(--text-dim)", fontFamily: "inherit", fontSize: 13, cursor: "pointer" }}>Cancel</button>
                  </div>
                </>
              )}
            </div>
          </div>
        )}

        {/* Grid layout with panels for active dashboard */}
        {!showConnectForm && activeDashboard && activePanels.length > 0 && (
          <GridLayout
            ref={gridLayoutRef}
            key={activeDashboardId}
            panels={activePanels}
            locked={activeDashboard.locked}
            focusPanelId={focusPanelId}
            focusCounter={focusCounter}
            SOURCE_CONFIG={SOURCE_CONFIG}
            panelDisplayName={panelDisplayName}
            onPanelClose={closePanel}
            onToggleFilters={togglePanelFilters}
            onLayoutChange={handleLayoutChange}
            onViewModeChange={handleViewModeChange}
            onCopyPanel={copyPanel}
            onChartTypeChange={handleChartTypeChange}
            onUpdatePanelLinks={updatePanelLinks}
            renderPanel={(panel) => {
              const dashLocked = activeDashboard.locked;
              if (panel.sourceType === "postgres" || panel.sourceType === "bigquery") {
                return (
                  <PostgresTabContent
                    key={panel.id}
                    tab={panel}
                    updateTab={(patch) => updatePanel(panel.id, patch)}
                    allPanels={activePanels}
                    pageSize={pageSize}
                    setError={setError}
                    refreshKey={refreshKey}
                    locked={dashLocked}
                  />
                );
              }
              if (panel.sourceType === "redis") {
                return (
                  <RedisTabContent
                    key={panel.id}
                    tab={panel}
                    updateTab={(patch) => updatePanel(panel.id, patch)}
                    pageSize={pageSize}
                    setError={setError}
                    refreshKey={refreshKey}
                    locked={dashLocked}
                  />
                );
              }
              if (panel.sourceType === "s3") {
                return (
                  <S3TabContent
                    key={panel.id}
                    tab={panel}
                    updateTab={(patch) => updatePanel(panel.id, patch)}
                    pageSize={pageSize}
                    setError={setError}
                    refreshKey={refreshKey}
                    locked={dashLocked}
                  />
                );
              }
              return null;
            }}
          />
        )}

        {/* Empty state: no dashboards */}
        {!showConnectForm && !activeDashboard && (
          <div style={{ flex: 1, display: "flex", alignItems: "center", justifyContent: "center", color: "var(--text-dim)", flexDirection: "column", gap: 8 }}>
            <span>Click + to create a dashboard</span>
          </div>
        )}

        {/* Empty state: dashboard has no panels */}
        {!showConnectForm && activeDashboard && activePanels.length === 0 && (
          <div style={{ flex: 1, display: "flex", alignItems: "center", justifyContent: "center", color: "var(--text-dim)", flexDirection: "column", gap: 12 }}>
            <span style={{ fontSize: 14 }}>{activeDashboard.name}</span>
            <span>Click <strong>+ Panel</strong> to add a data source</span>
            {!hasAnyInstances && (
              <span style={{ fontSize: 11 }}>Connect to PostgreSQL, BigQuery, Redis, or S3 to get started</span>
            )}
          </div>
        )}
      </div>
    </div>
  );
}

// Source picker dropdown — extracted for readability
function SourcePickerDropdown({
  pickerStage, pickerType, pickerInstance,
  instancesOfType, handlePickType, handlePickInstance,
  setShowSourcePicker, setShowConnectForm,
  setPickerStage, setPickerType, setPickerInstance,
  pgTablesCache, s3BucketsCache, instances, activePanels,
  addPgPanel, addS3Panel,
}) {
  return (
    <div
      style={{
        position: "absolute",
        top: "100%",
        right: 0,
        marginTop: 4,
        background: "var(--panel)",
        border: "1px solid var(--border)",
        borderRadius: 6,
        minWidth: 240,
        maxHeight: 400,
        overflowY: "auto",
        zIndex: 200,
        boxShadow: "0 8px 24px rgba(0,0,0,0.4)",
      }}
    >
      {pickerStage === "type" && (
        <>
          <PickerTypeRow typeKey="postgres" label="PostgreSQL" badge="PG" connected={instancesOfType("postgres")} onClick={() => handlePickType("postgres")} onConnect={() => { setShowSourcePicker(false); setShowConnectForm("postgres"); }} />
          <PickerTypeRow typeKey="bigquery" label="BigQuery" badge="BQ" connected={instancesOfType("bigquery")} onClick={() => handlePickType("bigquery")} onConnect={() => { setShowSourcePicker(false); setShowConnectForm("bigquery"); }} />
          <PickerTypeRow typeKey="redis" label="Redis" badge="RD" connected={instancesOfType("redis")} onClick={() => handlePickType("redis")} onConnect={() => { setShowSourcePicker(false); setShowConnectForm("redis"); }} />
          <PickerTypeRow typeKey="s3" label="S3" badge="S3" connected={instancesOfType("s3")} onClick={() => handlePickType("s3")} onConnect={() => { setShowSourcePicker(false); setShowConnectForm("s3"); }} />
        </>
      )}

      {pickerStage === "instance" && pickerType && (
        <>
          <div
            onClick={() => { setPickerStage("type"); setPickerType(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"; }}
          >
            ◀ Back
          </div>
          {instancesOfType(pickerType).map((inst) => (
            <div
              key={inst.id}
              onClick={() => handlePickInstance(inst)}
              style={{ padding: "8px 12px", cursor: "pointer", fontSize: 12, display: "flex", alignItems: "center", gap: 8, color: "var(--text)" }}
              onMouseEnter={(e) => { e.currentTarget.style.background = "rgba(255,255,255,0.05)"; }}
              onMouseLeave={(e) => { e.currentTarget.style.background = "transparent"; }}
            >
              <span style={{ color: SOURCE_CONFIG[pickerType].color, fontWeight: 600, fontSize: 10 }}>{SOURCE_CONFIG[pickerType].label}</span>
              <span>{inst.label}</span>
              {pickerType !== "redis" && <span style={{ color: "var(--text-dim)", fontSize: 10, marginLeft: "auto" }}>▶</span>}
            </div>
          ))}
          <div
            onClick={() => { setShowSourcePicker(false); setShowConnectForm(pickerType); }}
            style={{ padding: "8px 12px", cursor: "pointer", fontSize: 12, color: "var(--text-dim)", borderTop: "1px solid var(--border)" }}
            onMouseEnter={(e) => { e.currentTarget.style.background = "rgba(255,255,255,0.05)"; }}
            onMouseLeave={(e) => { e.currentTarget.style.background = "transparent"; }}
          >
            + Connect new...
          </div>
        </>
      )}

      {pickerStage === "target" && pickerInstance && (
        <>
          <div
            onClick={() => {
              const connected = instancesOfType(pickerType);
              if (connected.length > 1) {
                setPickerStage("instance");
                setPickerInstance(null);
              } else {
                setPickerStage("type");
                setPickerType(null);
                setPickerInstance(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"; }}
          >
            ◀ Back
          </div>

          {(pickerType === "postgres" || pickerType === "bigquery") && (
            <>
              {(pgTablesCache[pickerInstance] || []).map((t) => {
                const inst = instances.find((i) => i.id === pickerInstance);
                const isOpen = activePanels.some((p) => (p.sourceType === "postgres" || p.sourceType === "bigquery") && p.instanceId === pickerInstance && p.schema === t.schema && p.name === t.name);
                return (
                  <div
                    key={`${t.schema}.${t.name}`}
                    onClick={() => addPgPanel(pickerInstance, inst?.label || pickerInstance, t.schema, t.name, pickerType)}
                    style={{ padding: "8px 12px", cursor: "pointer", fontSize: 12, display: "flex", justifyContent: "space-between", alignItems: "center", color: isOpen ? "var(--text-dim)" : "var(--text)" }}
                    onMouseEnter={(e) => { e.currentTarget.style.background = "rgba(255,255,255,0.05)"; }}
                    onMouseLeave={(e) => { e.currentTarget.style.background = "transparent"; }}
                  >
                    <span>{t.schema}.{t.name}</span>
                    {isOpen && <span style={{ fontSize: 10, color: "var(--text-dim)" }}>open</span>}
                  </div>
                );
              })}
              {!pgTablesCache[pickerInstance] && <div style={{ padding: "12px", fontSize: 12, color: "var(--text-dim)" }}>Loading tables...</div>}
              {pgTablesCache[pickerInstance]?.length === 0 && <div style={{ padding: "12px", fontSize: 12, color: "var(--text-dim)" }}>No tables found</div>}
            </>
          )}

          {pickerType === "s3" && (
            <>
              {(s3BucketsCache[pickerInstance] || []).map((b) => {
                const inst = instances.find((i) => i.id === pickerInstance);
                const isOpen = activePanels.some((p) => p.sourceType === "s3" && p.instanceId === pickerInstance && p.bucket === b.name);
                return (
                  <div
                    key={b.name}
                    onClick={() => addS3Panel(pickerInstance, inst?.label || pickerInstance, b.name)}
                    style={{ padding: "8px 12px", cursor: "pointer", fontSize: 12, display: "flex", justifyContent: "space-between", alignItems: "center", color: isOpen ? "var(--text-dim)" : "var(--text)" }}
                    onMouseEnter={(e) => { e.currentTarget.style.background = "rgba(255,255,255,0.05)"; }}
                    onMouseLeave={(e) => { e.currentTarget.style.background = "transparent"; }}
                  >
                    <span>{b.name}</span>
                    {isOpen && <span style={{ fontSize: 10, color: "var(--text-dim)" }}>open</span>}
                  </div>
                );
              })}
              {!s3BucketsCache[pickerInstance] && <div style={{ padding: "12px", fontSize: 12, color: "var(--text-dim)" }}>Loading buckets...</div>}
              {s3BucketsCache[pickerInstance]?.length === 0 && <div style={{ padding: "12px", fontSize: 12, color: "var(--text-dim)" }}>No buckets found</div>}
            </>
          )}
        </>
      )}
    </div>
  );
}

function RefreshMenuItem({ label, hint, active, onClick }) {
  return (
    <div
      onClick={onClick}
      style={{
        padding: "7px 12px",
        cursor: "pointer",
        fontSize: 12,
        display: "flex",
        justifyContent: "space-between",
        alignItems: "center",
        color: active ? "var(--cyan)" : "var(--text)",
      }}
      onMouseEnter={(e) => { e.currentTarget.style.background = "rgba(255,255,255,0.05)"; }}
      onMouseLeave={(e) => { e.currentTarget.style.background = "transparent"; }}
    >
      <span>{label}</span>
      {hint && <span style={{ fontSize: 10, color: "var(--text-dim)" }}>{hint}</span>}
      {active && <span style={{ fontSize: 10, color: "var(--cyan)" }}>●</span>}
    </div>
  );
}

function ConfigMenuItem({ label, onClick }) {
  return (
    <div
      onClick={onClick}
      style={{
        padding: "7px 12px",
        cursor: "pointer",
        fontSize: 12,
        color: "var(--text)",
      }}
      onMouseEnter={(e) => { e.currentTarget.style.background = "rgba(255,255,255,0.05)"; }}
      onMouseLeave={(e) => { e.currentTarget.style.background = "transparent"; }}
    >
      {label}
    </div>
  );
}

function PickerTypeRow({ typeKey, label, badge, connected, onClick, onConnect }) {
  const cfg = SOURCE_CONFIG[typeKey];
  if (connected.length > 0) {
    return (
      <div
        onClick={onClick}
        style={{ padding: "8px 12px", cursor: "pointer", fontSize: 12, display: "flex", alignItems: "center", gap: 8 }}
        onMouseEnter={(e) => { e.currentTarget.style.background = "rgba(255,255,255,0.05)"; }}
        onMouseLeave={(e) => { e.currentTarget.style.background = "transparent"; }}
      >
        <span style={{ color: cfg.color, fontWeight: 600, fontSize: 10 }}>{badge}</span>
        <span style={{ color: "var(--text)" }}>
          {label}
          {connected.length > 1 && <span style={{ color: "var(--text-dim)", fontSize: 10, marginLeft: 4 }}>({connected.length})</span>}
        </span>
        <span style={{ color: "var(--text-dim)", fontSize: 10, marginLeft: "auto" }}>▶</span>
      </div>
    );
  }
  return (
    <div
      onClick={onConnect}
      style={{ padding: "8px 12px", cursor: "pointer", fontSize: 12, display: "flex", alignItems: "center", gap: 8 }}
      onMouseEnter={(e) => { e.currentTarget.style.background = "rgba(255,255,255,0.05)"; }}
      onMouseLeave={(e) => { e.currentTarget.style.background = "transparent"; }}
    >
      <span style={{ color: cfg.color, fontWeight: 600, fontSize: 10 }}>{badge}</span>
      <span style={{ color: "var(--text-dim)" }}>Connect {label}...</span>
    </div>
  );
}
