"use client";

import React, {
  useCallback,
  useEffect,
  useMemo,
  useState,
  createContext,
  useContext,
  type ReactNode,
} from "react";
import {
  DockviewReact,
  type DockviewApi,
  type DockviewReadyEvent,
  type IDockviewPanelProps,
  type IDockviewPanelHeaderProps,
  type IWatermarkPanelProps,
  themeAbyss,
} from "dockview";
import { Circle, Disc, Grid3X3 } from "lucide-react";

import type { Geometry, Sample } from "@/types";
import { useStore } from "@/store/useStore";
import { findLayoutByGeometry } from "@/lib/layouts";
import { ImageGrid } from "./ImageGrid";
import { ScatterPanel } from "./ScatterPanel";
import { ExplorerPanel } from "./ExplorerPanel";
import { PlaceholderPanel } from "./PlaceholderPanel";
import { HyperViewLogo } from "./icons";

const LAYOUT_STORAGE_KEY = "hyperview:dockview-layout:v4";

// Panel IDs
const PANEL = {
  EXPLORER: "explorer",
  GRID: "grid",
  SCATTER_EUCLIDEAN: "scatter-euclidean",
  SCATTER_POINCARE: "scatter-poincare",
  SCATTER_DEFAULT: "scatter-default",
  RIGHT_PLACEHOLDER: "right-placeholder",
  BOTTOM_PLACEHOLDER: "bottom-placeholder",
} as const;

const CENTER_PANEL_IDS = [
  PANEL.GRID,
  PANEL.SCATTER_EUCLIDEAN,
  PANEL.SCATTER_POINCARE,
  PANEL.SCATTER_DEFAULT,
] as const;

export const CENTER_PANEL_DEFS = [
  { id: PANEL.GRID, label: "Samples", icon: Grid3X3 },
  { id: PANEL.SCATTER_EUCLIDEAN, label: "Euclidean", icon: Circle },
  { id: PANEL.SCATTER_POINCARE, label: "Hyperbolic", icon: Disc },
] as const;

const NON_ANCHOR_PANEL_IDS = new Set<string>([
  PANEL.EXPLORER,
  PANEL.RIGHT_PLACEHOLDER,
  PANEL.BOTTOM_PLACEHOLDER,
]);

const DRAG_LOCKED_PANEL_IDS = new Set<string>([PANEL.EXPLORER]);

const DEFAULT_CONTAINER_WIDTH = 1200;
const DEFAULT_CONTAINER_HEIGHT = 800;
const MIN_SIDE_PANEL_WIDTH = 120;
const MIN_BOTTOM_PANEL_HEIGHT = 150;

const getContainerWidth = (api?: DockviewApi | null) =>
  api?.width ??
  (typeof window === "undefined" ? DEFAULT_CONTAINER_WIDTH : window.innerWidth);
const getContainerHeight = (api?: DockviewApi | null) =>
  api?.height ??
  (typeof window === "undefined" ? DEFAULT_CONTAINER_HEIGHT : window.innerHeight);

const getDefaultLeftPanelWidth = (screenWidth: number) =>
  Math.round(Math.min(0.35 * screenWidth, 200));
const getDefaultRightPanelWidth = (screenWidth: number) =>
  Math.round(Math.min(0.45 * screenWidth, 300));
const getDefaultBottomPanelHeight = (containerHeight: number) =>
  Math.round(
    Math.min(Math.max(0.25 * containerHeight, MIN_BOTTOM_PANEL_HEIGHT), 250)
  );
const getBottomPanelMaxHeight = (containerHeight: number) =>
  Math.round(
    Math.max(containerHeight - MIN_BOTTOM_PANEL_HEIGHT, MIN_BOTTOM_PANEL_HEIGHT)
  );

function getCenterAnchorPanel(api: DockviewApi) {
  for (const id of CENTER_PANEL_IDS) {
    const panel = api.getPanel(id);
    if (panel) return panel;
  }

  const fallback = api.panels.find((panel) => !NON_ANCHOR_PANEL_IDS.has(panel.id));
  return fallback ?? api.activePanel;
}

