'use client';

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

import type { ChatConfig, ChatLabels, ChatTransport } from '../types';
import { DEFAULT_LABELS } from '../types';
import { useChat, type UseChatReturn } from '../hooks/useChat';
import { useChatLayout, type UseChatLayoutReturn } from '../hooks/useChatLayout';
import { useChatAudio } from '../hooks/useChatAudio';
import type { ChatAudioConfig, UseChatAudioReturn } from '../core/audio/types';

export interface ChatContextValue extends UseChatReturn {
  layout: UseChatLayoutReturn;
  config: ChatConfig;
  labels: ChatLabels;
  audio: UseChatAudioReturn;
  /** True iff the host wired at least one ``audio.sounds[event]`` URL.
   * Components like ``AudioToggle`` use this to auto-hide when there
   * is nothing to mute. */
  hasAudio: boolean;
}

const Ctx = createContext<ChatContextValue | null>(null);

export interface ChatProviderProps {
  transport: ChatTransport;
  config?: ChatConfig;
  initialSessionId?: string;
  autoCreateSession?: boolean;
  streaming?: boolean;
  /** Audio-trigger configuration. Off by default (no `sounds` map). */
  audio?: ChatAudioConfig;
  /** Enable verbose dev logging via consola. Defaults to `isDev`. */
  debug?: boolean;
  children?: ReactNode;
}

export function ChatProvider({
  transport,
  config = {},
  initialSessionId,
  autoCreateSession,
  streaming,
  audio,
  debug,
  children,
}: ChatProviderProps) {
  const audioApi = useChatAudio(audio ?? {});

  // Keep latest audio API in a ref so the chat-callback closures stay
  // referentially stable (don't re-mount transport on every audio change).
  const audioRef = useRef(audioApi);
  audioRef.current = audioApi;

  const onMessageSent = useCallback(() => audioRef.current.play('messageSent'), []);
  const onMessageEnd = useCallback(() => audioRef.current.play('messageReceived'), []);
  const onStreamStart = useCallback(() => audioRef.current.play('streamStart'), []);
  const onError = useCallback(() => audioRef.current.play('error'), []);

  const chat = useChat({
    transport,
    initialSessionId,
    autoCreateSession,
    streaming,
    debug,
    metadata: {
      locale: config.locale ?? config.prefs?.locale,
      slug: config.slug,
    },
    userPersona: config.user,
    onMessageSent,
    onMessageEnd,
    onStreamStart,
    onError,
  });
  const layout = useChatLayout({ defaultMode: 'embedded' });

  // Auto-unlock audio on the first user gesture inside the provider.
  const rootRef = useRef<HTMLDivElement | null>(null);
  useEffect(() => {
    if (audioApi.isUnlocked) return;
    const root = rootRef.current;
    if (!root) return;
    const handler = () => {
      audioApi.unlock();
    };
    root.addEventListener('pointerdown', handler, { once: true, capture: true });
    root.addEventListener('keydown', handler, { once: true, capture: true });
    return () => {
      root.removeEventListener('pointerdown', handler, { capture: true });
      root.removeEventListener('keydown', handler, { capture: true });
    };
  }, [audioApi]);

  const labels = useMemo<ChatLabels>(
    () => ({ ...DEFAULT_LABELS, ...(config.labels ?? {}) }),
    [config.labels],
  );

  const hasAudio = useMemo<boolean>(() => {
    const sounds = audio?.sounds;
    if (!sounds) return false;
    return Object.values(sounds).some(
      (v) => typeof v === 'string' && v.length > 0,
    );
  }, [audio]);

  const value = useMemo<ChatContextValue>(
    () => ({ ...chat, layout, config, labels, audio: audioApi, hasAudio }),
    [chat, layout, config, labels, audioApi, hasAudio],
  );

  return (
    <Ctx.Provider value={value}>
      <div ref={rootRef} style={{ display: 'contents' }}>
        {children}
      </div>
    </Ctx.Provider>
  );
}

export function useChatContext(): ChatContextValue {
  const v = useContext(Ctx);
  if (!v) throw new Error('useChatContext must be used inside <ChatProvider>');
  return v;
}

export function useChatContextOptional(): ChatContextValue | null {
  return useContext(Ctx);
}
