'use client';

import { type ReactNode, useRef } from 'react';

import { cn } from '@djangocfg/ui-core/lib';

import type { ChatAttachment, ChatConfig, ChatMessage, ChatTransport } from '../types';
import type { ChatAudioConfig } from '../core/audio/types';
import { ChatProvider, useChatContext, type ChatContextValue } from '../context';
import { useChatComposer, type UseChatComposerReturn } from '../hooks/useChatComposer';
import { useChatScroll } from '../hooks/useChatScroll';
import { useChatHistory } from '../hooks/useChatHistory';
import { Composer, type ComposerSize } from './Composer';
import { EmptyState } from './EmptyState';
import { ErrorBanner } from './ErrorBanner';
import { JumpToLatest } from './JumpToLatest';
import { MessageBubble } from './MessageBubble';
import { MessageList } from './MessageList';
import type { AttachmentRendererMap } from './Attachments';
import type { ToolCallsProps } from './ToolCalls';

export interface ChatRootProps {
  // ---- core wiring -------------------------------------------------------
  transport: ChatTransport;
  config?: ChatConfig;
  initialSessionId?: string;
  autoCreateSession?: boolean;
  streaming?: boolean;
  /** Audio-trigger configuration. Off by default (no `sounds` map). */
  audio?: ChatAudioConfig;
  /**
   * Verbose dev-mode logging via `consola` (namespace `chat:*`).
   * Defaults to `isDev` from `@djangocfg/ui-core/lib`. Pass `false` to silence
   * even in development, or `true` to force on in production for debugging.
   */
  debug?: boolean;
  className?: string;

  // ---- named ReactNode slots --------------------------------------------
  /** Sticky banner above the message list (e.g. quota warning). */
  banner?: ReactNode;
  /** Header row below the banner — title / actions / session switcher. */
  header?: ReactNode;
  /** Footer slot below the composer (disclaimers, model picker). */
  footer?: ReactNode;
  /** Replaces the default `<EmptyState>` rendered when the conversation is empty. */
  empty?: ReactNode;
  /** Slot left of the textarea inside `<Composer>`. */
  composerToolbarStart?: ReactNode;
  /** Slot right of the textarea inside `<Composer>`. */
  composerToolbarEnd?: ReactNode;
  /** Replaces the default attachment tray inside `<Composer>`. */
  composerAttachmentTray?: ReactNode;
  /** Replaces the default `<JumpToLatest>` floating pill. */
  jumpToLatest?: ReactNode;

  // ---- render-prop slots (need access to data) --------------------------
  /** Replace `<MessageBubble>` per message. */
  renderMessage?: (m: ChatMessage, i: number) => ReactNode;
  /** Render the header lazily — receives the chat context. */
  renderHeader?: (ctx: ChatContextValue) => ReactNode;
  /** Render the empty-state lazily — receives a `setValue` to seed the composer. */
  renderEmpty?: (api: { setValue: (v: string) => void; focus: () => void }) => ReactNode;
  /** Forwarded into `<MessageBubble toolCallsProps>` so hosts can swap payload renderers. */
  toolCallsProps?: Omit<ToolCallsProps, 'calls'>;
  /** Per-type attachment renderers — `{ image, audio, video, file, default }`. */
  attachmentRenderers?: AttachmentRendererMap;
  /** Called when an attachment tile is clicked (e.g. open lightbox). */
  onAttachmentOpen?: (attachment: ChatAttachment) => void;

  // ---- composer customization -------------------------------------------
  /** Show the paperclip "attach" button in the composer. */
  showAttachmentButton?: boolean;
  /** Called when the user clicks the attach button (host opens its file picker). */
  onPickFiles?: () => void;
  /** Hide the composer input area entirely (e.g. while waiting for human approval). */
  hideComposer?: boolean;
  /** Composer size variant. Default ``md`` (36px slot). Use ``lg`` for primary
   * surfaces (onboarding, full-page chat), ``sm`` for dense sidebars. */
  composerSize?: ComposerSize;
  /** Extra className forwarded to the `<MessageList>` scroll container.
   * Use to add padding inside the scroll area so the scrollbar stays flush
   * with the edge (e.g. `"px-6 pt-6 sm:px-8"`). */
  listClassName?: string;
  /** Extra className forwarded to the `<Composer>` wrapper div. */
  composerClassName?: string;
}

