import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import type { ComposerDraftData } from '@/components/email/email-composer';

export type ProTabKind =
  | 'mail' | 'calendar' | 'contacts' | 'files' | 'settings'
  | 'compose' | 'email';

export type ProPaneId = 'main' | 'split';
/**
 * Pro split layout. Only side-by-side is supported - the pane that "splits
 * off" always lives next to the main pane on the horizontal axis. Kept as
 * a type alias to leave room for future layouts without churning callers.
 */
export type ProSplitOrientation = 'vertical';

export type ProComposerMode = 'compose' | 'reply' | 'replyAll' | 'forward';

/**
 * Mirror of `EmailComposer.replyTo` - kept as a structural type here so the
 * tab store doesn't take a runtime dependency on the composer module.
 */
export interface ProReplyContext {
  from?: { email?: string; name?: string }[];
  replyToAddresses?: { email?: string; name?: string }[];
  to?: { email?: string; name?: string }[];
  cc?: { email?: string; name?: string }[];
  bcc?: { email?: string; name?: string }[];
  subject?: string;
  body?: string;
  htmlBody?: string;
  receivedAt?: string;
  accountId?: string;
  attachments?: Array<{
    blobId: string; name?: string; type: string; size: number;
    cid?: string; disposition?: string;
  }>;
  messageId?: string;
  inReplyTo?: string[];
  references?: string[];
  quoteHeaderHtml?: string;
  quoteHeaderText?: string;
  quoteWrapInBlockquote?: boolean;
}

export interface ProComposeTabData {
  sessionId: number;
  mode: ProComposerMode;
  replyTo?: ProReplyContext;
  initialDraftText?: string;
  initialData?: ComposerDraftData | null;
  sourceEmailId?: string | null;
  title: string;
}

export interface ProEmailTabData {
  accountId: string;
  emailId: string;
  mailboxId: string | null;
  title: string;
}

export interface ProTab {
  id: string;
  kind: ProTabKind;
  /** i18n key under `sidebar.*` for built-in app tabs. Empty for compose/email. */
  labelKey: string;
  title?: string;
  closeable: boolean;
  composeData?: ProComposeTabData;
  emailData?: ProEmailTabData;
  /** Which pane this tab lives in. Defaults to 'main' for the single-pane case. */
  paneId: ProPaneId;
}

interface ProTabState {
  tabs: ProTab[];
  /** Active tab in each pane. `split` is null when there is no split. */
  activeTabId: string;
  activeSplitTabId: string | null;
  /** When the user last clicked into a tab/body, which pane was it? */
  focusedPaneId: ProPaneId;
  splitOrientation: ProSplitOrientation | null;
  loadedTabIds: string[];

  openTab: (kind: 'mail' | 'calendar' | 'contacts' | 'files' | 'settings') => string;
  openComposeTab: (data: ProComposeTabData) => string;
  openEmailTab: (data: ProEmailTabData) => string;
  closeTab: (id: string) => void;
  setActiveTab: (id: string) => void;
  setFocusedPane: (paneId: ProPaneId) => void;

  /**
   * Move a tab next to another tab. `edge` controls whether it lands before
   * or after the target - used by the tab bar's drop indicator. Reordering
   * works both within a pane and across panes (cross-pane drops move the
   * tab to the target pane).
   */
  reorderTab: (draggedId: string, targetTabId: string, edge: 'before' | 'after') => void;

  /**
   * Move a tab to a specific pane. If moving into `split` and no split
   * exists, opens a new split using the supplied orientation.
   */
  moveTabToPane: (
    tabId: string,
    paneId: ProPaneId,
    orientation?: ProSplitOrientation,
  ) => void;

  /** Collapse the split: every split-pane tab returns to main. */
  collapseSplit: () => void;

  updateTabTitle: (id: string, title: string) => void;
  updateComposeDraft: (id: string, draft: ComposerDraftData) => void;
}

const TAB_BLUEPRINTS: Record<'mail' | 'calendar' | 'contacts' | 'files' | 'settings', { labelKey: string }> = {
  mail:     { labelKey: 'mail' },
  calendar: { labelKey: 'calendar' },
  contacts: { labelKey: 'contacts' },
  files:    { labelKey: 'files' },
  settings: { labelKey: 'settings' },
};

const HOME_TAB: ProTab = {
  id: 'home-mail',
  kind: 'mail',
  labelKey: TAB_BLUEPRINTS.mail.labelKey,
  closeable: false,
  paneId: 'main',
};

