import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

import {
  SIDE_PANEL_DEFAULT_WIDTH,
  SIDE_PANEL_DETAIL_MIN_WIDTH,
  SIDE_PANEL_MAX_WIDTH,
  SIDE_PANEL_MIN_WIDTH,
} from "@/lib/ui-layout";
import { SIDE_PANEL_TAB, useSidePanelStore } from "@/store/side-panel";

describe("useSidePanelStore", () => {
  afterEach(() => {
    vi.unstubAllGlobals();
  });

  beforeEach(() => {
    localStorage.clear();
    useSidePanelStore.setState({
      isOpen: false,
      selectedTab: SIDE_PANEL_TAB.AI_CHAT,
      hasBeenOpened: false,
      width: SIDE_PANEL_DEFAULT_WIDTH,
      isResizing: false,
      contextTab: null,
      contextOwnerToken: 0,
      contextOutlet: null,
      hasSeenAiTriggerHint: false,
    });
  });

  it("opens the panel on the requested tab and latches hasBeenOpened", () => {
    // When
    useSidePanelStore.getState().openPanel(SIDE_PANEL_TAB.AI_CHAT);

    // Then
    const state = useSidePanelStore.getState();
    expect(state.isOpen).toBe(true);
    expect(state.selectedTab).toBe(SIDE_PANEL_TAB.AI_CHAT);
    expect(state.hasBeenOpened).toBe(true);
  });

  it("reopens on the last selected tab when no tab is given", () => {
    // Given
    useSidePanelStore.getState().openPanel(SIDE_PANEL_TAB.AI_CHAT);
    useSidePanelStore.getState().closePanel();

    // When
    useSidePanelStore.getState().openPanel();

    // Then
    expect(useSidePanelStore.getState().selectedTab).toBe(
      SIDE_PANEL_TAB.AI_CHAT,
    );
    expect(useSidePanelStore.getState().isOpen).toBe(true);
  });

  it("keeps content latched as mounted after closing", () => {
    // Given
    useSidePanelStore.getState().openPanel();

    // When
    useSidePanelStore.getState().closePanel();

    // Then
    expect(useSidePanelStore.getState().isOpen).toBe(false);
    expect(useSidePanelStore.getState().hasBeenOpened).toBe(true);
  });

  it("ignores a tab-scoped close for a tab that is not showing", () => {
    // Given
    useSidePanelStore.getState().openPanel(SIDE_PANEL_TAB.AI_CHAT);

    // When: a stale closer targets a different tab id
    useSidePanelStore.getState().closePanel(SIDE_PANEL_TAB.CONTEXT);

    // Then
    expect(useSidePanelStore.getState().isOpen).toBe(true);

    // When: the close targets the showing tab
    useSidePanelStore.getState().closePanel(SIDE_PANEL_TAB.AI_CHAT);

    // Then
    expect(useSidePanelStore.getState().isOpen).toBe(false);
  });

  it("toggles open and closed", () => {
    // When / Then
    useSidePanelStore.getState().togglePanel();
    expect(useSidePanelStore.getState().isOpen).toBe(true);
    expect(useSidePanelStore.getState().hasBeenOpened).toBe(true);

    useSidePanelStore.getState().togglePanel();
    expect(useSidePanelStore.getState().isOpen).toBe(false);
  });

  it("registering a context tab opens the panel on it", () => {
    // When
    useSidePanelStore.getState().registerContextTab({
      label: "Details",
      onRequestClose: vi.fn(),
    });

    // Then
    const state = useSidePanelStore.getState();
    expect(state.isOpen).toBe(true);
    expect(state.selectedTab).toBe(SIDE_PANEL_TAB.CONTEXT);
    expect(state.contextTab?.label).toBe("Details");
  });

  it("closing the panel asks the context owner to close its detail view", () => {
    // Given
    const onRequestClose = vi.fn();
    useSidePanelStore
      .getState()
      .registerContextTab({ label: "Details", onRequestClose });

    // When: the user dismisses the panel (X button / Escape)
    useSidePanelStore.getState().closePanel();

    // Then: the owning table is told to clear its selection
    expect(onRequestClose).toHaveBeenCalledTimes(1);
    expect(useSidePanelStore.getState().isOpen).toBe(false);
  });

  it("asks the context owner to close even while the AI tab is showing", () => {
    // Given: details registered, user switched to the AI tab
    const onRequestClose = vi.fn();
    useSidePanelStore
      .getState()
      .registerContextTab({ label: "Details", onRequestClose });
    useSidePanelStore.getState().openPanel(SIDE_PANEL_TAB.AI_CHAT);

    // When
    useSidePanelStore.getState().closePanel();

    // Then: dismissing the panel dismisses the detail view it hosts
    expect(onRequestClose).toHaveBeenCalledTimes(1);
  });

  it("unregistering the showing context tab closes the panel and falls back to AI", () => {
    // Given
    const token = useSidePanelStore.getState().registerContextTab({
      label: "Details",
      onRequestClose: vi.fn(),
    });

    // When
    useSidePanelStore.getState().unregisterContextTab(token);

    // Then
    const state = useSidePanelStore.getState();
    expect(state.contextTab).toBeNull();
    expect(state.isOpen).toBe(false);
    expect(state.selectedTab).toBe(SIDE_PANEL_TAB.AI_CHAT);
  });

  it("unregistering while the AI tab is showing keeps the panel open", () => {
    // Given: details registered but the user is chatting
    const token = useSidePanelStore.getState().registerContextTab({
      label: "Details",
      onRequestClose: vi.fn(),
    });
    useSidePanelStore.getState().openPanel(SIDE_PANEL_TAB.AI_CHAT);

    // When
    useSidePanelStore.getState().unregisterContextTab(token);

    // Then: the chat survives; only the Details tab disappears
    const state = useSidePanelStore.getState();
    expect(state.contextTab).toBeNull();
    expect(state.isOpen).toBe(true);
    expect(state.selectedTab).toBe(SIDE_PANEL_TAB.AI_CHAT);
  });

  it("replacing the context tab asks the previous owner to close itself", () => {
    // Given: finding A's detail view owns the context tab
    const closeA = vi.fn();
    useSidePanelStore
      .getState()
      .registerContextTab({ label: "Details", onRequestClose: closeA });

    // When: finding B's detail view registers
    const closeB = vi.fn();
    useSidePanelStore
      .getState()
      .registerContextTab({ label: "Details", onRequestClose: closeB });

    // Then: A is told to close; B stays untouched as the new owner
    expect(closeA).toHaveBeenCalledTimes(1);
    expect(closeB).not.toHaveBeenCalled();
    expect(useSidePanelStore.getState().contextTab?.onRequestClose).toBe(
      closeB,
    );
  });

  it("ignores an unregister from a replaced (stale) owner", () => {
    // Given: A registered, then B took over the context tab
    const tokenA = useSidePanelStore.getState().registerContextTab({
      label: "Details",
      onRequestClose: vi.fn(),
    });
    const tokenB = useSidePanelStore.getState().registerContextTab({
      label: "Details",
      onRequestClose: vi.fn(),
    });

    // When: A unmounts late and unregisters with its stale token
    useSidePanelStore.getState().unregisterContextTab(tokenA);

    // Then: B's registration survives
    const state = useSidePanelStore.getState();
    expect(state.contextTab).not.toBeNull();
    expect(state.contextOwnerToken).toBe(tokenB);
    expect(state.isOpen).toBe(true);

    // When: the current owner unregisters
    useSidePanelStore.getState().unregisterContextTab(tokenB);

    // Then
    expect(useSidePanelStore.getState().contextTab).toBeNull();
    expect(useSidePanelStore.getState().isOpen).toBe(false);
  });

  it("clamps resize widths to the allowed range", () => {
    // When: dragging far past both limits (jsdom viewport is 1024px wide)
    useSidePanelStore.getState().setWidth(100);
    expect(useSidePanelStore.getState().width).toBe(SIDE_PANEL_MIN_WIDTH);

    useSidePanelStore.getState().setWidth(5000);
    // Then: capped at 85% of the viewport
    expect(useSidePanelStore.getState().width).toBe(
      Math.floor(window.innerWidth * 0.85),
    );
  });

  it("allows the panel to grow to half the viewport on ultra-wide screens", () => {
    // Given: a viewport wider than the standard 1920px maximum threshold
    vi.stubGlobal("innerWidth", 2560);

    // When
    useSidePanelStore.getState().setWidth(5000);

    // Then
    expect(useSidePanelStore.getState().width).toBe(1280);
  });

  it("keeps the standard maximum on screens up to 1920px wide", () => {
    // Given
    vi.stubGlobal("innerWidth", 1920);

    // When
    useSidePanelStore.getState().setWidth(5000);

    // Then
    expect(useSidePanelStore.getState().width).toBe(SIDE_PANEL_MAX_WIDTH);
  });

  it("widens to detail room when a context tab registers, keeping wider user choices", () => {
    // When: registering with the default (chat) width
    useSidePanelStore.getState().registerContextTab({
      label: "Details",
      onRequestClose: vi.fn(),
    });

    // Then
    expect(useSidePanelStore.getState().width).toBe(
      SIDE_PANEL_DETAIL_MIN_WIDTH,
    );

    // Given: the user resized wider than the detail minimum
    useSidePanelStore.getState().setWidth(800);

    // When
    useSidePanelStore.getState().registerContextTab({
      label: "Details",
      onRequestClose: vi.fn(),
    });

    // Then: the wider choice is respected
    expect(useSidePanelStore.getState().width).toBe(800);
  });

  it("persists only the selected tab and width, never open state nor the context tab", () => {
    // When: the context tab is the one selected
    useSidePanelStore.getState().registerContextTab({
      label: "Details",
      onRequestClose: vi.fn(),
    });

    // Then: a transient context selection is persisted as the AI tab
    const persisted = JSON.parse(
      localStorage.getItem("side-panel-store") ?? "{}",
    );
    expect(persisted.state).toEqual({
      selectedTab: SIDE_PANEL_TAB.AI_CHAT,
      width: SIDE_PANEL_DETAIL_MIN_WIDTH,
      hasSeenAiTriggerHint: false,
    });
    expect(persisted.state.isOpen).toBeUndefined();
    expect(persisted.state.contextTab).toBeUndefined();
  });

  it("retires the AI trigger hint when the AI chat opens by any entry point", () => {
    // When: the trigger (or the Overview banner) opens the AI chat
    useSidePanelStore.getState().openPanel(SIDE_PANEL_TAB.AI_CHAT);

    // Then
    expect(useSidePanelStore.getState().hasSeenAiTriggerHint).toBe(true);
  });

  it("retires the AI trigger hint when ⌘. toggles the panel open on the AI tab", () => {
    // Given: the AI tab is the remembered selection
    expect(useSidePanelStore.getState().selectedTab).toBe(
      SIDE_PANEL_TAB.AI_CHAT,
    );

    // When
    useSidePanelStore.getState().togglePanel();

    // Then
    expect(useSidePanelStore.getState().hasSeenAiTriggerHint).toBe(true);
  });

  it("keeps the AI trigger hint while only a detail view opens the panel", () => {
    // When: a detail view registers (panel opens on the context tab)
    useSidePanelStore.getState().registerContextTab({
      label: "Details",
      onRequestClose: vi.fn(),
    });

    // Then: the user has not discovered the AI entry point yet
    expect(useSidePanelStore.getState().hasSeenAiTriggerHint).toBe(false);
  });

  it("marks and persists the AI trigger hint as seen on dismissal", () => {
    // When
    useSidePanelStore.getState().markAiTriggerHintSeen();

    // Then
    expect(useSidePanelStore.getState().hasSeenAiTriggerHint).toBe(true);
    const persisted = JSON.parse(
      localStorage.getItem("side-panel-store") ?? "{}",
    );
    expect(persisted.state.hasSeenAiTriggerHint).toBe(true);
  });
});
