import { createContext, useContext, useState, useCallback, useRef, useEffect, useMemo } from 'react';

// Build WebSocket URL from current location
function getWsUrl() {
  if (import.meta.env.VITE_WS_URL) return import.meta.env.VITE_WS_URL;
  const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
  return `${protocol}//${window.location.host}/ws`;
}

const SESSIONS_STORAGE_KEY = 'claude-webui-sessions';
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB

// Generate unique session ID
function generateSessionId() {
  return `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}

// Create initial session state (messages are stored separately for performance)
function createSessionState(id, host = 'neko', project = '/home/sumdex/projects') {
  return {
    id,
    name: null, // Auto-generated from context, or user-defined
    host,
    project,
    connected: false,
    active: false, // Claude session started
    // messages: [], // Stored in separate sessionMessages state for performance
    isProcessing: false,
    error: null,
    claudeSessionId: null,
    stats: {
      totalCost: 0,
      inputTokens: 0,
      outputTokens: 0,
      cacheReadTokens: 0,
      cacheCreationTokens: 0,
      numTurns: 0,
    },
    permissionMode: 'default',
    controlInitialized: false,
    availableCommands: [], // Slash commands from Claude
    pendingPermission: null,
    unreadCount: 0,
    currentContext: null, // For dynamic tab naming
    resumeOnStart: true,
  };
}

const SessionContext = createContext(null);

export function SessionProvider({ children }) {
  // All sessions keyed by ID (excludes messages for performance)
  const [sessions, setSessions] = useState({});

  // Messages stored separately to avoid re-rendering entire context on every message
  const [sessionMessages, setSessionMessages] = useState({});

  // Currently focused session ID
  const [focusedSessionId, setFocusedSessionId] = useState(null);

  // Sessions shown in split view (array of session IDs)
  const [splitSessions, setSplitSessions] = useState([]);

  // Tab order (array of session IDs)
  const [tabOrder, setTabOrder] = useState([]);

  // WebSocket refs keyed by session ID
  const wsRefs = useRef({});

  // Current assistant message refs keyed by session ID
  const currentAssistantMessages = useRef({});

  // Ref to current sessions state (for stable callbacks)
  const sessionsRef = useRef(sessions);
  sessionsRef.current = sessions;

  // Track if initial load is done (for auto-connecting restored sessions)
  const initialLoadDone = useRef(false);
  const sessionsToConnect = useRef([]);

  // Load sessions from localStorage on mount
  useEffect(() => {
    try {
      const stored = localStorage.getItem(SESSIONS_STORAGE_KEY);
      if (stored) {
        const data = JSON.parse(stored);
        // Restore sessions but reset connection state
        const restored = {};
        const toConnect = [];
        Object.entries(data.sessions || {}).forEach(([id, session]) => {
          restored[id] = {
            ...session,
            connected: false,
            active: false,
            isProcessing: false,
            pendingPermission: null,
            controlInitialized: false,
          };
          toConnect.push(id);
        });
        setSessions(restored);
        setTabOrder(data.tabOrder || []);
        setSplitSessions(data.splitSessions || []);
        setFocusedSessionId(data.focusedSessionId || null);
        sessionsToConnect.current = toConnect;
      }
      initialLoadDone.current = true;
    } catch (e) {
      console.error('Failed to restore sessions:', e);
      initialLoadDone.current = true;
    }
  }, []);

  // Save sessions to localStorage on change (debounced, excluding messages)
  const saveTimeoutRef = useRef(null);
  useEffect(() => {
    // Debounce saves to avoid performance issues during streaming
    if (saveTimeoutRef.current) {
      clearTimeout(saveTimeoutRef.current);
    }

    saveTimeoutRef.current = setTimeout(() => {
      try {
        // Only save essential config, not transient state
        const sessionsToSave = {};
        Object.entries(sessions).forEach(([id, session]) => {
          sessionsToSave[id] = {
            id: session.id,
            name: session.name,
            host: session.host,
            project: session.project,
            permissionMode: session.permissionMode,
            resumeOnStart: session.resumeOnStart,
          };
        });

        const data = {
          sessions: sessionsToSave,
          tabOrder,
          splitSessions,
          focusedSessionId,
        };
        localStorage.setItem(SESSIONS_STORAGE_KEY, JSON.stringify(data));
      } catch (e) {
        console.error('Failed to save sessions:', e);
      }
    }, 1000);

    return () => {
      if (saveTimeoutRef.current) {
        clearTimeout(saveTimeoutRef.current);
      }
    };
  }, [sessions, tabOrder, splitSessions, focusedSessionId]);

  // Update a specific session
  const updateSession = useCallback((sessionId, updates) => {
    setSessions(prev => {
      if (!prev[sessionId]) return prev;
      const newUpdates = typeof updates === 'function' ? updates(prev[sessionId]) : updates;
      return {
        ...prev,
        [sessionId]: {
          ...prev[sessionId],
          ...newUpdates,
        },
      };
    });
  }, []);

  // Add message to session (uses separate state for performance)
  const addMessage = useCallback((sessionId, message) => {
    setSessionMessages(prev => ({
      ...prev,
      [sessionId]: [...(prev[sessionId] || []), { ...message, timestamp: Date.now() }],
    }));

    // Update unread count in session state only if not focused
    const isFocused = sessionId === focusedSessionId || splitSessions.includes(sessionId);
    if (!isFocused) {
      setSessions(prev => {
        if (!prev[sessionId]) return prev;
        return {
          ...prev,
          [sessionId]: {
            ...prev[sessionId],
            unreadCount: (prev[sessionId].unreadCount || 0) + 1,
          },
        };
      });
    }
  }, [focusedSessionId, splitSessions]);

  // Throttled streaming update refs
  const pendingStreamUpdates = useRef({});
  const streamUpdateTimers = useRef({});

  // Update last assistant message (for streaming) - throttled to reduce re-renders
  const updateLastAssistantMessage = useCallback((sessionId, content) => {
    // Store the latest content
    pendingStreamUpdates.current[sessionId] = content;

    // If no timer is running, start one
    if (!streamUpdateTimers.current[sessionId]) {
      streamUpdateTimers.current[sessionId] = setTimeout(() => {
        const latestContent = pendingStreamUpdates.current[sessionId];
        delete pendingStreamUpdates.current[sessionId];
        delete streamUpdateTimers.current[sessionId];

        // Update only sessionMessages, not sessions (for performance)
        setSessionMessages(prev => {
          const messages = [...(prev[sessionId] || [])];

          // Find last assistant message
          for (let i = messages.length - 1; i >= 0; i--) {
            if (messages[i].type === 'assistant') {
              messages[i] = { ...messages[i], content: latestContent };
              break;
            }
          }

          return {
            ...prev,
            [sessionId]: messages,
          };
        });
      }, 50); // Update UI max every 50ms
    }
  }, []);

  // Handle Claude events (wrapped in claude_event messages from backend)
  const handleClaudeEvent = useCallback((sessionId, event) => {
    if (!event) return;

    const { type } = event;

    switch (type) {
      case 'assistant': {
        // Assistant message with content blocks (text + tool_use)
        const message = event.message;
        if (!message?.content) break;

        const textBlocks = [];
        const toolUseBlocks = [];

        for (const block of message.content) {
          if (block.type === 'text') {
            textBlocks.push(block.text);
          } else if (block.type === 'tool_use') {
            toolUseBlocks.push({
              type: 'tool_use',
              tool: block.name,
              input: block.input,
              toolUseId: block.id,
            });
          }
        }

        // Add text content as assistant message
        if (textBlocks.length > 0) {
          // Check if we're streaming and should update existing message
          if (currentAssistantMessages.current[sessionId] !== undefined) {
            updateLastAssistantMessage(sessionId, textBlocks.join('\n'));
            delete currentAssistantMessages.current[sessionId];
          } else {
            addMessage(sessionId, {
              type: 'assistant',
              content: textBlocks.join('\n'),
            });
          }
        }

        // Add tool use blocks as separate messages
        for (const toolMsg of toolUseBlocks) {
          addMessage(sessionId, toolMsg);
        }

        // Extract usage stats from message if present
        const usage = message.usage;
        if (usage) {
          const inputTokens = (usage.input_tokens || 0) + (usage.cache_read_input_tokens || 0);
          const outputTokens = usage.output_tokens || 0;
          const cacheReadTokens = usage.cache_read_input_tokens || 0;
          const cacheCreationTokens = usage.cache_creation_input_tokens || 0;

          updateSession(sessionId, (session) => ({
            stats: {
              ...(session.stats || {}),
              inputTokens: (session.stats?.inputTokens || 0) + inputTokens,
              outputTokens: (session.stats?.outputTokens || 0) + outputTokens,
              cacheReadTokens: (session.stats?.cacheReadTokens || 0) + cacheReadTokens,
              cacheCreationTokens: (session.stats?.cacheCreationTokens || 0) + cacheCreationTokens,
              numTurns: (session.stats?.numTurns || 0) + 1,
            },
          }));
        }
        break;
      }

      case 'user': {
        // User message with tool results
        const message = event.message;
        if (!message?.content) break;

        for (const block of message.content) {
          if (block.type === 'tool_result') {
            addMessage(sessionId, {
              type: 'tool_result',
              toolUseId: block.tool_use_id,
              content: typeof block.content === 'string' ? block.content : JSON.stringify(block.content),
              isError: block.is_error || false,
            });
          }
        }
        break;
      }

      case 'content_block_delta':
      case 'stream_event': {
        // Streaming text delta
        const delta = event.delta || event;
        if (delta?.type === 'text_delta' || delta?.text) {
          const text = delta.text || '';

          if (currentAssistantMessages.current[sessionId] === undefined) {
            // Start new streaming message
            currentAssistantMessages.current[sessionId] = text;
            addMessage(sessionId, { type: 'assistant', content: text });
          } else {
            // Append to existing
            currentAssistantMessages.current[sessionId] += text;
            updateLastAssistantMessage(sessionId, currentAssistantMessages.current[sessionId]);
          }
        }
        break;
      }

      case 'content_block_start': {
        // Starting a new content block (text or tool_use)
        const contentBlock = event.content_block;
        if (contentBlock?.type === 'text') {
          // Initialize streaming for text
          currentAssistantMessages.current[sessionId] = '';
          addMessage(sessionId, { type: 'assistant', content: '' });
        } else if (contentBlock?.type === 'tool_use') {
          // Tool use start - we'll get the full thing in assistant message
        }
        break;
      }

      case 'content_block_stop': {
        // Content block finished
        if (currentAssistantMessages.current[sessionId] !== undefined) {
          // Clean up streaming state - message is already updated
          delete currentAssistantMessages.current[sessionId];
        }
        break;
      }

      case 'message_start': {
        // Message is starting
        updateSession(sessionId, { isProcessing: true });
        break;
      }

      case 'message_stop': {
        // Message is complete
        if (currentAssistantMessages.current[sessionId] !== undefined) {
          delete currentAssistantMessages.current[sessionId];
        }
        break;
      }

      case 'result': {
        // Final result with stats
        console.log(`[${sessionId}] Result event:`, JSON.stringify(event, null, 2));
        const defaultStats = { totalCost: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheCreationTokens: 0, numTurns: 0 };
        updateSession(sessionId, (session) => ({
          isProcessing: false,
          stats: {
            ...(session.stats || defaultStats),
            totalCost: event.totalCost ?? session.stats?.totalCost ?? 0,
            inputTokens: event.inputTokens ?? session.stats?.inputTokens ?? 0,
            outputTokens: event.outputTokens ?? session.stats?.outputTokens ?? 0,
            cacheReadTokens: event.cacheReadTokens ?? session.stats?.cacheReadTokens ?? 0,
            cacheCreationTokens: event.cacheCreationTokens ?? session.stats?.cacheCreationTokens ?? 0,
            numTurns: event.numTurns ?? session.stats?.numTurns ?? 0,
          },
          currentContext: event.currentContext || session.currentContext,
        }));
        break;
      }

      case 'system': {
        // System events from Claude - log full event for debugging
        console.log(`[${sessionId}] System event:`, event.subtype, JSON.stringify(event, null, 2));

        // Check for context info in system init event
        if (event.subtype === 'init') {
          // Log all fields to find context info
          console.log(`[${sessionId}] System init fields:`, Object.keys(event));
        }

        // Parse context message if present
        if (event.message) {
          const contextMatch = event.message.match(/Context left[^:]*:\s*(\d+)%/i);
          if (contextMatch) {
            const contextLeft = parseInt(contextMatch[1], 10);
            console.log(`[${sessionId}] Context left:`, contextLeft + '%');
            updateSession(sessionId, (session) => ({
              stats: {
                ...(session.stats || {}),
                contextLeftPercent: contextLeft,
              },
            }));
          }
        }
        break;
      }

      default:
        console.log(`[${sessionId}] Unhandled claude event:`, type, event);
    }
  }, [updateSession, addMessage, updateLastAssistantMessage]);

  // Handle incoming WebSocket message for a session
  const handleWsMessage = useCallback((sessionId, data) => {
    const { type } = data;

    switch (type) {
      case 'session_started':
        updateSession(sessionId, {
          active: true,
          claudeSessionId: data.sessionId,
          error: null,
        });
        break;

      case 'session_stopped':
        updateSession(sessionId, {
          active: false,
          isProcessing: false,
        });
        break;

      case 'control_initialized': {
        updateSession(sessionId, {
          controlInitialized: true,
          availableCommands: data.commands || [],
        });
        // Restore saved permission mode after control is initialized
        // Read from localStorage since sessions state may be stale in this callback
        try {
          const stored = localStorage.getItem(SESSIONS_STORAGE_KEY);
          if (stored) {
            const savedData = JSON.parse(stored);
            const savedSession = savedData.sessions?.[sessionId];
            if (savedSession?.permissionMode && savedSession.permissionMode !== 'default') {
              const ws = wsRefs.current[sessionId];
              if (ws?.readyState === WebSocket.OPEN) {
                ws.send(JSON.stringify({ type: 'set_permission_mode', mode: savedSession.permissionMode }));
              }
            }
          }
        } catch (e) {
          console.error('Failed to restore permission mode:', e);
        }
        break;
      }

      case 'permission_mode_changed':
        updateSession(sessionId, {
          permissionMode: data.mode,
        });
        break;

      case 'permission_request':
        updateSession(sessionId, {
          pendingPermission: data,
        });
        break;

      case 'permission_resolved':
        updateSession(sessionId, {
          pendingPermission: null,
        });
        break;

      case 'assistant_message':
        if (data.partial) {
          // Streaming update
          if (!currentAssistantMessages.current[sessionId]) {
            currentAssistantMessages.current[sessionId] = '';
            addMessage(sessionId, { type: 'assistant', content: '' });
          }
          currentAssistantMessages.current[sessionId] = data.content;
          updateLastAssistantMessage(sessionId, data.content);
        } else {
          // Final message
          if (currentAssistantMessages.current[sessionId] !== undefined) {
            updateLastAssistantMessage(sessionId, data.content);
            delete currentAssistantMessages.current[sessionId];
          } else {
            addMessage(sessionId, { type: 'assistant', content: data.content });
          }
        }
        break;

      case 'user_message':
        addMessage(sessionId, {
          type: 'user',
          content: data.content,
          attachments: data.attachments,
        });
        break;

      case 'tool_use':
        addMessage(sessionId, {
          type: 'tool_use',
          tool: data.tool,
          input: data.input,
          toolUseId: data.toolUseId,
        });
        break;

      case 'tool_result':
        addMessage(sessionId, {
          type: 'tool_result',
          content: data.content,
          toolUseId: data.toolUseId,
          isError: data.isError,
        });
        break;

      case 'processing':
        updateSession(sessionId, { isProcessing: data.isProcessing });
        break;

      case 'result': {
        const defaultStats = { totalCost: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheCreationTokens: 0, numTurns: 0 };
        updateSession(sessionId, (session) => ({
          isProcessing: false,
          stats: {
            ...(session.stats || defaultStats),
            totalCost: data.totalCost ?? session.stats?.totalCost ?? 0,
            inputTokens: data.inputTokens ?? session.stats?.inputTokens ?? 0,
            outputTokens: data.outputTokens ?? session.stats?.outputTokens ?? 0,
            cacheReadTokens: data.cacheReadTokens ?? session.stats?.cacheReadTokens ?? 0,
            cacheCreationTokens: data.cacheCreationTokens ?? session.stats?.cacheCreationTokens ?? 0,
            numTurns: data.numTurns ?? session.stats?.numTurns ?? 0,
          },
          currentContext: data.currentContext || session.currentContext,
        }));
        break;
      }

      case 'error':
        updateSession(sessionId, {
          error: data.message,
          isProcessing: false,
        });
        addMessage(sessionId, { type: 'error', content: data.message });
        break;

      case 'history':
        if (data.messages && Array.isArray(data.messages)) {
          setSessionMessages(prev => ({
            ...prev,
            [sessionId]: data.messages,
          }));
        }
        break;

      case 'session_ended':
        updateSession(sessionId, {
          active: false,
          isProcessing: false,
        });
        addMessage(sessionId, {
          type: 'system',
          content: `Session ended (code: ${data.code})`,
        });
        break;

      case 'claude_event':
        handleClaudeEvent(sessionId, data.event);
        break;

      case 'generation_stopped':
        updateSession(sessionId, { isProcessing: false });
        if (data.message) {
          addMessage(sessionId, { type: 'system', content: data.message });
        }
        break;

      case 'context_update':
        // Live context window metrics from message_delta events
        updateSession(sessionId, (session) => ({
          stats: {
            ...(session.stats || {}),
            tokensUsed: data.tokensUsed,
            contextWindow: data.contextWindow,
            contextLeftPercent: data.percentRemaining,
          },
        }));
        break;

      case 'compacting_started':
        console.log(`[${sessionId}] Compacting started`);
        updateSession(sessionId, (session) => ({
          stats: {
            ...(session.stats || {}),
            isCompacting: true,
          },
        }));
        addMessage(sessionId, {
          type: 'system',
          content: '⏳ Context compacting in progress...',
        });
        break;

      case 'compacting_finished':
        console.log(`[${sessionId}] Compacting finished`);
        updateSession(sessionId, (session) => ({
          stats: {
            ...(session.stats || {}),
            isCompacting: false,
          },
        }));
        break;

      case 'compact_boundary':
        console.log(`[${sessionId}] Compact boundary:`, data);
        // Context was compacted - reset the metrics
        updateSession(sessionId, (session) => ({
          stats: {
            ...(session.stats || {}),
            isCompacting: false,
            tokensUsed: 0,
            contextLeftPercent: 100,
            lastCompactTrigger: data.trigger,
            lastCompactPreTokens: data.preTokens,
          },
        }));
        addMessage(sessionId, {
          type: 'system',
          content: `✅ Context compacted (${data.trigger}). Was at ${data.percentUsedBeforeCompact}% usage.`,
        });
        break;

      default:
        console.log(`[${sessionId}] Unhandled message type:`, type, data);
    }
  }, [updateSession, addMessage, updateLastAssistantMessage, handleClaudeEvent]);

  // Connect WebSocket for a session
  const connectSession = useCallback((sessionId) => {
    if (wsRefs.current[sessionId]?.readyState === WebSocket.OPEN) return;

    const ws = new WebSocket(getWsUrl());
    wsRefs.current[sessionId] = ws;

    ws.onopen = () => {
      console.log(`[${sessionId}] WebSocket connected`);
      updateSession(sessionId, { connected: true, error: null });
    };

    ws.onclose = () => {
      console.log(`[${sessionId}] WebSocket disconnected`);
      updateSession(sessionId, {
        connected: false,
        active: false,
        isProcessing: false,
      });
      delete wsRefs.current[sessionId];
    };

    ws.onerror = (err) => {
      console.error(`[${sessionId}] WebSocket error:`, err);
      updateSession(sessionId, { error: 'Connection error' });
    };

    ws.onmessage = (event) => {
      try {
        const data = JSON.parse(event.data);
        handleWsMessage(sessionId, data);
      } catch (e) {
        console.error(`[${sessionId}] Failed to parse message:`, e);
      }
    };
  }, [updateSession, handleWsMessage]);

  // Auto-connect restored sessions
  useEffect(() => {
    if (sessionsToConnect.current.length > 0) {
      const toConnect = [...sessionsToConnect.current];
      sessionsToConnect.current = [];
      toConnect.forEach(sessionId => {
        connectSession(sessionId);
      });
    }
  }, [connectSession]);

  // Disconnect WebSocket for a session
  const disconnectSession = useCallback((sessionId) => {
    const ws = wsRefs.current[sessionId];
    if (ws) {
      ws.close();
      delete wsRefs.current[sessionId];
    }
  }, []);

  // Create a new session
  const createSession = useCallback((host = 'neko', project = '/home/sumdex/projects') => {
    const id = generateSessionId();
    const session = createSessionState(id, host, project);

    setSessions(prev => ({ ...prev, [id]: session }));
    setTabOrder(prev => [...prev, id]);
    setFocusedSessionId(id);

    // Auto-connect
    setTimeout(() => connectSession(id), 0);

    return id;
  }, [connectSession]);

  // Close a session (stops Claude but keeps in tabs unless removed)
  const closeSession = useCallback((sessionId) => {
    const ws = wsRefs.current[sessionId];
    if (ws?.readyState === WebSocket.OPEN) {
      ws.send(JSON.stringify({ type: 'stop_session' }));
    }
    disconnectSession(sessionId);
  }, [disconnectSession]);

  // Remove session completely
  const removeSession = useCallback((sessionId) => {
    closeSession(sessionId);

    setSessions(prev => {
      const { [sessionId]: removed, ...rest } = prev;
      return rest;
    });

    setTabOrder(prev => prev.filter(id => id !== sessionId));
    setSplitSessions(prev => prev.filter(id => id !== sessionId));

    // Focus another session if this was focused
    setFocusedSessionId(prev => {
      if (prev === sessionId) {
        const remaining = tabOrder.filter(id => id !== sessionId);
        return remaining[0] || null;
      }
      return prev;
    });
  }, [closeSession, tabOrder]);

  // Start Claude session
  const startClaudeSession = useCallback(async (sessionId) => {
    const session = sessionsRef.current[sessionId];
    if (!session) return;

    const ws = wsRefs.current[sessionId];
    if (!ws || ws.readyState !== WebSocket.OPEN) {
      connectSession(sessionId);
      // Wait for connection
      await new Promise(resolve => setTimeout(resolve, 500));
    }

    const currentWs = wsRefs.current[sessionId];
    if (currentWs?.readyState === WebSocket.OPEN) {
      // Load history if resuming
      if (session.resumeOnStart) {
        try {
          const res = await fetch(
            `/api/history/${encodeURIComponent(session.project)}?host=${session.host}`,
            { credentials: 'include' }
          );
          const data = await res.json();
          if (data.messages && Array.isArray(data.messages)) {
            setSessionMessages(prev => ({
              ...prev,
              [sessionId]: data.messages,
            }));
          }
        } catch (e) {
          console.error('Failed to load history:', e);
        }
      }

      currentWs.send(JSON.stringify({
        type: 'start_session',
        project: session.project,
        resume: session.resumeOnStart,
        host: session.host,
      }));
    }
  }, [connectSession]);

  // Stop Claude session
  const stopClaudeSession = useCallback((sessionId) => {
    const ws = wsRefs.current[sessionId];
    if (ws?.readyState === WebSocket.OPEN) {
      ws.send(JSON.stringify({ type: 'stop_session' }));
    }
    updateSession(sessionId, { active: false, isProcessing: false });
  }, [updateSession]);

  // Send message to session
  const sendMessage = useCallback(async (sessionId, message, attachments = []) => {
    const session = sessionsRef.current[sessionId];
    if (!session?.active) return;

    const ws = wsRefs.current[sessionId];
    if (!ws || ws.readyState !== WebSocket.OPEN) return;

    // Handle file uploads if needed
    let uploadedFiles = [];
    if (attachments.length > 0) {
      const formData = new FormData();
      for (const file of attachments) {
        formData.append('files', file.file);
      }

      try {
        const res = await fetch(`/api/upload/${session.claudeSessionId}`, {
          method: 'POST',
          body: formData,
          credentials: 'include',
        });
        const data = await res.json();
        uploadedFiles = data.files || [];
      } catch (e) {
        console.error('Upload failed:', e);
        updateSession(sessionId, { error: `Upload failed: ${e.message}` });
        return;
      }
    }

    // Build message with file references
    let finalMessage = message;
    if (uploadedFiles.length > 0) {
      const fileRefs = uploadedFiles.map(f =>
        f.isImage ? `[Image: ${f.path}]` : `[File: ${f.path}]`
      ).join('\n');
      finalMessage = `${fileRefs}\n\n${message}`;
    }

    // Add user message to local state immediately for instant feedback
    addMessage(sessionId, {
      type: 'user',
      content: message,
      attachments: uploadedFiles.length > 0 ? uploadedFiles : undefined,
    });

    updateSession(sessionId, { isProcessing: true });

    ws.send(JSON.stringify({
      type: 'user_message',
      message: finalMessage,
    }));
  }, [updateSession, addMessage]);

  // Stop generation
  const stopGeneration = useCallback((sessionId) => {
    const ws = wsRefs.current[sessionId];
    if (ws?.readyState === WebSocket.OPEN) {
      ws.send(JSON.stringify({ type: 'stop_generation' }));
    }
    updateSession(sessionId, { isProcessing: false });
  }, [updateSession]);

  // Clear messages
  const clearMessages = useCallback((sessionId) => {
    setSessionMessages(prev => ({
      ...prev,
      [sessionId]: [],
    }));
    updateSession(sessionId, { unreadCount: 0 });
  }, [updateSession]);

  // Set compacting state
  const setCompacting = useCallback((sessionId, value) => {
    updateSession(sessionId, {
      stats: {
        ...(sessions[sessionId]?.stats || {}),
        isCompacting: value,
      },
    });
  }, [updateSession, sessions]);

  // Change permission mode
  const changePermissionMode = useCallback((sessionId, mode) => {
    const ws = wsRefs.current[sessionId];
    if (ws?.readyState === WebSocket.OPEN) {
      ws.send(JSON.stringify({ type: 'set_permission_mode', mode }));
    }
  }, []);

  // Respond to permission request
  const respondToPermission = useCallback((sessionId, requestId, allow) => {
    const ws = wsRefs.current[sessionId];
    if (ws?.readyState === WebSocket.OPEN) {
      ws.send(JSON.stringify({
        type: 'permission_response',
        requestId,
        allow,
      }));
    }
    updateSession(sessionId, { pendingPermission: null });
  }, [updateSession]);

  // Reorder tabs
  const reorderTabs = useCallback((fromIndex, toIndex) => {
    setTabOrder(prev => {
      const result = [...prev];
      const [removed] = result.splice(fromIndex, 1);
      result.splice(toIndex, 0, removed);
      return result;
    });
  }, []);

  // Add session to split view
  const addToSplit = useCallback((sessionId) => {
    setSplitSessions(prev => {
      if (prev.includes(sessionId)) return prev;
      if (prev.length >= 4) return prev; // Max 4 panels
      return [...prev, sessionId];
    });
  }, []);

  // Remove session from split view
  const removeFromSplit = useCallback((sessionId) => {
    setSplitSessions(prev => prev.filter(id => id !== sessionId));
  }, []);

  // Clear split view
  const clearSplit = useCallback(() => {
    setSplitSessions([]);
  }, []);

  // Mark session as read
  const markAsRead = useCallback((sessionId) => {
    updateSession(sessionId, { unreadCount: 0 });
  }, [updateSession]);

  // Rename session
  const renameSession = useCallback((sessionId, name) => {
    updateSession(sessionId, { name });
  }, [updateSession]);

  // Update session config (host, project, resume)
  const updateSessionConfig = useCallback((sessionId, config) => {
    updateSession(sessionId, config);
  }, [updateSession]);

  // Memoize focused session to prevent unnecessary re-renders
  const focusedSession = useMemo(() => {
    return focusedSessionId ? sessions[focusedSessionId] : null;
  }, [focusedSessionId, sessions]);

  // Memoize the entire context value to prevent re-renders when nothing changed
  const value = useMemo(() => ({
    // State
    sessions,
    sessionMessages,
    tabOrder,
    splitSessions,
    focusedSessionId,
    focusedSession,

    // Session management
    createSession,
    closeSession,
    removeSession,
    renameSession,
    updateSessionConfig,

    // Focus & view
    setFocusedSessionId,
    markAsRead,
    reorderTabs,
    addToSplit,
    removeFromSplit,
    clearSplit,

    // Claude session control
    connectSession,
    disconnectSession,
    startClaudeSession,
    stopClaudeSession,

    // Messaging
    sendMessage,
    stopGeneration,
    clearMessages,
    setCompacting,

    // Permissions
    changePermissionMode,
    respondToPermission,
  }), [
    sessions, sessionMessages, tabOrder, splitSessions, focusedSessionId, focusedSession,
    createSession, closeSession, removeSession, renameSession, updateSessionConfig,
    setFocusedSessionId, markAsRead, reorderTabs, addToSplit, removeFromSplit, clearSplit,
    connectSession, disconnectSession, startClaudeSession, stopClaudeSession,
    sendMessage, stopGeneration, clearMessages, setCompacting, changePermissionMode, respondToPermission,
  ]);

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

export function useSessionManager() {
  const context = useContext(SessionContext);
  if (!context) {
    throw new Error('useSessionManager must be used within SessionProvider');
  }
  return context;
}

// Hook to get a specific session's data and actions
export function useSession(sessionId) {
  const manager = useSessionManager();
  const session = manager.sessions[sessionId];
  const messages = manager.sessionMessages[sessionId] || [];

  // Combine session data with messages for backward compatibility
  const sessionWithMessages = session ? { ...session, messages } : null;

  return {
    session: sessionWithMessages,
    messages, // Also expose separately for components that only need messages
    start: () => manager.startClaudeSession(sessionId),
    stop: () => manager.stopClaudeSession(sessionId),
    send: (msg, attachments) => manager.sendMessage(sessionId, msg, attachments),
    stopGeneration: () => manager.stopGeneration(sessionId),
    clearMessages: () => manager.clearMessages(sessionId),
    changePermissionMode: (mode) => manager.changePermissionMode(sessionId, mode),
    respondToPermission: (reqId, allow) => manager.respondToPermission(sessionId, reqId, allow),
    close: () => manager.closeSession(sessionId),
    remove: () => manager.removeSession(sessionId),
    rename: (name) => manager.renameSession(sessionId, name),
    updateConfig: (config) => manager.updateSessionConfig(sessionId, config),
    addToSplit: () => manager.addToSplit(sessionId),
    removeFromSplit: () => manager.removeFromSplit(sessionId),
    focus: () => manager.setFocusedSessionId(sessionId),
    markAsRead: () => manager.markAsRead(sessionId),
  };
}