function getZonePosition(zone: "left" | "right" | "bottom") {
  return { direction: zone === "bottom" ? "below" : zone };
}

function getCenterTabPosition(api: DockviewApi) {
  const anchor = getCenterAnchorPanel(api);
  if (!anchor) return undefined;
  return { referencePanel: anchor, direction: "within" as const };
}

// -----------------------------------------------------------------------------
// Context for sharing dockview API across components
// -----------------------------------------------------------------------------
interface DockviewContextValue {
  api: DockviewApi | null;
  setApi: (api: DockviewApi) => void;
  samples: Sample[];
  onLoadMore: () => void;
  hasMore: boolean;
}

const DockviewContext = createContext<DockviewContextValue | null>(null);

function useDockviewContext() {
  const ctx = useContext(DockviewContext);
  if (!ctx) throw new Error("useDockviewContext must be used within DockviewProvider");
  return ctx;
}

// Public hook for components like Header
export function useDockviewApi() {
  const ctx = useContext(DockviewContext);
  const datasetInfo = useStore((state) => state.datasetInfo);
  const {
    leftPanelOpen,
    rightPanelOpen,
    bottomPanelOpen,
    setLeftPanelOpen,
    setRightPanelOpen,
    setBottomPanelOpen,
  } = useStore();

  const addPanel = useCallback(
    (panelId: string) => {
      if (!ctx?.api) return;

      const api = ctx.api;
      const position = getCenterTabPosition(api);
      const baseOptions = position ? { position } : {};

      const layouts = datasetInfo?.layouts ?? [];
      const euclideanLayout = findLayoutByGeometry(layouts, "euclidean");
      const poincareLayout = findLayoutByGeometry(layouts, "poincare");

      // Don't add if already exists - just focus it
      if (api.getPanel(panelId)) {
        api.getPanel(panelId)?.focus();
        return;
      }

      switch (panelId) {
        case PANEL.GRID:
          api.addPanel({
            id: PANEL.GRID,
            component: "grid",
            title: "Samples",
            tabComponent: "samplesTab",
            renderer: "always",
            ...baseOptions,
          });
          break;

        case PANEL.SCATTER_EUCLIDEAN:
          api.addPanel({
            id: PANEL.SCATTER_EUCLIDEAN,
            component: "scatter",
            title: "Euclidean",
            tabComponent: "euclideanTab",
            params: {
              layoutKey: euclideanLayout?.layout_key,
              geometry: "euclidean" as Geometry,
            },
            renderer: "always",
            ...baseOptions,
          });
          break;

        case PANEL.SCATTER_POINCARE:
          api.addPanel({
            id: PANEL.SCATTER_POINCARE,
            component: "scatter",
            title: "Hyperbolic",
            tabComponent: "hyperbolicTab",
            params: {
              layoutKey: poincareLayout?.layout_key,
              geometry: "poincare" as Geometry,
            },
            renderer: "always",
            ...baseOptions,
          });
          break;
      }
    },
    [ctx?.api, datasetInfo]
  );

  const resetLayout = useCallback(() => {
    localStorage.removeItem(LAYOUT_STORAGE_KEY);
    window.location.reload();
  }, []);

  // Toggle zone visibility
  const toggleZone = useCallback(
    (zone: "left" | "right" | "bottom") => {
      if (!ctx?.api) return;

      const api = ctx.api;
      const panelId =
        zone === "left"
          ? PANEL.EXPLORER
          : zone === "right"
          ? PANEL.RIGHT_PLACEHOLDER
          : PANEL.BOTTOM_PLACEHOLDER;
      const setOpen =
        zone === "left"
          ? setLeftPanelOpen
          : zone === "right"
          ? setRightPanelOpen
          : setBottomPanelOpen;
      const isOpen =
        zone === "left"
          ? leftPanelOpen
          : zone === "right"
          ? rightPanelOpen
          : bottomPanelOpen;

      const existingPanel = api.getPanel(panelId);

      if (isOpen && existingPanel) {
        existingPanel.api.close();
        setOpen(false);
        return;
      }

      if (isOpen) return;

      const containerWidth = getContainerWidth(api);
      const containerHeight = getContainerHeight(api);
      const position = getZonePosition(zone);

      let newPanel;
      if (zone === "left") {
        const targetWidth = getDefaultLeftPanelWidth(containerWidth);
        newPanel = api.addPanel({
          id: panelId,
          component: "explorer",
          title: "Explorer",
          position,
          initialWidth: targetWidth,
          minimumWidth: MIN_SIDE_PANEL_WIDTH,
          maximumWidth: targetWidth,
        });

        if (newPanel) {
          newPanel.group.locked = true;
          newPanel.group.header.hidden = true;
          // Explicitly set the width to ensure it's applied
          newPanel.api.setSize({ width: targetWidth });
        }
      } else if (zone === "right") {
        newPanel = api.addPanel({
          id: panelId,
          component: "placeholder",
          title: "Blank",
          position,
          initialWidth: getDefaultRightPanelWidth(containerWidth),
          minimumWidth: MIN_SIDE_PANEL_WIDTH,
          maximumWidth: Math.round(containerWidth * 0.65),
        });
      } else {
        newPanel = api.addPanel({
          id: panelId,
          component: "placeholder",
          title: "Blank",
          position,
          initialHeight: getDefaultBottomPanelHeight(containerHeight),
          minimumHeight: MIN_BOTTOM_PANEL_HEIGHT,
          maximumHeight: getBottomPanelMaxHeight(containerHeight),
        });
      }

      if (newPanel) {
        setOpen(true);
        // Activate the panel so its content renders immediately
        newPanel.api.setActive();
      }
    },
    [
      ctx?.api,
      leftPanelOpen,
      rightPanelOpen,
      bottomPanelOpen,
      setLeftPanelOpen,
      setRightPanelOpen,
      setBottomPanelOpen,
    ]
  );

  if (!ctx) return null;

  return {
    api: ctx.api,
    addPanel,
    resetLayout,
    toggleZone,
  };
}

