import { useState, useCallback, useRef, useEffect, useMemo } from 'react';
import { trpc } from '@/lib/trpc';

export type FloatingDomainSignal =
  | 'tasks'
  | 'notes'
  | 'timelines'
  | 'projects'
  | {
      type: 'task' | 'timeline' | 'project' | 'note' | 'node';
      id?: string | null;
      section?: 'task' | 'timeline' | 'note' | null;
    };

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

/**
 * Renderer-only context describing where the user is in the UI.
 * Retained for call-site compatibility; mode/scope fields support v3 routing.
 */
export interface UIChatContext {
  type: 'global' | 'project' | 'floating';
  projectId?: string;
  /** For floating mode — the entity scope to pass to the backend. */
  scope?: {
    type: 'task' | 'project' | 'note' | 'timeline';
    id?: string;
  };
}

export interface ChatMessage {
  id: string;
  role: 'user' | 'assistant';
  content: string;
  error?: boolean;
}

interface UseAIChatReturn {
  messages: ChatMessage[];
  isStreaming: boolean;
  streamingContent: string;
  handleSend: (message: string, overrideContext?: UIChatContext) => void;
  clearMessages: (resetSession?: boolean) => void;
  /** Cache key for this chat context — pass to ChatInputBox for draft persistence. */
  cacheKey: string;
}

interface UseAIChatOptions {
  onDomainSignal?: (domain: FloatingDomainSignal) => void;
}

interface CachedChatState {
  messages: ChatMessage[];
  /** Written by ChatInputBox; read on mount to restore draft. Not written by this hook. */
  input: string;
  sessionId: string;
}

const chatSessionCache = new Map<string, CachedChatState>();

function getContextCacheKey(ctx: UIChatContext): string {
  if (ctx.type === 'global') return 'global';
  if (ctx.type === 'project') return `project:${ctx.projectId ?? ''}`;

  // Floating chat should keep a single continuous session while the panel is open,
  // even when route/section context changes due floating-domain navigation.
  return 'floating';
}

// ---------------------------------------------------------------------------
// Draft helpers — used by ChatInputBox to persist the textarea value
// ---------------------------------------------------------------------------

export function readInputDraft(key: string): string {
  return chatSessionCache.get(key)?.input ?? '';
}

export function writeInputDraft(key: string, value: string): void {
  const existing = chatSessionCache.get(key);
  chatSessionCache.set(key, {
    messages: existing?.messages ?? [],
    sessionId: existing?.sessionId ?? crypto.randomUUID(),
    input: value,
  });
}

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

const TABLE_TO_ENTITY: Record<string, 'task' | 'project' | 'note' | 'timeline'> = {
  tasks: 'task',
  projects: 'project',
  notes: 'note',
  timelines: 'timeline',
  timelineEvents: 'timeline',
};

function parseMutationsToEntityTags(mutations: unknown[] | undefined): string {
  if (!Array.isArray(mutations)) return '';
  const tags: string[] = [];
  for (const m of mutations) {
    if (!m || typeof m !== 'object') continue;
    const mut = m as Record<string, unknown>;
    if (mut.action !== 'select') continue;
    const entity = TABLE_TO_ENTITY[String(mut.table ?? '')];
    if (!entity) continue;
    const data = mut.data as Record<string, unknown> | undefined;
    const rows = Array.isArray(data?.rows) ? (data!.rows as Record<string, unknown>[]) : [];
    if (!rows.length) continue;
    const ids = rows.map((r) => String(r.id ?? '')).filter(Boolean);
    if (!ids.length) continue;
    tags.push(`<${entity}>[${ids.join(',')}]</${entity}>`);
  }
  return tags.length ? '\n' + tags.join('\n') : '';
}

// ---------------------------------------------------------------------------
// Hook
// ---------------------------------------------------------------------------

