import { useState, useRef, useCallback, useEffect } 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 MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB

// Reconnect configuration
const RECONNECT_DELAY_BASE = 1000; // 1 second
const RECONNECT_DELAY_MAX = 30000; // 30 seconds
const RECONNECT_MAX_ATTEMPTS = 10;

export function useClaudeSession() {
  const [connected, setConnected] = useState(false);
  const [sessionActive, setSessionActive] = useState(false);
  const [messages, setMessages] = useState([]);
  const [currentProject, setCurrentProject] = useState(null);
  const [currentHost, setCurrentHost] = useState(null);
  const [isProcessing, setIsProcessing] = useState(false);
  const [error, setError] = useState(null);
  const [sessionId, setSessionId] = useState(null);

  // Session stats
  const [sessionStats, setSessionStats] = useState({
    totalCost: 0,
    inputTokens: 0,
    outputTokens: 0,
    cacheReadTokens: 0,
    cacheCreationTokens: 0,
    numTurns: 0,
    contextWindow: 200000, // Default, updated from modelUsage
    contextLeftPercent: null, // From Claude's "Context left until auto-compact: X%" message
    isCompacting: false,
  });

  // Permission mode and control protocol state
  // Load from localStorage, default to 'default'
  const [permissionMode, setPermissionMode] = useState(() => {
    try {
      return localStorage.getItem('claude-permission-mode') || 'default';
    } catch {
      return 'default';
    }
  });
  const [controlInitialized, setControlInitialized] = useState(false);
  const [availableModels, setAvailableModels] = useState([]);
  const [accountInfo, setAccountInfo] = useState(null);

  // Pending permission request (tool approval dialog)
  const [pendingPermission, setPendingPermission] = useState(null);

  // Note: respondedPlanApprovals removed - plan approval now handled via control protocol permission flow

  const wsRef = useRef(null);
  const currentAssistantMessage = useRef(null);
  const reconnectAttempts = useRef(0);
  const reconnectTimeout = useRef(null);
  const intentionalClose = useRef(false);

  const connect = useCallback(() => {
    if (wsRef.current?.readyState === WebSocket.OPEN) return;

    const ws = new WebSocket(getWsUrl());
    wsRef.current = ws;

    ws.onopen = () => {
      console.log('WebSocket connected');
      setConnected(true);
      setError(null);
      reconnectAttempts.current = 0; // Reset on successful connection
    };

    ws.onclose = (event) => {
      console.log('WebSocket disconnected', { code: event.code, reason: event.reason, wasClean: event.wasClean });
      setConnected(false);
      setSessionActive(false);
      setIsProcessing(false);

      // Attempt reconnect unless it was intentional or auth failure
      if (!intentionalClose.current && event.code !== 1008) {
        if (reconnectAttempts.current < RECONNECT_MAX_ATTEMPTS) {
          const delay = Math.min(
            RECONNECT_DELAY_BASE * Math.pow(2, reconnectAttempts.current),
            RECONNECT_DELAY_MAX
          );
          reconnectAttempts.current++;
          console.log(`Reconnecting in ${delay}ms (attempt ${reconnectAttempts.current}/${RECONNECT_MAX_ATTEMPTS})`);

          reconnectTimeout.current = setTimeout(() => {
            console.log('Attempting reconnect...');
            connect();
          }, delay);
        } else {
          console.log('Max reconnect attempts reached');
          setError('Connection lost. Please refresh the page.');
        }
      }
    };

    ws.onerror = (err) => {
      console.error('WebSocket error:', err);
      setError('Connection error');
    };

    ws.onmessage = (event) => {
      try {
        const data = JSON.parse(event.data);
        handleMessage(data);
      } catch (e) {
        console.error('Failed to parse message:', e);
      }
    };
  }, []);

  const handleMessage = useCallback((data) => {
    console.log('Received:', data.type, data);

    switch (data.type) {
      case 'session_started':
        setSessionActive(true);
        setCurrentProject(data.project);
        setSessionId(data.sessionId || `session-${Date.now()}`);
        setMessages(prev => [...prev, {
          type: 'system',
          content: `Session started in ${data.project}`,
          timestamp: data.timestamp
        }]);
        break;

      case 'session_ended':
        setSessionActive(false);
        setIsProcessing(false);
        setMessages(prev => [...prev, {
          type: 'system',
          content: `Session ended (code: ${data.code})`,
          timestamp: data.timestamp
        }]);
        break;

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

      case 'raw_output':
        console.log('Raw:', data.content);
        break;

      case 'stderr':
        console.log('Stderr:', data.content);
        break;

      case 'error':
        setError(data.message);
        setIsProcessing(false);
        break;

      case 'system':
        // Handle system messages from backend
        if (data.message) {
          setMessages(prev => [...prev, {
            type: 'system',
            content: data.message,
            timestamp: data.timestamp || Date.now()
          }]);
        }
        break;

      case 'generation_stopped':
        // User requested to stop - process killed and restarting
        setIsProcessing(false);
        if (data.message) {
          setMessages(prev => [...prev, {
            type: 'system',
            content: data.message,
            timestamp: data.timestamp || Date.now()
          }]);
        }
        break;

      case 'control_initialized':
        setControlInitialized(true);
        if (data.models) setAvailableModels(data.models);
        if (data.account) setAccountInfo(data.account);
        console.log('Control protocol initialized:', data);

        // Apply saved permission mode after control protocol is ready
        try {
          const savedMode = localStorage.getItem('claude-permission-mode');
          if (savedMode && savedMode !== 'default' && wsRef.current?.readyState === WebSocket.OPEN) {
            console.log('Applying saved permission mode:', savedMode);
            wsRef.current.send(JSON.stringify({
              type: 'set_permission_mode',
              mode: savedMode
            }));
          }
        } catch {}
        break;

      case 'permission_mode_changed':
        setPermissionMode(data.mode);
        try { localStorage.setItem('claude-permission-mode', data.mode); } catch {}
        setMessages(prev => [...prev, {
          type: 'system',
          content: `Mode changed to: ${data.mode}`,
          timestamp: data.timestamp
        }]);
        break;

      case 'permission_mode':
        setPermissionMode(data.mode);
        try { localStorage.setItem('claude-permission-mode', data.mode); } catch {}
        break;

      case 'control_error':
        setError(`Control error: ${data.error}`);
        break;

      case 'permission_request':
        console.log('Permission request:', data);
        setPendingPermission({
          requestId: data.requestId,
          toolName: data.toolName,
          toolInput: data.toolInput,
          permissionSuggestions: data.permissionSuggestions,
          blockedPath: data.blockedPath
        });
        break;
    }
  }, []);

  const handleClaudeEvent = useCallback((event) => {
    // Debug: log all event types to understand the structure
    console.log('Claude Event:', event.type, event);

    // Handle different Claude event types
    if (event.type === 'assistant') {
      setIsProcessing(true);

      // Check if this is a content block
      if (event.message?.content) {
        const newMessages = [];

        for (const block of event.message.content) {
          if (block.type === 'text' && block.text) {
            // Only add text if we don't have a streaming message with similar content
            // (to avoid duplicates from stream_event + final assistant event)
            newMessages.push({
              type: 'assistant',
              content: block.text,
              timestamp: Date.now(),
              final: true  // Mark as final message
            });
          } else if (block.type === 'tool_use') {
            // Tool use is embedded in assistant message content
            newMessages.push({
              type: 'tool_use',
              tool: block.name,
              input: block.input,
              toolUseId: block.id,
              timestamp: Date.now()
            });
          }
        }

        if (newMessages.length > 0) {
          setMessages(prev => {
            // Collect existing toolUseIds for deduplication
            const existingToolUseIds = new Set(
              prev.filter(m => m.type === 'tool_use' && m.toolUseId).map(m => m.toolUseId)
            );

            // Check if last message is a streaming message - if so, replace it with final
            const last = prev[prev.length - 1];
            if (last?.type === 'assistant' && last.streaming) {
              // Replace streaming message with final content, keep other new messages (tool_use)
              const textMessages = newMessages.filter(m => m.type === 'assistant');
              const otherMessages = newMessages.filter(m => m.type !== 'assistant');

              // Deduplicate tool_use messages by toolUseId
              const uniqueOtherMessages = otherMessages.filter(
                m => m.type !== 'tool_use' || !existingToolUseIds.has(m.toolUseId)
              );

              if (textMessages.length > 0) {
                return [
                  ...prev.slice(0, -1),
                  { ...textMessages[0], streaming: false },
                  ...uniqueOtherMessages
                ];
              }
            }

            // Deduplicate tool_use messages by toolUseId
            const uniqueNewMessages = newMessages.filter(
              m => m.type !== 'tool_use' || !existingToolUseIds.has(m.toolUseId)
            );

            return [...prev, ...uniqueNewMessages];
          });
        }
      }
    } else if (event.type === 'user') {
      // Tool results come as 'user' events with message.content containing tool_result blocks
      if (event.message?.content) {
        for (const block of event.message.content) {
          if (block.type === 'tool_result') {
            console.log('Tool result found:', block);
            setMessages(prev => [...prev, {
              type: 'tool_result',
              content: block.content,
              toolUseId: block.tool_use_id,
              isError: block.is_error || false,
              timestamp: Date.now()
            }]);
          }
        }
      }
    } else if (event.type === 'content_block_delta') {
      // Streaming delta (direct)
      if (event.delta?.text) {
        setMessages(prev => {
          const last = prev[prev.length - 1];
          if (last?.type === 'assistant' && last.streaming) {
            return [
              ...prev.slice(0, -1),
              { ...last, content: last.content + event.delta.text }
            ];
          }
          return [...prev, {
            type: 'assistant',
            content: event.delta.text,
            streaming: true,
            timestamp: Date.now()
          }];
        });
      }
    } else if (event.type === 'stream_event' && event.event?.type === 'content_block_delta') {
      // Streaming delta (wrapped in stream_event)
      const deltaText = event.event?.delta?.text;
      if (deltaText) {
        setMessages(prev => {
          const last = prev[prev.length - 1];
          if (last?.type === 'assistant' && last.streaming) {
            return [
              ...prev.slice(0, -1),
              { ...last, content: last.content + deltaText }
            ];
          }
          return [...prev, {
            type: 'assistant',
            content: deltaText,
            streaming: true,
            timestamp: Date.now()
          }];
        });
      }
    } else if (event.type === 'result') {
      // Final result - extract stats and stop processing
      setIsProcessing(false);

      // Update session stats from result event
      // Claude sends detailed modelUsage with per-model stats including contextWindow
      // usage contains: input_tokens (new), cache_read_input_tokens, cache_creation_input_tokens, output_tokens
      if (event.usage || event.modelUsage || event.total_cost_usd !== undefined) {
        // Get the primary model's usage (usually claude-opus or claude-sonnet)
        const modelUsage = event.modelUsage || {};
        const primaryModel = Object.keys(modelUsage).find(k => k.includes('opus') || k.includes('sonnet'))
          || Object.keys(modelUsage)[0];
        const primaryUsage = primaryModel ? modelUsage[primaryModel] : null;

        // Context calculation: Use top-level usage (aggregated) as primary source
        // Per GitHub issue anthropics/claude-agent-sdk-typescript#66:
        // Cache tokens DON'T count toward the 200K context window - they're handled separately
        // Only input_tokens + output_tokens count toward the context window
        // Cache tokens can appear in the millions because they're cumulative across the session
        const usage = event.usage || {};

        // Prefer top-level usage (aggregated), fallback to primaryUsage (per-model)
        const inputTokens = usage.input_tokens ?? primaryUsage?.inputTokens ?? 0;
        const cacheRead = usage.cache_read_input_tokens ?? primaryUsage?.cacheReadInputTokens ?? 0;
        const cacheCreation = usage.cache_creation_input_tokens ?? primaryUsage?.cacheCreationInputTokens ?? 0;
        const outputTokens = usage.output_tokens ?? primaryUsage?.outputTokens ?? 0;

        // Context = input + output tokens ONLY (cache tokens are separate)
        const totalContextUsed = inputTokens + outputTokens;

        console.log('Result event stats:', {
          inputTokens,
          cacheRead,
          cacheCreation,
          totalContextUsed,
          outputTokens,
          contextWindow: primaryUsage?.contextWindow
        });

        setSessionStats(prev => ({
          totalCost: event.total_cost_usd ?? prev.totalCost,
          // Context = input + output tokens only (cache tokens don't count toward window)
          inputTokens: totalContextUsed,
          outputTokens: outputTokens,
          cacheReadTokens: cacheRead,
          cacheCreationTokens: cacheCreation,
          numTurns: event.num_turns ?? prev.numTurns,
          contextWindow: primaryUsage?.contextWindow ?? prev.contextWindow,
          isCompacting: false,
        }));
      }
    } else if (event.type === 'system' && event.subtype === 'result') {
      setIsProcessing(false);
    } else if (event.type === 'system' && event.message) {
      // Parse "Context left until auto-compact: X%" message
      const contextMatch = event.message.match(/Context left until auto-compact:\s*(\d+)%/i);
      if (contextMatch) {
        const contextLeft = parseInt(contextMatch[1], 10);
        console.log('Context left from Claude:', contextLeft + '%');
        setSessionStats(prev => ({
          ...prev,
          contextLeftPercent: contextLeft,
          isCompacting: false,
        }));
      }
      // Detect compacting start/end from Claude's system messages
      const msg = event.message.toLowerCase();
      if (msg.includes('compacting') || msg.includes('summarizing conversation')) {
        setSessionStats(prev => ({ ...prev, isCompacting: true }));
      } else if (msg.includes('compacted') || msg.includes('summarized')) {
        setSessionStats(prev => ({ ...prev, isCompacting: false }));
      }
    }
  }, []);

  const loadHistory = useCallback(async (project, host = null) => {
    try {
      const encodedProject = encodeURIComponent(project);
      const hostParam = host ? `?host=${host}` : '';
      const response = await fetch(`/api/history/${encodedProject}${hostParam}`, {
        credentials: 'include',
      });
      if (response.ok) {
        const data = await response.json();
        if (data.messages && data.messages.length > 0) {
          console.log(`Loaded ${data.messages.length} messages from history (source: ${data.source || 'local'})`);
          setMessages(data.messages);
          return data.sessionId;
        }
      }
    } catch (err) {
      console.error('Failed to load history:', err);
    }
    return null;
  }, []);

  const startSession = useCallback(async (project = '/projects', resume = true, host = null) => {
    if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
      setError('Not connected');
      return;
    }

    // Track which host we're connecting to
    setCurrentHost(host);

    // Load history before starting session if resuming
    if (resume) {
      await loadHistory(project, host);
    }

    wsRef.current.send(JSON.stringify({
      type: 'start_session',
      project,
      resume,
      host
    }));
  }, [loadHistory]);

  // Upload files to server
  const uploadFiles = useCallback(async (files) => {
    if (!files || files.length === 0) return [];

    const formData = new FormData();
    for (const fileData of files) {
      formData.append('files', fileData.file);
    }

    try {
      const response = await fetch(`/api/upload/${sessionId}`, {
        method: 'POST',
        body: formData,
        credentials: 'include',
      });

      if (!response.ok) {
        const error = await response.json();
        throw new Error(error.error || 'Upload failed');
      }

      const data = await response.json();
      return data.files;
    } catch (err) {
      console.error('Upload error:', err);
      setError(`Upload failed: ${err.message}`);
      return [];
    }
  }, [sessionId]);

  const sendMessage = useCallback(async (message, attachedFiles = []) => {
    if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
      setError('Not connected');
      return;
    }

    if (!sessionActive) {
      setError('No active session');
      return;
    }

    let uploadedFiles = [];
    let attachmentInfo = null;

    // Upload files if any
    if (attachedFiles.length > 0) {
      setIsProcessing(true);
      uploadedFiles = await uploadFiles(attachedFiles);

      if (uploadedFiles.length > 0) {
        // Create attachment info for the message
        attachmentInfo = uploadedFiles.map(f => ({
          path: f.path,
          name: f.originalName,
          type: f.mimeType,
          isImage: f.isImage
        }));
      }
    }

    // Build the prompt with attachment info
    let finalMessage = message;
    if (uploadedFiles.length > 0) {
      const attachmentPrefix = uploadedFiles
        .map(f => `${f.path} (${f.mimeType})`)
        .join(', ');
      finalMessage = `[Attachments: ${attachmentPrefix}]\n\n${message}`;
    }

    // Add user message to display (with attachment badge info)
    setMessages(prev => [...prev, {
      type: 'user',
      content: message,
      attachments: attachmentInfo,
      timestamp: Date.now()
    }]);

    setIsProcessing(true);

    wsRef.current.send(JSON.stringify({
      type: 'user_message',
      message: finalMessage
    }));
  }, [sessionActive, uploadFiles]);

  const stopSession = useCallback(() => {
    if (wsRef.current?.readyState === WebSocket.OPEN) {
      wsRef.current.send(JSON.stringify({ type: 'stop_session' }));
    }
    setCurrentHost(null);
  }, []);

  // Stop current generation (interrupt Claude)
  const stopGeneration = useCallback(() => {
    if (wsRef.current?.readyState === WebSocket.OPEN) {
      wsRef.current.send(JSON.stringify({ type: 'stop_generation' }));
      setIsProcessing(false);
    }
  }, []);

  const clearMessages = useCallback(() => {
    setMessages([]);
  }, []);

  // Respond to a permission request (allow/deny tool execution)
  const respondToPermission = useCallback((requestId, allow, message = null) => {
    if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
      setError('Not connected');
      return;
    }

    wsRef.current.send(JSON.stringify({
      type: 'permission_response',
      requestId,
      allow,
      message
    }));

    // Clear the pending permission
    setPendingPermission(null);
  }, []);

  // Note: respondToPlanApproval removed - plan approval now handled via permission_response with isPlanApproval flag

  // Change permission mode during session
  const changePermissionMode = useCallback((mode) => {
    if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
      setError('Not connected');
      return;
    }

    if (!sessionActive) {
      setError('No active session');
      return;
    }

    if (!controlInitialized) {
      setError('Control protocol not initialized');
      return;
    }

    wsRef.current.send(JSON.stringify({
      type: 'set_permission_mode',
      mode
    }));
  }, [sessionActive, controlInitialized]);

  // Auto-connect on mount
  useEffect(() => {
    intentionalClose.current = false;
    connect();
    return () => {
      intentionalClose.current = true;
      if (reconnectTimeout.current) {
        clearTimeout(reconnectTimeout.current);
      }
      wsRef.current?.close();
    };
  }, [connect]);

  return {
    connected,
    sessionActive,
    sessionId,
    messages,
    currentProject,
    currentHost,
    isProcessing,
    error,
    sessionStats,
    permissionMode,
    controlInitialized,
    availableModels,
    accountInfo,
    pendingPermission,
    connect,
    startSession,
    sendMessage,
    stopSession,
    stopGeneration,
    clearMessages,
    changePermissionMode,
    respondToPermission,
    setError,
    setMessages,
    setCompacting: (value) => setSessionStats(prev => ({ ...prev, isCompacting: value })),
  };
}