export function ChatRoot(props: ChatRootProps) {
  const { transport, config, initialSessionId, autoCreateSession, streaming, audio, debug, className, listClassName, ...slots } = props;
  return (
    <ChatProvider
      transport={transport}
      config={config}
      initialSessionId={initialSessionId}
      autoCreateSession={autoCreateSession}
      streaming={streaming}
      audio={audio}
      debug={debug}
    >
      <ChatRootShell className={className} listClassName={listClassName} slots={slots} />
    </ChatProvider>
  );
}

interface ChatRootShellProps {
  className?: string;
  listClassName?: string;
  slots: Omit<ChatRootProps, 'transport' | 'config' | 'initialSessionId' | 'autoCreateSession' | 'streaming' | 'audio' | 'debug' | 'className' | 'listClassName'>;
}

function ChatRootShell({ className, listClassName, slots }: ChatRootShellProps) {
  const chat = useChatContext();
  const composer = useChatComposer({
    onSubmit: (content, attachments) => chat.sendMessage(content, attachments),
    disabled: chat.isStreaming,
  });
  const containerRef = useRef<HTMLDivElement | null>(null);
  const bottomRef = useRef<HTMLDivElement | null>(null);
  const topRef = useRef<HTMLDivElement | null>(null);

  const scroll = useChatScroll({
    containerRef,
    bottomRef,
    isStreaming: chat.isStreaming,
    messagesCount: chat.messages.length,
  });

  useChatHistory({
    containerRef,
    topSentinelRef: topRef,
    hasMore: chat.hasMore,
    isLoadingMore: chat.isLoadingMore,
    loadMore: chat.loadMore,
  });

  const greeting = chat.config.greeting ?? 'How can I help?';
  const description = chat.config.description;
  const suggestions = chat.config.suggestions;

  const headerNode = slots.renderHeader ? slots.renderHeader(chat) : slots.header;

  const emptyNode = slots.empty
    ?? (slots.renderEmpty
      ? slots.renderEmpty({ setValue: composer.setValue, focus: composer.focus })
      : (
          <EmptyState
            greeting={greeting}
            description={description}
            suggestions={suggestions}
            onPickSuggestion={(prompt) => {
              composer.setValue(prompt);
              composer.focus();
            }}
          />
        ));

  const renderItem = slots.renderMessage
    ?? ((m: ChatMessage) => (
      <MessageBubble
        key={m.id}
        message={m}
        toolCallsProps={slots.toolCallsProps}
        attachmentRenderers={slots.attachmentRenderers}
        onAttachmentOpen={slots.onAttachmentOpen}
        onCopy={() => copy(m.content)}
        onRegenerate={() => void chat.regenerate(m.id)}
        onDelete={() => chat.deleteMessage(m.id)}
      />
    ));

  return (
    <div className={cn('relative flex h-full min-h-0 flex-col overflow-hidden', className)}>
      {slots.banner ?? null}
      {headerNode ?? null}
      <div className="relative flex min-h-0 flex-1 flex-col">
        <ErrorBanner
          error={chat.error}
          onDismiss={chat.error ? () => chat.clearMessages() : undefined}
          onRetry={chat.error ? () => void chat.regenerate() : undefined}
        />
        <MessageList
          ref={containerRef}
          topSentinelRef={topRef}
          bottomRef={bottomRef}
          renderItem={renderItem}
          renderEmpty={() => <>{emptyNode}</>}
          className={listClassName}
        />
        <div className="pointer-events-none absolute inset-x-0 bottom-2 flex justify-center">
          {slots.jumpToLatest ?? (
            <JumpToLatest
              visible={!scroll.isAtBottom}
              unreadCount={scroll.unreadCount}
              onClick={() => scroll.scrollToBottom(true)}
            />
          )}
        </div>
      </div>
      {!slots.hideComposer && (
        <Composer
          composer={composer}
          placeholder={chat.config.placeholder}
          showAttachmentButton={slots.showAttachmentButton}
          onPickFiles={slots.onPickFiles}
          toolbarStart={slots.composerToolbarStart}
          toolbarEnd={slots.composerToolbarEnd}
          attachmentTray={slots.composerAttachmentTray}
          size={slots.composerSize}
        />
      )}
      {slots.footer ?? null}
    </div>
  );
}

function copy(text: string) {
  if (typeof navigator !== 'undefined' && navigator.clipboard) {
    void navigator.clipboard.writeText(text);
  }
}

// re-export for convenience: composer hook return is a common slot dependency
export type { UseChatComposerReturn };
