import { useCallback, useEffect, useState, useRef } from 'react';
import type { WsClient } from '../lib/ws-client';
import { authFetch } from '../lib/auth';

export interface StoredAttachment {
  type: string;
  name: string;
  mediaType: string;
  filePath: string;
}

export interface ChatMessage {
  id: string;
  role: 'user' | 'assistant';
  content: string;
  timestamp: string; // ISO string
  hasAttachments?: boolean;
  audioData?: string; // data URL or HTTP URL for voice messages
  attachments?: StoredAttachment[];
  transcribing?: boolean; // true while audio is being transcribed
}

export interface ToolActivity {
  name: string;
  status: 'running' | 'done';
}

export interface Attachment {
  id: string;
  type: 'image' | 'file';
  name: string;
  preview: string; // base64 data URL
}

export function useChat(ws: WsClient | null) {
  const [messages, setMessages] = useState<ChatMessage[]>([]);
  const [conversationId, setConversationId] = useState<string | null>(null);
  const [streaming, setStreaming] = useState(false);
  const [streamBuffer, setStreamBuffer] = useState('');
  const [tools, setTools] = useState<ToolActivity[]>([]);
  const loaded = useRef(false);
  /** Ref to current streamBuffer (avoids stale closures in callbacks) */
  const streamBufferRef = useRef('');

  // Load current conversation from DB on mount
  useEffect(() => {
    if (loaded.current) return;
    loaded.current = true;
    authFetch('/api/context/current')
      .then((r) => r.json())
      .then((data) => {
        if (data.conversationId) {
          setConversationId(data.conversationId);
        }
      })
      .catch(() => {});
  }, []);

  // Load messages when conversationId is set
  useEffect(() => {
    if (!conversationId) return;
    authFetch(`/api/conversations/${conversationId}`)
      .then((r) => {
        if (!r.ok) throw new Error('not found');
        return r.json();
      })
      .then((data) => {
        if (data.messages?.length) {
          setMessages(
            data.messages
              .filter((m: any) => m.role === 'user' || m.role === 'assistant')
              .map((m: any) => {
                // Backward compat for audio_data: file path → URL, data: prefix → legacy, else → prepend data URL
                let audioData: string | undefined;
                if (m.audio_data) {
                  if (m.audio_data.startsWith('data:')) {
                    audioData = m.audio_data; // legacy data URL
                  } else if (m.audio_data.includes('/')) {
                    audioData = `/api/files/${m.audio_data}`; // file path → HTTP URL
                  } else {
                    audioData = `data:audio/webm;base64,${m.audio_data}`; // raw base64
                  }
                }

                // Parse stored attachments
                let attachments: StoredAttachment[] | undefined;
                if (m.attachments) {
                  try {
                    attachments = JSON.parse(m.attachments);
                  } catch { /* ignore malformed */ }
                }

                return {
                  id: m.id,
                  role: m.role,
                  content: m.content,
                  timestamp: m.created_at,
                  audioData,
                  hasAttachments: !!(attachments && attachments.length > 0),
                  attachments,
                };
              }),
          );
        }
      })
      .catch(() => {
        // Conversation gone — clear
        setConversationId(null);
        authFetch('/api/context/clear', { method: 'POST' }).catch(() => {});
      });
  }, [conversationId]);

  // Persist conversationId to DB when it changes
  const prevConvId = useRef<string | null>(null);
  useEffect(() => {
    if (conversationId && conversationId !== prevConvId.current) {
      prevConvId.current = conversationId;
      authFetch('/api/context/set', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ conversationId }),
      }).catch(() => {});
    }
  }, [conversationId]);

  useEffect(() => {
    if (!ws) return;

    const unsubs = [
      ws.on('bot:typing', () => {
        setStreaming(true);
        setTools([]);
      }),
      ws.on('bot:token', (data: { token: string }) => {
        // Auto-detect new turn — tokens arriving means agent is working
        setStreaming(true);
        setStreamBuffer((buf) => {
          const next = buf + data.token;
          streamBufferRef.current = next;
          return next;
        });
      }),
      ws.on('bot:tool', (data: { name: string; input?: any; status?: string }) => {
        setTools((prev) => {
          const existing = prev.find((t) => t.name === data.name && t.status === 'running');
          if (existing) return prev;
          return [...prev, { name: data.name, status: 'running' }];
        });
      }),
      ws.on('bot:response', (data: { conversationId: string; messageId?: string; content: string }) => {
        setConversationId(data.conversationId);

        // Always add as a new bubble
        setMessages((msgs) => [
          ...msgs,
          {
            id: data.messageId || Date.now().toString(),
            role: 'assistant',
            content: data.content,
            timestamp: new Date().toISOString(),
          },
        ]);

        setStreamBuffer('');
        streamBufferRef.current = '';
        setTools([]);
        // Don't clear streaming here — wait for bot:idle from the server
      }),
      ws.on('bot:idle', () => {
        // Server confirmed agent is idle — safe to stop streaming
        setStreaming(false);
      }),
      ws.on('bot:error', (data: { error: string }) => {
        setStreamBuffer('');
        streamBufferRef.current = '';
        setStreaming(false);
        setTools([]);
        setMessages((msgs) => [
          ...msgs,
          {
            id: Date.now().toString(),
            role: 'assistant',
            content: `Error: ${data.error}`,
            timestamp: new Date().toISOString(),
          },
        ]);
      }),
    ];

    return () => unsubs.forEach((u) => u());
  }, [ws]);

  const sendMessage = useCallback(
    (content: string, attachments?: Attachment[], audioData?: string) => {
      if (!ws || (!content.trim() && (!attachments || attachments.length === 0))) return;

      // Build optimistic stored attachments from client previews
      const optimisticAttachments: StoredAttachment[] | undefined = attachments?.map((att) => {
        const match = att.preview.match(/^data:([^;]+);/);
        return { type: att.type, name: att.name, mediaType: match?.[1] || 'application/octet-stream', filePath: att.preview };
      });

      const userMsg: ChatMessage = {
        id: Date.now().toString(),
        role: 'user',
        content,
        timestamp: new Date().toISOString(),
        hasAttachments: !!(attachments && attachments.length > 0),
        audioData: audioData ? (audioData.startsWith('data:') ? audioData : `data:audio/webm;base64,${audioData}`) : undefined,
        attachments: optimisticAttachments,
      };

      // If bot is currently streaming, commit the partial response as a completed message
      // so the user's new message appears BELOW the bot's in-progress text (chronological order)
      const partialContent = streamBufferRef.current;
      if (partialContent) {
        setMessages((msgs) => [
          ...msgs,
          {
            id: 'partial-' + Date.now(),
            role: 'assistant' as const,
            content: partialContent,
            timestamp: new Date().toISOString(),
          },
          userMsg,
        ]);
        setStreamBuffer('');
        streamBufferRef.current = '';
      } else {
        setMessages((msgs) => [...msgs, userMsg]);
      }

      // Build WS payload
      const payload: any = { conversationId, content };
      if (audioData) {
        // Send raw base64 (strip data URL prefix)
        payload.audioData = audioData.includes(',') ? audioData.split(',')[1] : audioData;
      }
      if (attachments?.length) {
        payload.attachments = attachments.map((att) => {
          const match = att.preview.match(/^data:([^;]+);base64,(.+)$/);
          return {
            type: att.type,
            name: att.name,
            mediaType: match?.[1] || 'application/octet-stream',
            data: match?.[2] || '',
          };
        });
      }
      ws.send('user:message', payload);
    },
    [ws, conversationId],
  );

  const stopStreaming = useCallback(() => {
    if (!ws || !conversationId) return;
    ws.send('user:stop', { conversationId });
    setStreaming(false);
    setStreamBuffer('');
    streamBufferRef.current = '';
    setTools([]);
  }, [ws, conversationId]);

  const clearContext = useCallback(() => {
    setMessages([]);
    setConversationId(null);
    setStreamBuffer('');
    streamBufferRef.current = '';
    setStreaming(false);
    setTools([]);
    prevConvId.current = null;
    loaded.current = false;
    authFetch('/api/context/clear', { method: 'POST' }).catch(() => {});
  }, []);

  return { messages, streaming, streamBuffer, conversationId, tools, sendMessage, stopStreaming, clearContext };
}