function makeId(): string {
  if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
    return crypto.randomUUID();
  }
  return `pro-tab-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
}

function neighborInPane(tabs: ProTab[], removedId: string, paneId: ProPaneId): string | null {
  const inPane = tabs.filter((t) => t.paneId === paneId);
  const idx = inPane.findIndex((t) => t.id === removedId);
  if (idx === -1) return inPane[0]?.id ?? null;
  return (inPane[idx + 1] ?? inPane[idx - 1])?.id ?? null;
}

export const useProTabStore = create<ProTabState>()(
  persist(
    (set, get) => ({
      tabs: [HOME_TAB],
      activeTabId: HOME_TAB.id,
      activeSplitTabId: null,
      focusedPaneId: 'main',
      splitOrientation: null,
      loadedTabIds: [HOME_TAB.id],

      openTab: (kind) => {
        const state = get();
        const targetPane = state.focusedPaneId;
        const existing = state.tabs.find((tab) => tab.kind === kind && tab.paneId === targetPane);
        if (existing) {
          if (targetPane === 'main') {
            set({
              activeTabId: existing.id,
              loadedTabIds: state.loadedTabIds.includes(existing.id)
                ? state.loadedTabIds
                : [...state.loadedTabIds, existing.id],
            });
          } else {
            set({
              activeSplitTabId: existing.id,
              loadedTabIds: state.loadedTabIds.includes(existing.id)
                ? state.loadedTabIds
                : [...state.loadedTabIds, existing.id],
            });
          }
          return existing.id;
        }
        const blueprint = TAB_BLUEPRINTS[kind];
        const newTab: ProTab = {
          id: makeId(),
          kind,
          labelKey: blueprint.labelKey,
          closeable: true,
          paneId: targetPane,
        };
        set({
          tabs: [...state.tabs, newTab],
          ...(targetPane === 'main'
            ? { activeTabId: newTab.id }
            : { activeSplitTabId: newTab.id }),
          loadedTabIds: [...state.loadedTabIds, newTab.id],
        });
        return newTab.id;
      },

      openComposeTab: (data) => {
        const state = get();
        const targetPane = state.focusedPaneId;
        const newTab: ProTab = {
          id: makeId(),
          kind: 'compose',
          labelKey: '',
          title: data.title,
          closeable: true,
          composeData: data,
          paneId: targetPane,
        };
        set({
          tabs: [...state.tabs, newTab],
          ...(targetPane === 'main'
            ? { activeTabId: newTab.id }
            : { activeSplitTabId: newTab.id }),
          loadedTabIds: [...state.loadedTabIds, newTab.id],
        });
        return newTab.id;
      },

      openEmailTab: (data) => {
        const state = get();
        const targetPane = state.focusedPaneId;
        const existing = state.tabs.find(
          (tab) => tab.kind === 'email'
            && tab.emailData?.emailId === data.emailId
            && tab.emailData?.accountId === data.accountId
        );
        if (existing) {
          // Focus the existing email tab in its current pane.
          if (existing.paneId === 'main') {
            set({
              activeTabId: existing.id,
              focusedPaneId: 'main',
              loadedTabIds: state.loadedTabIds.includes(existing.id)
                ? state.loadedTabIds
                : [...state.loadedTabIds, existing.id],
            });
          } else {
            set({
              activeSplitTabId: existing.id,
              focusedPaneId: 'split',
              loadedTabIds: state.loadedTabIds.includes(existing.id)
                ? state.loadedTabIds
                : [...state.loadedTabIds, existing.id],
            });
          }
          return existing.id;
        }
        const newTab: ProTab = {
          id: makeId(),
          kind: 'email',
          labelKey: '',
          title: data.title,
          closeable: true,
          emailData: data,
          paneId: targetPane,
        };
        set({
          tabs: [...state.tabs, newTab],
          ...(targetPane === 'main'
            ? { activeTabId: newTab.id }
            : { activeSplitTabId: newTab.id }),
          loadedTabIds: [...state.loadedTabIds, newTab.id],
        });
        return newTab.id;
      },

      closeTab: (id) => {
        const state = get();
        const tab = state.tabs.find((t) => t.id === id);
        if (!tab || !tab.closeable) return;

        const removedPane = tab.paneId;
        const newTabs = state.tabs.filter((t) => t.id !== id);
        const newLoaded = state.loadedTabIds.filter((tid) => tid !== id);

        let activeTabId = state.activeTabId;
        let activeSplitTabId = state.activeSplitTabId;
        let splitOrientation = state.splitOrientation;
        let focusedPaneId = state.focusedPaneId;

        if (removedPane === 'main' && state.activeTabId === id) {
          activeTabId = neighborInPane(newTabs, id, 'main') ?? HOME_TAB.id;
        }
        if (removedPane === 'split' && state.activeSplitTabId === id) {
          activeSplitTabId = neighborInPane(newTabs, id, 'split');
        }

        // If the split pane is empty, collapse the split.
        const stillSplit = newTabs.some((t) => t.paneId === 'split');
        if (!stillSplit) {
          activeSplitTabId = null;
          splitOrientation = null;
          focusedPaneId = 'main';
        }

        // Guard: never let the tab list be fully empty.
        if (newTabs.length === 0) {
          set({
            tabs: [HOME_TAB],
            activeTabId: HOME_TAB.id,
            activeSplitTabId: null,
            splitOrientation: null,
            focusedPaneId: 'main',
            loadedTabIds: [HOME_TAB.id],
          });
          return;
        }

        // Make sure the chosen active tab is loaded.
        const ensureLoaded = (loaded: string[], id: string | null) =>
          id && !loaded.includes(id) ? [...loaded, id] : loaded;
        const loaded = ensureLoaded(ensureLoaded(newLoaded, activeTabId), activeSplitTabId);

        set({
          tabs: newTabs,
          activeTabId,
          activeSplitTabId,
          splitOrientation,
          focusedPaneId,
          loadedTabIds: loaded,
        });
      },

      setActiveTab: (id) => {
        const state = get();
        const tab = state.tabs.find((t) => t.id === id);
        if (!tab) return;
        const loaded = state.loadedTabIds.includes(id)
          ? state.loadedTabIds
          : [...state.loadedTabIds, id];
        if (tab.paneId === 'main') {
          if (state.activeTabId === id && state.focusedPaneId === 'main') return;
          set({ activeTabId: id, focusedPaneId: 'main', loadedTabIds: loaded });
        } else {
          if (state.activeSplitTabId === id && state.focusedPaneId === 'split') return;
          set({ activeSplitTabId: id, focusedPaneId: 'split', loadedTabIds: loaded });
        }
      },

      setFocusedPane: (paneId) => {
        const state = get();
        if (state.focusedPaneId === paneId) return;
        // Switching focus to the split pane is only meaningful when it exists.
        if (paneId === 'split' && state.splitOrientation === null) return;
        set({ focusedPaneId: paneId });
      },

      reorderTab: (draggedId, targetTabId, edge) => {
        const state = get();
        if (draggedId === targetTabId) return;
        const dragged = state.tabs.find((t) => t.id === draggedId);
        const target = state.tabs.find((t) => t.id === targetTabId);
        if (!dragged || !target) return;

        // Mirror the moveTabToPane guard: never let a cross-pane reorder
        // empty the main pane, which would otherwise leave the layout with
        // a blank main pane next to a populated split pane.
        if (dragged.paneId === 'main' && target.paneId !== 'main') {
          const otherMainTabs = state.tabs.filter((t) => t.paneId === 'main' && t.id !== draggedId);
          if (otherMainTabs.length === 0) return;
        }

        const next = state.tabs.filter((t) => t.id !== draggedId);
        const insertAt = next.findIndex((t) => t.id === targetTabId) + (edge === 'after' ? 1 : 0);
        const reassigned: ProTab = dragged.paneId === target.paneId
          ? dragged
          : { ...dragged, paneId: target.paneId };
        next.splice(insertAt, 0, reassigned);

        // If the dragged tab was active in its old pane and just moved to a
        // different pane, fix up the active ids so the empty side doesn't
        // hang on to a stale id.
        const patch: Partial<ProTabState> = { tabs: next };
        if (dragged.paneId !== target.paneId) {
          if (dragged.paneId === 'main' && state.activeTabId === draggedId) {
            patch.activeTabId = neighborInPane(next, draggedId, 'main') ?? HOME_TAB.id;
          }
          if (dragged.paneId === 'split' && state.activeSplitTabId === draggedId) {
            patch.activeSplitTabId = neighborInPane(next, draggedId, 'split');
          }
          // Make the dragged tab active in its new home.
          if (target.paneId === 'main') {
            patch.activeTabId = draggedId;
            patch.focusedPaneId = 'main';
          } else {
            patch.activeSplitTabId = draggedId;
            patch.focusedPaneId = 'split';
          }
          // Collapse the split if it just emptied.
          const stillSplit = next.some((t) => t.paneId === 'split');
          if (!stillSplit) {
            patch.activeSplitTabId = null;
            patch.splitOrientation = null;
            patch.focusedPaneId = 'main';
          }
        }
        set(patch);
      },

      moveTabToPane: (tabId, paneId, orientation) => {
        const state = get();
        const tab = state.tabs.find((t) => t.id === tabId);
        if (!tab) return;
        if (tab.paneId === paneId) return;

        // The home tab can move freely (and the unsplit guard below restores
        // sanity), but if moving it would leave main empty we prevent it.
        const movingFromMain = tab.paneId === 'main';
        if (movingFromMain) {
          const otherMainTabs = state.tabs.filter((t) => t.paneId === 'main' && t.id !== tabId);
          if (otherMainTabs.length === 0) return; // refuse to empty main
        }

        const newTabs = state.tabs.map((t) => t.id === tabId ? { ...t, paneId } : t);

        const patch: Partial<ProTabState> = { tabs: newTabs };

        if (paneId === 'split') {
          // Creating or extending a split.
          patch.splitOrientation = state.splitOrientation ?? orientation ?? 'vertical';
          patch.activeSplitTabId = tabId;
          patch.focusedPaneId = 'split';
          // If main lost its active tab, pick a neighbor.
          if (state.activeTabId === tabId) {
            patch.activeTabId = neighborInPane(newTabs, tabId, 'main') ?? HOME_TAB.id;
          }
        } else {
          patch.activeTabId = tabId;
          patch.focusedPaneId = 'main';
          if (state.activeSplitTabId === tabId) {
            patch.activeSplitTabId = neighborInPane(newTabs, tabId, 'split');
          }
          // Collapse if split just emptied.
          const stillSplit = newTabs.some((t) => t.paneId === 'split');
          if (!stillSplit) {
            patch.activeSplitTabId = null;
            patch.splitOrientation = null;
          }
        }

        const loaded = state.loadedTabIds.includes(tabId)
          ? state.loadedTabIds
          : [...state.loadedTabIds, tabId];
        patch.loadedTabIds = loaded;

        set(patch);
      },

      collapseSplit: () => {
        const state = get();
        if (state.splitOrientation === null) return;
        const newTabs = state.tabs.map((t) =>
          t.paneId === 'split' ? { ...t, paneId: 'main' as const } : t
        );
        set({
          tabs: newTabs,
          activeSplitTabId: null,
          splitOrientation: null,
          focusedPaneId: 'main',
        });
      },

      updateTabTitle: (id, title) => {
        const state = get();
        const tabs = state.tabs.map((tab) =>
          tab.id === id ? { ...tab, title } : tab
        );
        set({ tabs });
      },

      updateComposeDraft: (id, draft) => {
        const state = get();
        const tabs = state.tabs.map((tab) => {
          if (tab.id !== id || tab.kind !== 'compose' || !tab.composeData) return tab;
          return {
            ...tab,
            composeData: { ...tab.composeData, initialData: draft },
          };
        });
        set({ tabs });
      },
    }),
    {
      name: 'pro-tabs',
      version: 3,
      // Don't persist transient compose drafts in tab metadata - the composer's
      // own draft-store already handles that. Persisted email tabs are fine to
      // restore (the tab body refetches the email by id).
      partialize: (state) => ({
        tabs: state.tabs
          .filter((tab) => tab.kind !== 'compose')
          .map((tab) => tab.kind === 'compose'
            ? { ...tab, composeData: undefined }
            : tab),
        activeTabId: state.activeTabId,
        activeSplitTabId: state.activeSplitTabId,
        splitOrientation: state.splitOrientation,
        focusedPaneId: state.focusedPaneId,
        loadedTabIds: state.loadedTabIds,
      }),
      onRehydrateStorage: () => (state) => {
        if (!state) return;
        // Backfill paneId in case the user upgrades from version 2.
        state.tabs = state.tabs.map((tab) => tab.paneId ? tab : { ...tab, paneId: 'main' as const });
        if (state.tabs.length === 0) {
          state.tabs = [HOME_TAB];
          state.activeTabId = HOME_TAB.id;
          state.activeSplitTabId = null;
          state.splitOrientation = null;
          state.focusedPaneId = 'main';
          state.loadedTabIds = [HOME_TAB.id];
          return;
        }
        // Heal broken state where every tab ended up on the split pane:
        // collapse the split so the surviving tabs return to main, otherwise
        // the layout would render an empty main pane next to the split.
        if (!state.tabs.some((t) => t.paneId === 'main')) {
          state.tabs = state.tabs.map((t) => ({ ...t, paneId: 'main' as const }));
          state.activeSplitTabId = null;
          state.splitOrientation = null;
          state.focusedPaneId = 'main';
        }
        if (!state.tabs.some((t) => t.id === state.activeTabId && t.paneId === 'main')) {
          state.activeTabId = state.tabs.find((t) => t.paneId === 'main')?.id ?? HOME_TAB.id;
        }
        if (state.activeSplitTabId !== null && !state.tabs.some((t) => t.id === state.activeSplitTabId && t.paneId === 'split')) {
          state.activeSplitTabId = state.tabs.find((t) => t.paneId === 'split')?.id ?? null;
        }
        if (state.activeSplitTabId === null) {
          state.splitOrientation = null;
          state.focusedPaneId = 'main';
        }
        if (!state.loadedTabIds.includes(state.activeTabId)) {
          state.loadedTabIds = [...state.loadedTabIds, state.activeTabId];
        }
        if (state.activeSplitTabId && !state.loadedTabIds.includes(state.activeSplitTabId)) {
          state.loadedTabIds = [...state.loadedTabIds, state.activeSplitTabId];
        }
      },
    },
  ),
);