// -----------------------------------------------------------------------------
// Panel Components - stable references defined outside component
// -----------------------------------------------------------------------------
type ScatterPanelParams = {
  layoutKey?: string;
  geometry?: Geometry;
};

const ScatterDockPanel = React.memo(function ScatterDockPanel(
  props: IDockviewPanelProps<ScatterPanelParams>
) {
  const params = props.params ?? {};
  return (
    <ScatterPanel
      className="h-full"
      layoutKey={params.layoutKey}
      geometry={params.geometry}
    />
  );
});

// Custom tab component with icon (like Rerun's "Image and segmentation mask" tab)
type TabWithIconProps = IDockviewPanelHeaderProps & {
  icon: React.ReactNode;
};

const TabWithIcon = React.memo(function TabWithIcon({ api, icon }: TabWithIconProps) {
  return (
    <div className="flex items-center gap-1 text-[12px] leading-[16px] font-medium tracking-[-0.15px]">
      <span className="flex-shrink-0">{icon}</span>
      <span className="truncate">{api.title}</span>
    </div>
  );
});

// Tab components for different panel types
const EuclideanTab = React.memo(function EuclideanTab(props: IDockviewPanelHeaderProps) {
  return <TabWithIcon {...props} icon={<Circle className="h-3.5 w-3.5" />} />;
});

const HyperbolicTab = React.memo(function HyperbolicTab(props: IDockviewPanelHeaderProps) {
  return <TabWithIcon {...props} icon={<Disc className="h-3.5 w-3.5" />} />;
});

const SamplesTab = React.memo(function SamplesTab(props: IDockviewPanelHeaderProps) {
  return <TabWithIcon {...props} icon={<Grid3X3 className="h-3.5 w-3.5" />} />;
});

// Grid panel uses context to get samples
const GridDockPanel = React.memo(function GridDockPanel() {
  const ctx = useDockviewContext();
  return (
    <ImageGrid
      samples={ctx.samples}
      onLoadMore={ctx.onLoadMore}
      hasMore={ctx.hasMore}
    />
  );
});