export function useAIChat(defaultContext: UIChatContext, options?: UseAIChatOptions): UseAIChatReturn {
  const contextCacheKey = useMemo(
    () => getContextCacheKey(defaultContext),
    [defaultContext.type, defaultContext.projectId, defaultContext.scope?.type, defaultContext.scope?.id],
  );

  const [messages, setMessages] = useState<ChatMessage[]>(
    () => chatSessionCache.get(contextCacheKey)?.messages ?? [],
  );
  const [sessionId, setSessionId] = useState<string>(
    () => chatSessionCache.get(contextCacheKey)?.sessionId ?? crypto.randomUUID(),
  );
  const [isStreaming, setIsStreaming] = useState(false);
  const [streamingContent, setStreamingContent] = useState('');

  const streamingContentRef = useRef('');
  const chatMutation = trpc.ai.chat.useMutation();

  // Refs for values used inside handleSend/clearMessages callbacks.
  // Using refs breaks the stale-closure chain so these callbacks are not
  // recreated on every render.
  const chatMutationRef = useRef(chatMutation);
  chatMutationRef.current = chatMutation;
  const messagesRef = useRef(messages);
  messagesRef.current = messages;
  const sessionIdRef = useRef(sessionId);
  sessionIdRef.current = sessionId;
  const onDomainSignalRef = useRef(options?.onDomainSignal);
  onDomainSignalRef.current = options?.onDomainSignal;

  // Keep local state aligned when the chat context changes in-place.
  useEffect(() => {
    const cached = chatSessionCache.get(contextCacheKey);
    setMessages(cached?.messages ?? []);
    setSessionId(cached?.sessionId ?? crypto.randomUUID());
    setIsStreaming(false);
    setStreamingContent('');
    streamingContentRef.current = '';
    // ChatInputBox re-inits its own draft via its cacheKey effect.
  }, [contextCacheKey]);

  // Persist messages + sessionId so remounting the panel restores the conversation.
  // input is managed separately by ChatInputBox via writeInputDraft.
  useEffect(() => {
    chatSessionCache.set(contextCacheKey, {
      messages,
      sessionId,
      input: chatSessionCache.get(contextCacheKey)?.input ?? '',
    });
  }, [contextCacheKey, messages, sessionId]);

  const clearMessages = useCallback((resetSession = true) => {
    const newSessionId = resetSession ? crypto.randomUUID() : sessionIdRef.current;
    setMessages([]);
    setStreamingContent('');
    streamingContentRef.current = '';
    if (resetSession) setSessionId(newSessionId);
    chatSessionCache.set(contextCacheKey, { messages: [], input: '', sessionId: newSessionId });
  }, [contextCacheKey]);

  const handleSend = useCallback(
    (message: string, overrideContext?: UIChatContext) => {
      const trimmed = message.trim();
      if (!trimmed || isStreaming) return;

      const ctx = overrideContext ?? defaultContext;

      const userMsg: ChatMessage = {
        id: crypto.randomUUID(),
        role: 'user',
        content: trimmed,
      };
      const requestId = crypto.randomUUID();

      setMessages((prev) => [...prev, userMsg]);
      setIsStreaming(true);
      setStreamingContent('');
      streamingContentRef.current = '';

      const unsubscribe = window.electronAI.onStreamEvent((event) => {
        // Strictly process only frames for this chat request.
        if (event.requestId !== requestId) {
          return;
        }

        switch (event.type) {
          case 'stream_start':
            break;

          case 'stream_text':
            streamingContentRef.current += event.chunk;
            setStreamingContent(streamingContentRef.current);
            break;

          case 'stream_end': {
            const mutationTags = parseMutationsToEntityTags(event.mutations);
            const finalContent = streamingContentRef.current + mutationTags;

            setMessages((prev) => [
              ...prev,
              {
                id: crypto.randomUUID(),
                role: 'assistant',
                content: finalContent,
              },
            ]);
            setStreamingContent('');
            streamingContentRef.current = '';
            setIsStreaming(false);
            unsubscribe();
            break;
          }

          case 'floating_domain':
            onDomainSignalRef.current?.(event.domain);
            break;
        }
      });

      // Build conversation history from current messages (before this send), capped to last 20 turns
      const conversationHistory = messagesRef.current.slice(-20).map((m) => ({
        role: m.role as 'user' | 'assistant',
        content: m.content,
      }));

      const isFloating = ctx.type === 'floating';

      chatMutationRef.current.mutate(
        {
          requestId,
          message: trimmed,
          conversationHistory,
          sessionId: sessionIdRef.current,
          ...(isFloating && ctx.scope
            ? { mode: 'floating' as const, scope: ctx.scope }
            : {}),
        },
        {
          onSuccess: (data) => {
            if (data.error) {
              unsubscribe();
              setMessages((prev) => [
                ...prev,
                { id: crypto.randomUUID(), role: 'assistant', content: data.error!, error: true },
              ]);
              setStreamingContent('');
              streamingContentRef.current = '';
              setIsStreaming(false);
            } else {
              // Safety fallback: the tRPC response always arrives after the
              // orchestrator finishes (which is after stream_end was sent).
              // If the IPC stream_end was missed (e.g. listener removed by
              // React StrictMode, or requestId mismatch), this ensures
              // isStreaming is reset.
              unsubscribe();
              if (streamingContentRef.current) {
                setMessages((prev) => [
                  ...prev,
                  {
                    id: crypto.randomUUID(),
                    role: 'assistant',
                    content: streamingContentRef.current,
                  },
                ]);
              }
              setStreamingContent('');
              streamingContentRef.current = '';
              setIsStreaming(false);
            }
          },
          onError: (err) => {
            unsubscribe();
            setMessages((prev) => [
              ...prev,
              {
                id: crypto.randomUUID(),
                role: 'assistant',
                content: err.message || 'An unexpected error occurred.',
                error: true,
              },
            ]);
            setStreamingContent('');
            streamingContentRef.current = '';
            setIsStreaming(false);
          },
        },
      );
    },
    [isStreaming, defaultContext],
  );

  return {
    messages,
    isStreaming,
    streamingContent,
    handleSend,
    clearMessages,
    cacheKey: contextCacheKey,
  };
}
