import { useEffect, useMemo, useState } from 'react';
import { Terminal, Clipboard } from 'lucide-react';
import { api } from '@/api/client';
import type { Chat, Project, Session, WorkspacePane } from '@/api/types';
import { MAX_PANES, activePaneChatId, type UseWorkspacePanesResult } from '@/hooks/useWorkspacePanes';
import type { UseSessionChatsResult } from '@/hooks/useSessionChats';
import { useViewport } from '@/hooks/useViewport';
import { terminalsRegistry } from '@/lib/events';
import { ChatPane } from '@/components/panes/ChatPane';
import { SettingsPane } from '@/components/panes/SettingsPane';
import { TerminalPane } from '@/components/panes/TerminalPane';
import { CoderPane } from '@/components/panes/CoderPane';
import { MarkdownArtifactPane } from '@/components/MarkdownArtifactPane';
import { HtmlArtifactPane } from '@/components/HtmlArtifactPane';
import { ChatTabBar } from '@/components/ChatTabBar';
import { PaneHeaderActions } from '@/components/PaneHeaderActions';
import { SessionLandingPage } from '@/components/SessionLandingPage';
import { cn } from '@/lib/utils';

interface Props {
  sessionId: string;
  projectId: string;
  // Batch 9: threaded down to ChatPane → ChatInput → AgentPicker.
  agentId?: string | null;
  onAgentChange?: (agentId: string | null) => void | Promise<void>;
  // v1.8: panes + chats hoisted into Session.tsx so the mobile header pill
  // (MobileTabSwitcher) can share state with the pane grid.
  panesHook: UseWorkspacePanesResult;
  chatsHook: UseSessionChatsResult;
  // v1.9: passed through to SettingsPane when one is mounted in the grid.
  session: Session;
  project: Project | null;
  /** New BooCode opens a fresh coder session; chat/terminal split in-place. */
  onAddPane: (kind: 'chat' | 'terminal' | 'coder') => void;
  onCoderConnectedChange?: (paneId: string, connected: boolean) => void;
}