// Explorer panel for left zone
const ExplorerDockPanel = React.memo(function ExplorerDockPanel() {
  return <ExplorerPanel />;
});

// Placeholder panel for right/bottom zones
const PlaceholderDockPanel = React.memo(function PlaceholderDockPanel(
  props: IDockviewPanelProps
) {
  const handleClose = React.useCallback(() => {
    props.api.close();
  }, [props.api]);

  return <PlaceholderPanel onClose={handleClose} />;
});

// Watermark shown when dock is empty - just the logo, no text
const Watermark = React.memo(function Watermark(_props: IWatermarkPanelProps) {
  return (
    <div className="flex items-center justify-center h-full w-full">
      <div className="text-muted-foreground/20">
        <HyperViewLogo className="w-16 h-16" />
      </div>
    </div>
  );
});

// Stable components object - never changes
const COMPONENTS = {
  grid: GridDockPanel,
  scatter: ScatterDockPanel,
  explorer: ExplorerDockPanel,
  placeholder: PlaceholderDockPanel,
};

// Tab components with icons
const TAB_COMPONENTS = {
  euclideanTab: EuclideanTab,
  hyperbolicTab: HyperbolicTab,
  samplesTab: SamplesTab,
};

// -----------------------------------------------------------------------------
// Provider Component
// -----------------------------------------------------------------------------
interface DockviewProviderProps {
  children: ReactNode;
  samples: Sample[];
  onLoadMore: () => void;
  hasMore: boolean;
}

export function DockviewProvider({
  children,
  samples,
  onLoadMore,
  hasMore,
}: DockviewProviderProps) {
  const [api, setApi] = useState<DockviewApi | null>(null);

  const contextValue = useMemo(
    () => ({
      api,
      setApi,
      samples,
      onLoadMore,
      hasMore,
    }),
    [api, samples, onLoadMore, hasMore]
  );

  return (
    <DockviewContext.Provider value={contextValue}>
      {children}
    </DockviewContext.Provider>
  );
}

function applyZonePolicies(api: DockviewApi) {
  const explorer = api.getPanel(PANEL.EXPLORER);
  if (explorer) {
    explorer.group.locked = true;
    explorer.group.header.hidden = true;
  }

  // Hide tab headers for placeholder panels
  const rightPlaceholder = api.getPanel(PANEL.RIGHT_PLACEHOLDER);
  if (rightPlaceholder) {
    rightPlaceholder.group.header.hidden = true;
  }

  const bottomPlaceholder = api.getPanel(PANEL.BOTTOM_PLACEHOLDER);
  if (bottomPlaceholder) {
    bottomPlaceholder.group.header.hidden = true;
  }
}

// -----------------------------------------------------------------------------
// Workspace Component - the actual dockview renderer
// -----------------------------------------------------------------------------
export function DockviewWorkspace() {
  const ctx = useDockviewContext();
  const datasetInfo = useStore((state) => state.datasetInfo);
  const { setLeftPanelOpen, setRightPanelOpen, setBottomPanelOpen } = useStore();

  const buildDefaultLayout = useCallback(
    (api: DockviewApi) => {
      const layouts = datasetInfo?.layouts ?? [];
      const euclideanLayout = findLayoutByGeometry(layouts, "euclidean");
      const poincareLayout = findLayoutByGeometry(layouts, "poincare");
      const fallbackLayout = !euclideanLayout && !poincareLayout ? layouts[0] : null;
      const hasLayouts = layouts.length > 0;

      // Create the grid panel first (center zone)
      const gridPanel =
        api.getPanel(PANEL.GRID) ??
        api.addPanel({
          id: PANEL.GRID,
          component: "grid",
          title: "Samples",
          tabComponent: "samplesTab",
          renderer: "always",
        });

      let scatterPanel: typeof gridPanel | null = null;

      if (hasLayouts && euclideanLayout) {
        scatterPanel =
          api.getPanel(PANEL.SCATTER_EUCLIDEAN) ??
          api.addPanel({
            id: PANEL.SCATTER_EUCLIDEAN,
            component: "scatter",
            title: "Euclidean",
            tabComponent: "euclideanTab",
            params: {
              layoutKey: euclideanLayout.layout_key,
              geometry: "euclidean" as Geometry,
            },
            position: {
              referencePanel: gridPanel.id,
              direction: "right",
            },
            renderer: "always",
          });
      }

      if (hasLayouts && poincareLayout) {
        const position = scatterPanel
          ? { referencePanel: scatterPanel.id, direction: "within" as const }
          : { referencePanel: gridPanel.id, direction: "right" as const };

        const poincarePanel =
          api.getPanel(PANEL.SCATTER_POINCARE) ??
          api.addPanel({
            id: PANEL.SCATTER_POINCARE,
            component: "scatter",
            title: "Hyperbolic",
            tabComponent: "hyperbolicTab",
            params: {
              layoutKey: poincareLayout.layout_key,
              geometry: "poincare" as Geometry,
            },
            position,
            renderer: "always",
          });

        if (!scatterPanel) {
          scatterPanel = poincarePanel;
        }
      }

      if (!hasLayouts) {
        const euclideanPanel =
          api.getPanel(PANEL.SCATTER_EUCLIDEAN) ??
          api.addPanel({
            id: PANEL.SCATTER_EUCLIDEAN,
            component: "scatter",
            title: "Euclidean",
            tabComponent: "euclideanTab",
            params: {
              geometry: "euclidean" as Geometry,
            },
            position: {
              referencePanel: gridPanel.id,
              direction: "right",
            },
            renderer: "always",
          });

        api.getPanel(PANEL.SCATTER_POINCARE) ??
          api.addPanel({
            id: PANEL.SCATTER_POINCARE,
            component: "scatter",
            title: "Hyperbolic",
            tabComponent: "hyperbolicTab",
            params: {
              geometry: "poincare" as Geometry,
            },
            position: {
              referencePanel: euclideanPanel.id,
              direction: "within" as const,
            },
            renderer: "always",
          });

        scatterPanel = euclideanPanel;
      }

      if (fallbackLayout && !scatterPanel) {
        api.getPanel(PANEL.SCATTER_DEFAULT) ??
          api.addPanel({
            id: PANEL.SCATTER_DEFAULT,
            component: "scatter",
            title: "Embeddings",
            params: {
              layoutKey: fallbackLayout.layout_key,
            },
            position: {
              referencePanel: gridPanel.id,
              direction: "right",
            },
            renderer: "always",
          });
      }

      const containerWidth = getContainerWidth(api);
      const explorerPanel =
        api.getPanel(PANEL.EXPLORER) ??
        api.addPanel({
          id: PANEL.EXPLORER,
          component: "explorer",
          title: "Explorer",
          position: getZonePosition("left"),
          initialWidth: getDefaultLeftPanelWidth(containerWidth),
          minimumWidth: MIN_SIDE_PANEL_WIDTH,
          maximumWidth: getDefaultLeftPanelWidth(containerWidth),
          inactive: true,
        });

      if (explorerPanel) {
        explorerPanel.group.locked = true;
        explorerPanel.group.header.hidden = true;
      }

      setLeftPanelOpen(!!explorerPanel);
      setRightPanelOpen(false);
      setBottomPanelOpen(false);
    },
    [datasetInfo, setLeftPanelOpen, setRightPanelOpen, setBottomPanelOpen]
  );

  const onReady = useCallback(
    (event: DockviewReadyEvent) => {
      ctx.setApi(event.api);

      const stored = localStorage.getItem(LAYOUT_STORAGE_KEY);
      if (stored) {
        try {
          event.api.fromJSON(JSON.parse(stored));
          if (event.api.totalPanels === 0) {
            localStorage.removeItem(LAYOUT_STORAGE_KEY);
            buildDefaultLayout(event.api);
          }

          // Re-apply side-zone policies after restore (header hidden, no-drop targets, etc)
          applyZonePolicies(event.api);
          
          // Sync store state with restored layout
          setLeftPanelOpen(!!event.api.getPanel(PANEL.EXPLORER));
          setRightPanelOpen(!!event.api.getPanel(PANEL.RIGHT_PLACEHOLDER));
          setBottomPanelOpen(!!event.api.getPanel(PANEL.BOTTOM_PLACEHOLDER));
          return;
        } catch (err) {
          console.warn("Failed to restore dock layout, resetting.", err);
          localStorage.removeItem(LAYOUT_STORAGE_KEY);
        }
      }

      if (event.api.totalPanels === 0) {
        buildDefaultLayout(event.api);
      }
    },
    [buildDefaultLayout, ctx, setLeftPanelOpen, setRightPanelOpen, setBottomPanelOpen]
  );

  // Save layout on changes
  useEffect(() => {
    const api = ctx.api;
    if (!api) return;

    const disposable = api.onDidLayoutChange(() => {
      if (api.totalPanels === 0) return;
      const layout = api.toJSON();
      localStorage.setItem(LAYOUT_STORAGE_KEY, JSON.stringify(layout));
    });

    return () => disposable.dispose();
  }, [ctx.api]);

  // Sync panel state when panels are closed
  useEffect(() => {
    const api = ctx.api;
    if (!api) return;

    const disposable = api.onDidRemovePanel((e) => {
      if (e.id === PANEL.EXPLORER) setLeftPanelOpen(false);
      if (e.id === PANEL.RIGHT_PLACEHOLDER) setRightPanelOpen(false);
      if (e.id === PANEL.BOTTOM_PLACEHOLDER) setBottomPanelOpen(false);
    });

    return () => disposable.dispose();
  }, [ctx.api, setLeftPanelOpen, setRightPanelOpen, setBottomPanelOpen]);

  // When a real panel is dropped into a placeholder group, close the placeholder
  useEffect(() => {
    const api = ctx.api;
    if (!api) return;

    const disposable = api.onDidAddPanel((e) => {
      // Skip if the added panel is a placeholder itself
      if (e.id === PANEL.RIGHT_PLACEHOLDER || e.id === PANEL.BOTTOM_PLACEHOLDER) {
        return;
      }

      // Check if this panel was added to the same group as a placeholder
      const group = e.group;
      if (!group) return;

      // Find and close any placeholder panels in the same group
      const rightPlaceholder = api.getPanel(PANEL.RIGHT_PLACEHOLDER);
      const bottomPlaceholder = api.getPanel(PANEL.BOTTOM_PLACEHOLDER);

      if (rightPlaceholder && rightPlaceholder.group?.id === group.id) {
        rightPlaceholder.api.close();
      }
      if (bottomPlaceholder && bottomPlaceholder.group?.id === group.id) {
        bottomPlaceholder.api.close();
      }
    });

    return () => disposable.dispose();
  }, [ctx.api]);

  // Prevent dragging locked panels (explorer only)
  useEffect(() => {
    const api = ctx.api;
    if (!api) return;

    const disposable = api.onWillDragPanel((event) => {
      if (DRAG_LOCKED_PANEL_IDS.has(event.panel.id)) {
        event.nativeEvent.preventDefault();
      }
    });

    return () => disposable.dispose();
  }, [ctx.api]);

  // Rebuild layout when dataset info changes
  useEffect(() => {
    if (!ctx.api) return;
    if (!datasetInfo) return;

    const hasScatter =
      ctx.api.getPanel(PANEL.SCATTER_EUCLIDEAN) ||
      ctx.api.getPanel(PANEL.SCATTER_POINCARE) ||
      ctx.api.getPanel(PANEL.SCATTER_DEFAULT);

    if (!hasScatter) {
      buildDefaultLayout(ctx.api);
    }
  }, [buildDefaultLayout, datasetInfo, ctx.api]);

  return (
    <div className="h-full w-full">
      <DockviewReact
        className="dockview-theme-abyss hyperview-dockview"
        components={COMPONENTS}
        tabComponents={TAB_COMPONENTS}
        onReady={onReady}
        theme={themeAbyss}
        defaultRenderer="always"
        scrollbars="native"
        watermarkComponent={Watermark}
      />
    </div>
  );
}