export function Workspace({
  sessionId,
  projectId,
  agentId,
  onAgentChange,
  panesHook,
  chatsHook,
  session,
  project,
  onCoderConnectedChange,
  onAddPane,
}: Props) {
  const {
    panes,
    tabNumbers,
    activePaneIdx,
    setActivePaneIdx,
    openChatInPane,
    switchTab,
    removeTab,
    closeOtherTabs,
    closeTabsToRight,
    closeAllTabs,
    showLandingPage,
    addSplitPane,
    createCoderTab,
    removePane,
    reopenPane,
    hasClosedPanes,
    isPaneChatPending,
    handlePaneDragStart,
    handlePaneDragOver,
    handlePaneDragLeave,
    handlePaneDrop,
    handlePaneDragEnd,
    dragOverIdx,
    draggingIdxRef,
  } = panesHook;
  const {
    chats,
    createChat,
    archiveChat,
    unarchiveChat,
    deleteChat,
    renameChat,
    handleLandingSend,
    handleLandingSkill,
  } = chatsHook;

  const { isMobile } = useViewport();

  // v1.9: workspace-level maximize state for the settings pane. CSS-only:
  // sibling panes get display:none, the maximized pane fills the grid cell.
  // ESC listener only mounted while maximized. Mobile is always full-width
  // for a single pane so maximize doesn't apply.
  const [maximized, setMaximized] = useState(false);
  const settingsIdx = panes.findIndex((p) => p.kind === 'settings');

  // Esc semantics: maximized → restore; otherwise → close settings pane (only
  // when it's the active pane). Bail when the user is typing in a field or
  // inside an open dialog so we don't eat their cancel keystroke.
  useEffect(() => {
    if (settingsIdx < 0) return;
    function onKey(e: KeyboardEvent) {
      if (e.key !== 'Escape') return;
      const t = e.target;
      if (t instanceof HTMLElement) {
        if (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.isContentEditable) return;
        if (t.closest('[role="dialog"]')) return;
      }
      if (maximized) {
        setMaximized(false);
      } else if (activePaneIdx === settingsIdx) {
        removePane(settingsIdx);
      }
    }
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [maximized, settingsIdx, activePaneIdx, removePane]);

  // If the settings pane was closed (no longer in panes) while maximized,
  // clear the maximize state so the grid renders normally.
  useEffect(() => {
    if (maximized && settingsIdx < 0) setMaximized(false);
  }, [maximized, settingsIdx]);

  function chatsForPane(pane: WorkspacePane): Chat[] {
    return pane.chatIds
      .map((id) => chats.find((c) => c.id === id))
      .filter((c): c is Chat => c !== undefined);
  }

  // v1.10 booterm: per-terminal label used by the registry that powers the
  // MessageBubble "Send to terminal" submenu. Numbered in workspace order.
  const terminalLabels = useMemo(() => {
    const out = new Map<string, string>();
    let n = 0;
    for (const p of panes) {
      if (p.kind === 'terminal') {
        n += 1;
        out.set(p.id, `Terminal ${n}`);
      }
    }
    return out;
  }, [panes]);

  // Per-coder-pane WS connection (status dot lives in the pane header).
  const [coderConnected, setCoderConnected] = useState<Record<string, boolean>>({});
  const [coderLabels, setCoderLabels] = useState<Record<string, string>>({});

  return (
    <div className="flex flex-col h-full min-h-0">
      <div
        className={cn('flex-1 min-h-0', isMobile ? 'flex' : 'grid')}
        style={
          isMobile
            ? undefined
            : maximized && settingsIdx >= 0
              ? { gridTemplateColumns: 'minmax(0, 1fr)' }
              : { gridTemplateColumns: `repeat(${panes.length}, minmax(0, 1fr))` }
        }
      >
        {panes.map((pane, idx) => {
          const isSettings = pane.kind === 'settings';
          const isTerminal = pane.kind === 'terminal';
          const isCoder = pane.kind === 'coder';
          const isArtifact = pane.kind === 'markdown_artifact' || pane.kind === 'html_artifact';
          // v1.9: when maximized, hide every pane except the settings one.
          // display:none keeps the React tree mounted so streams / drafts
          // survive the toggle without re-mount cost.
          const hiddenForMaximize = !isMobile && maximized && idx !== settingsIdx;
          const visible = (!isMobile || idx === activePaneIdx) && !hiddenForMaximize;
          if (!visible) {
            if (hiddenForMaximize) {
              return <div key={pane.id} className="hidden" />;
            }
            return null;
          }
          // Terminal + coder panes own their tab strip (no chats, no ChatTabBar).
          const isChromeless = isSettings || isTerminal || isCoder || isArtifact;
          return (
          <div
            key={pane.id}
            className={cn(
              'flex flex-col h-full min-h-0 border-r border-border last:border-r-0 relative',
              isMobile ? 'flex-1 w-full' : undefined,
              !isMobile && idx === activePaneIdx && 'ring-1 ring-inset ring-ring/20',
              !isMobile && dragOverIdx === idx && draggingIdxRef.current !== idx &&
                'before:absolute before:inset-y-0 before:left-0 before:w-0.5 before:bg-primary before:z-10'
            )}
            onClick={() => setActivePaneIdx(idx)}
            onDragOver={!isMobile && !isChromeless && panes.length > 1 ? handlePaneDragOver(idx) : undefined}
            onDragLeave={!isMobile && !isChromeless && panes.length > 1 ? handlePaneDragLeave : undefined}
            onDrop={!isMobile && !isChromeless && panes.length > 1 ? handlePaneDrop(idx) : undefined}
          >
            <div
              draggable={!isMobile && !isChromeless && panes.length > 1}
              onDragStart={!isMobile && !isChromeless && panes.length > 1 ? handlePaneDragStart(idx) : undefined}
              onDragEnd={!isMobile && !isChromeless && panes.length > 1 ? handlePaneDragEnd : undefined}
            >
              {/* Hidden on mobile per v1.8; settings + terminal panes own
                  their own header (no chats, so no ChatTabBar). */}
              {!isMobile && !isChromeless && (
                <ChatTabBar
                  pane={pane}
                  tabs={chatsForPane(pane)}
                  tabNumbers={tabNumbers}
                  onSwitchTab={(tabIdx) => switchTab(idx, tabIdx)}
                  onRemoveTab={(chatId) => removeTab(idx, chatId)}
                  onCloseOthers={(chatId) => closeOtherTabs(idx, chatId)}
                  onCloseToRight={(chatId) => closeTabsToRight(idx, chatId)}
                  onCloseAll={() => closeAllTabs(idx)}
                  onNewTab={() => void createChat(idx)}
                  onSplitPane={(kind) => onAddPane(kind)}
                  onReopenPane={hasClosedPanes ? reopenPane : undefined}
                  onShowHistory={() => showLandingPage(idx)}
                  onRename={renameChat}
                  onRemovePane={panes.length > 1 ? () => removePane(idx) : undefined}
                />
              )}
              {/* Coder panes host BooCode tabs (one chat = one agent context,
                  all sharing the session worktree). "+" adds a tab; the split
                  button adds a pane. Same tab strip as chat panes (tabKind). */}
              {isCoder && !isMobile && (
                <ChatTabBar
                  pane={pane}
                  tabs={chatsForPane(pane)}
                  tabKind="coder"
                  tabNumbers={tabNumbers}
                  onSwitchTab={(tabIdx) => switchTab(idx, tabIdx)}
                  onRemoveTab={(chatId) => removeTab(idx, chatId)}
                  onCloseOthers={(chatId) => closeOtherTabs(idx, chatId)}
                  onCloseToRight={(chatId) => closeTabsToRight(idx, chatId)}
                  onCloseAll={() => closeAllTabs(idx)}
                  onNewTab={() => void createCoderTab(idx)}
                  onSplitPane={(kind) => onAddPane(kind)}
                  onReopenPane={hasClosedPanes ? reopenPane : undefined}
                  onShowHistory={() => showLandingPage(idx)}
                  onRename={renameChat}
                  onRemovePane={panes.length > 1 ? () => removePane(idx) : undefined}
                />
              )}
              {isTerminal && (
                <div className="flex items-center gap-2 border-b border-border bg-muted/30 px-2 py-1 shrink-0">
                  <Terminal size={12} className="text-muted-foreground" />
                  <span className="text-xs text-muted-foreground">
                    {terminalLabels.get(pane.id) ?? 'Terminal'}
                  </span>
                  <div className="ml-auto flex items-center gap-0.5">
                    {/* v1.10.4: iOS Safari restricts navigator.clipboard.readText
                        outside direct user gestures. A real button click IS a
                        gesture, so this works where keystroke-driven paste may
                        not on iOS. The action lives in TerminalPane behind the
                        registry's paste() callback. */}
                    <button
                      type="button"
                      onClick={(e) => {
                        e.stopPropagation();
                        terminalsRegistry.get(pane.id)?.paste();
                      }}
                      className="inline-flex items-center justify-center p-1 rounded text-muted-foreground hover:bg-muted hover:text-foreground max-md:min-h-[44px] max-md:min-w-[44px]"
                      aria-label="Paste from clipboard"
                      title="Paste from clipboard"
                    >
                      <Clipboard size={12} />
                    </button>
                    <PaneHeaderActions
                      onSplitPane={onAddPane}
                      onReopenPane={hasClosedPanes ? reopenPane : undefined}
                      onShowHistory={() => showLandingPage(idx)}
                      onRemovePane={panes.length > 1 ? () => removePane(idx) : undefined}
                    />
                  </div>
                </div>
              )}
            </div>

            <div className="flex-1 min-h-0 overflow-hidden">
              {isSettings && project ? (
                <SettingsPane
                  session={session}
                  project={project}
                  maximized={maximized}
                  onToggleMaximize={() => setMaximized((v) => !v)}
                  onClose={() => removePane(idx)}
                  isMobile={isMobile}
                />
              ) : isTerminal ? (
                <TerminalPane
                  sessionId={sessionId}
                  paneId={pane.id}
                  label={terminalLabels.get(pane.id) ?? 'Terminal'}
                  active={idx === activePaneIdx}
                />
              ) : pane.kind === 'coder' ? (
                <CoderPane
                  sessionId={sessionId}
                  paneId={pane.id}
                  chatId={activePaneChatId(pane)}
                  chatPending={isPaneChatPending(pane.id)}
                  projectPath={project?.path}
                  onConnectedChange={(connected) => {
                    setCoderConnected((prev) =>
                      prev[pane.id] === connected ? prev : { ...prev, [pane.id]: connected },
                    );
                    onCoderConnectedChange?.(pane.id, connected);
                  }}
                  onAgentLabelChange={(label) =>
                    setCoderLabels((prev) =>
                      prev[pane.id] === label ? prev : { ...prev, [pane.id]: label },
                    )
                  }
                />
              ) : pane.kind === 'markdown_artifact' && pane.markdown_artifact_state ? (
                <MarkdownArtifactPane
                  chatId={pane.markdown_artifact_state.chat_id}
                  state={pane.markdown_artifact_state}
                  onClose={() => removePane(idx)}
                />
              ) : pane.kind === 'html_artifact' && pane.html_artifact_state ? (
                <HtmlArtifactPane
                  chatId={pane.html_artifact_state.chat_id}
                  state={pane.html_artifact_state}
                  onClose={() => removePane(idx)}
                />
              ) : pane.kind === 'chat' && pane.chatId ? (
                <ChatPane
                  sessionId={sessionId}
                  chatId={pane.chatId}
                  projectId={projectId}
                  agentId={agentId}
                  onAgentChange={onAgentChange}
                  sessionChats={chats}
                  webSearchEnabled={session.web_search_enabled}
                />
              ) : (
                <SessionLandingPage
                  projectId={projectId}
                  sessionId={sessionId}
                  agentId={agentId}
                  onAgentChange={onAgentChange}
                  createChat={() => api.chats.create(sessionId)}
                  onSend={(content) => void handleLandingSend(idx, content)}
                  onSkillInvoke={(skillName, userMessage) => void handleLandingSkill(idx, skillName, userMessage)}
                  chats={chats}
                  onOpenChat={(chatId) => openChatInPane(idx, chatId)}
                  onUnarchiveChat={unarchiveChat}
                  onArchiveChat={archiveChat}
                  onDeleteChat={deleteChat}
                />
              )}
            </div>
          </div>
          );
        })}
      </div>
    </div>
  );
}
