import { useMemo, useState } from 'react';
import { Settings, Sparkles, Volume2, VolumeX } from 'lucide-react';
import { defineStory, useBoolean, useSelect } from '@djangocfg/playground';

import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
} from '@djangocfg/ui-core/components';

import { LazyJsonTree } from '../JsonTree/lazy';
import { LazyPlayer as LazyAudioPlayer } from '../AudioPlayer/lazy';
import { LazyImageViewer } from '../ImageViewer/lazy';

import { ChatRoot } from './components/ChatRoot';
import { MessageBubble } from './components/MessageBubble';
import { MessageList } from './components/MessageList';
import { Composer } from './components/Composer';
import { Sources } from './components/Sources';
import { ToolCalls } from './components/ToolCalls';
import { Attachments } from './components/Attachments';
import { EmptyState } from './components/EmptyState';
import { ErrorBanner } from './components/ErrorBanner';
import { JumpToLatest } from './components/JumpToLatest';
import { StreamingIndicator } from './components/StreamingIndicator';

import { ChatProvider, useChatContext } from './context';
import { useChatComposer } from './hooks/useChatComposer';
import { useChatLightbox } from './hooks/useChatLightbox';
import { createMockTransport } from './core/transport/mock';
import { dispatchToolPayload, isLatLng, isPlainObject } from './core/payload-dispatch';
import { collectImageAttachments } from './utils/collectImageAttachments';
import type { ChatAttachment, ChatMessage, ChatStreamEvent } from './types';
import type { ChatAudioSounds } from './core/audio/types';

const CHAT_SOUNDS: ChatAudioSounds = {
  messageSent: '/audio/chat/sent.mp3',
  messageReceived: '/audio/chat/received.mp3',
  streamStart: '/audio/chat/start.mp3',
  error: '/audio/chat/error.mp3',
  mention: '/audio/chat/mention.mp3',
  notification: '/audio/chat/notification.mp3',
};

export default defineStory({
  title: 'Tools/Chat',
  component: ChatRoot,
  description:
    'Decomposed, transport-agnostic chat. Markdown reuse, sticky scroll, streaming, tools, attachments.',
});

// ---------------------------------------------------------------------------
// Frame helper — fixed-size container so the chat has somewhere to live.
// ---------------------------------------------------------------------------

function Frame({ children, h = 560, w = 480 }: { children: React.ReactNode; h?: number; w?: number }) {
  return (
    <div
      className="overflow-hidden rounded-lg border border-border bg-background shadow-sm"
      style={{ height: h, width: w }}
    >
      {children}
    </div>
  );
}

// ---------------------------------------------------------------------------
// 1) Default — full ChatRoot with mock transport
// ---------------------------------------------------------------------------

export const Default = () => {
  const transport = useMemo(
    () =>
      createMockTransport({
        replies: [
          'Hello! I am a mock assistant. Ask me anything — I will reply with scripted text.',
          '**Sure!** Here is a list:\n\n- alpha\n- beta\n- gamma\n\nAnd a snippet:\n\n```ts\nconst answer = 42;\n```',
          'Streaming chunked text works too. Each token arrives separately and the UI sticks to the bottom while the stream is in flight.',
        ],
        latencyMs: 35,
      }),
    [],
  );

  return (
    <Frame>
      <ChatRoot
        transport={transport}
        config={{
          greeting: 'Hi there 👋',
          description: 'Try sending a message — replies are scripted by the mock transport.',
          placeholder: 'Ask anything…',
          suggestions: [
            { label: 'Show me a markdown reply', prompt: 'Give me a markdown sample' },
            { label: 'Explain streaming', prompt: 'How does streaming work here?' },
          ],
        }}
      />
    </Frame>
  );
};

// ---------------------------------------------------------------------------
// 1b) ComposerSizes — sm / md / lg side-by-side
// ---------------------------------------------------------------------------

export const ComposerSizes = () => {
  const make = () =>
    createMockTransport({
      replies: ['I scale with the composer size — try `lg` on a primary surface.'],
      latencyMs: 25,
    });
  const cells: Array<{ size: 'sm' | 'md' | 'lg'; label: string }> = [
    { size: 'sm', label: 'sm — 32px (compact)' },
    { size: 'md', label: 'md — 36px (default)' },
    { size: 'lg', label: 'lg — 48px (primary surface)' },
  ];
  return (
    <div className="grid h-[640px] grid-cols-3 gap-3">
      {cells.map(({ size, label }) => (
        <Frame key={size} h={620}>
          <ChatRoot
            transport={make()}
            config={{
              greeting: label,
              placeholder: 'Type a message…',
            }}
            composerSize={size}
          />
        </Frame>
      ))}
    </div>
  );
};

// ---------------------------------------------------------------------------
// 2) WithToolCalls — scripted tool invocations
// ---------------------------------------------------------------------------

export const WithToolCalls = () => {
  const transport = useMemo(() => {
    const sequence: ChatStreamEvent[] = [
      { type: 'chunk', delta: 'Let me check the docs for you.\n\n' },
      {
        type: 'tool_call_start',
        toolId: 't1',
        name: 'search_docs',
        input: { query: 'streaming tokens' },
      },
      { type: 'tool_call_delta', toolId: 't1', delta: 'Reading index…\n' },
      { type: 'tool_call_delta', toolId: 't1', delta: 'Matched 3 chunks.\n' },
      {
        type: 'tool_call_end',
        toolId: 't1',
        output: { hits: 3, top: 'streaming.md' },
        status: 'success',
      },
      { type: 'chunk', delta: 'Found 3 relevant chunks. Here is a summary…' },
      {
        type: 'message_end',
        sources: [
          { title: 'streaming.md', url: 'https://example.com/streaming', snippet: 'How streaming works' },
          { title: 'sse.md', url: 'https://example.com/sse', snippet: 'Server-sent events' },
        ],
      },
    ];
    return createMockTransport({ replies: [sequence], latencyMs: 60 });
  }, []);

  return (
    <Frame>
      <ChatRoot
        transport={transport}
        config={{ greeting: 'Tool calls demo', placeholder: 'Ask "search the docs"…' }}
      />
    </Frame>
  );
};

// ---------------------------------------------------------------------------
// 3) Composition — bring your own layout, just hooks + parts
// ---------------------------------------------------------------------------

export const Composition = () => {
  const transport = useMemo(
    () => createMockTransport({ replies: ['Composed shell — full control over layout.'], latencyMs: 30 }),
    [],
  );
  return (
    <Frame h={520}>
      <ChatProvider transport={transport} config={{ placeholder: 'Type and press Enter…' }}>
        <CustomShell />
      </ChatProvider>
    </Frame>
  );
};

function CustomShell() {
  const chat = useChatContext();
  const composer = useChatComposer({
    onSubmit: (c, a) => chat.sendMessage(c, a),
    disabled: chat.isStreaming,
  });
  return (
    <div className="flex h-full flex-col">
      <header className="flex items-center justify-between border-b border-border bg-muted/40 px-3 py-2">
        <div className="flex items-center gap-2 text-xs">
          <span className="font-semibold">Custom shell</span>
          {chat.isStreaming ? <StreamingIndicator label="thinking…" /> : null}
        </div>
        <button
          type="button"
          onClick={() => void chat.newSession()}
          className="rounded border border-border bg-background px-2 py-0.5 text-[11px] hover:bg-accent"
        >
          New chat
        </button>
      </header>
      <ErrorBanner error={chat.error} onDismiss={() => chat.clearMessages()} />
      <MessageList
        renderEmpty={() => <EmptyState greeting="Bring your own layout" />}
      />
      <Composer composer={composer} />
    </div>
  );
}

// ---------------------------------------------------------------------------
// 4) Bubbles — visual matrix of message states (no transport)
// ---------------------------------------------------------------------------

const sampleMessages: ChatMessage[] = [
  {
    id: 'u1',
    role: 'user',
    content: 'Quick user message',
    createdAt: Date.now() - 60_000,
  },
  {
    id: 'a1',
    role: 'assistant',
    content:
      '**Markdown** is supported.\n\n- bullet one\n- bullet two\n\n```js\nconsole.log("hi")\n```',
    createdAt: Date.now() - 50_000,
    sources: [
      { title: 'docs.md', url: 'https://example.com/docs' },
      { title: 'guide.md', url: 'https://example.com/guide' },
    ],
  },
  {
    id: 'a2',
    role: 'assistant',
    content: 'Streaming…',
    isStreaming: true,
    toolActivity: 'Searching the knowledge base…',
    createdAt: Date.now() - 30_000,
  },
  {
    id: 'a3',
    role: 'assistant',
    content: '',
    isError: true,
    createdAt: Date.now() - 20_000,
  },
  {
    id: 'a4',
    role: 'assistant',
    content: 'Here is the analysis.',
    createdAt: Date.now() - 10_000,
    toolCalls: [
      {
        id: 't1',
        name: 'fetch',
        input: { url: 'https://api.example.com' },
        output: { status: 200 },
        status: 'success',
        startedAt: Date.now() - 12_000,
        endedAt: Date.now() - 11_000,
      },
      {
        id: 't2',
        name: 'parse',
        input: { format: 'json' },
        streamingText: 'parsing…\nstep 1…\nstep 2…',
        status: 'running',
        startedAt: Date.now() - 9_000,
      },
    ],
  },
];

export const Bubbles = () => (
  <Frame h={620}>
    <div className="h-full overflow-y-auto py-2">
      {sampleMessages.map((m) => (
        <MessageBubble key={m.id} message={m} showActions={false} />
      ))}
    </div>
  </Frame>
);

// ---------------------------------------------------------------------------
// 5) Parts — each decomposed component on its own
// ---------------------------------------------------------------------------

export const Parts = () => (
  <div className="flex flex-col gap-6 p-3">
    <section className="space-y-2">
      <h3 className="text-xs font-semibold text-muted-foreground">StreamingIndicator</h3>
      <div className="flex items-center gap-4">
        <StreamingIndicator />
        <StreamingIndicator label="searching…" />
        <StreamingIndicator variant="pulse" label="thinking" />
      </div>
    </section>
    <section className="space-y-2">
      <h3 className="text-xs font-semibold text-muted-foreground">Sources</h3>
      <Sources
        sources={[
          { title: 'guide.md', url: 'https://example.com/guide', snippet: 'How to use the chat' },
          { title: 'api.md', url: 'https://example.com/api' },
          { title: 'faq.md', url: 'https://example.com/faq' },
        ]}
      />
    </section>
    <section className="space-y-2">
      <h3 className="text-xs font-semibold text-muted-foreground">
        ToolCalls (default <code className="font-mono">&lt;pre&gt;</code> renderer)
      </h3>
      <ToolCalls
        defaultExpanded
        calls={[
          {
            id: 'a',
            name: 'search',
            input: { q: 'react' },
            output: { hits: 7 },
            status: 'success',
            startedAt: 0,
            endedAt: 1,
          },
          {
            id: 'b',
            name: 'fetch',
            input: { url: '/api' },
            streamingText: 'connecting…',
            status: 'running',
            startedAt: 0,
          },
          {
            id: 'c',
            name: 'parse',
            input: {},
            output: 'syntax error',
            status: 'error',
            startedAt: 0,
            endedAt: 1,
          },
        ]}
      />
    </section>

    <section className="space-y-2">
      <h3 className="text-xs font-semibold text-muted-foreground">
        ToolCalls + LazyJsonTree payloads (compact mode)
      </h3>
      <ToolCalls
        defaultExpanded
        calls={[
          {
            id: 'd',
            name: 'fetch_user',
            input: { id: 'usr_42', fields: ['email', 'roles', 'created_at'] },
            output: {
              id: 'usr_42',
              email: 'mark@example.com',
              roles: ['admin', 'editor'],
              created_at: '2026-04-01T12:34:56Z',
              metadata: { plan: 'pro', seats: 5, trial: false },
            },
            status: 'success',
            startedAt: 0,
            endedAt: 1,
          },
        ]}
        renderInput={(input) => (
          <LazyJsonTree data={input} mode="compact" />
        )}
        renderOutput={(output) => (
          <LazyJsonTree data={output} mode="compact" />
        )}
      />
    </section>
    <section className="space-y-2">
      <h3 className="text-xs font-semibold text-muted-foreground">Attachments</h3>
      <Attachments
        attachments={[
          {
            id: '1',
            type: 'image',
            url: 'https://images.unsplash.com/photo-1503023345310-bd7c1de61c7d?w=128',
            name: 'photo.jpg',
          },
          {
            id: '2',
            type: 'file',
            url: '#',
            name: 'spec.pdf',
            mimeType: 'application/pdf',
          },
          {
            id: '3',
            type: 'image',
            url: 'https://images.unsplash.com/photo-1494526585095-c41746248156?w=128',
            status: 'uploading',
            progress: 0.55,
          },
        ]}
      />
    </section>
    <section className="space-y-2">
      <h3 className="text-xs font-semibold text-muted-foreground">EmptyState</h3>
      <div className="rounded-lg border border-border bg-card">
        <EmptyState
          greeting="How can I help?"
          description="Pick a starter prompt or just type."
          suggestions={[
            { label: 'Summarise an article', prompt: 'Summarise the latest blog post' },
            { label: 'Generate code', prompt: 'Write a Pydantic model for users' },
          ]}
        />
      </div>
    </section>
    <section className="space-y-2">
      <h3 className="text-xs font-semibold text-muted-foreground">ErrorBanner</h3>
      <ErrorBanner
        error="Something went wrong while contacting the assistant."
        onRetry={() => undefined}
        onDismiss={() => undefined}
      />
    </section>
    <section className="space-y-2">
      <h3 className="text-xs font-semibold text-muted-foreground">JumpToLatest</h3>
      <div className="flex items-center gap-3">
        <JumpToLatest visible unreadCount={0} onClick={() => undefined} />
        <JumpToLatest visible unreadCount={3} onClick={() => undefined} />
      </div>
    </section>
  </div>
);

// ---------------------------------------------------------------------------
// 6) WithSlots — every named slot on ChatRoot
// ---------------------------------------------------------------------------

export const WithSlots = () => {
  const transport = useMemo(
    () =>
      createMockTransport({
        replies: [
          'I will use **the slots above and below**, plus a custom empty state. Try sending a message to see the composer toolbar buttons.',
          'Each slot is optional — omit any of them to fall back to the default.',
        ],
        latencyMs: 30,
      }),
    [],
  );

  return (
    <Frame h={620}>
      <ChatRoot
        transport={transport}
        config={{ placeholder: 'Slots demo…' }}
        banner={
          <div className="border-b border-amber-500/30 bg-amber-500/10 px-3 py-1.5 text-[11px] text-amber-700 dark:text-amber-400">
            Banner slot — sticky note above the conversation
          </div>
        }
        header={
          <header className="flex items-center justify-between border-b border-border bg-muted/30 px-3 py-2">
            <div className="flex items-center gap-2 text-xs">
              <Sparkles aria-hidden className="size-3.5 text-primary" />
              <span className="font-semibold">Custom header slot</span>
            </div>
            <button
              type="button"
              className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
              aria-label="Settings"
            >
              <Settings aria-hidden className="size-3.5" />
            </button>
          </header>
        }
        empty={
          <div className="grid place-items-center px-6 py-16 text-center">
            <Sparkles aria-hidden className="mb-3 size-6 text-primary" />
            <h3 className="text-sm font-semibold">Custom empty slot</h3>
            <p className="mt-1 max-w-sm text-xs text-muted-foreground">
              Replace the default <code className="font-mono">&lt;EmptyState&gt;</code> wholesale.
              Type below to start.
            </p>
          </div>
        }
        composerToolbarStart={
          <button
            type="button"
            aria-label="Slash commands"
            className="grid h-9 w-9 shrink-0 place-items-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground"
          >
            /
          </button>
        }
        composerToolbarEnd={
          <button
            type="button"
            aria-label="Mentions"
            className="grid h-9 w-9 shrink-0 place-items-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground"
          >
            @
          </button>
        }
        footer={
          <div className="border-t border-border bg-muted/20 px-3 py-1.5 text-center text-[10px] text-muted-foreground">
            Footer slot — model: gpt-4o · responses can be inaccurate
          </div>
        }
      />
    </Frame>
  );
};

// ---------------------------------------------------------------------------
// 7) WithJsonTreePayload — wire LazyJsonTree into ChatRoot.toolCallsProps
// ---------------------------------------------------------------------------

export const WithJsonTreePayload = () => {
  const transport = useMemo(() => {
    const sequence: ChatStreamEvent[] = [
      { type: 'chunk', delta: 'Looking that up for you.\n\n' },
      {
        type: 'tool_call_start',
        toolId: 't1',
        name: 'fetch_user',
        input: { id: 'usr_42', fields: ['email', 'roles', 'metadata'] },
      },
      {
        type: 'tool_call_end',
        toolId: 't1',
        output: {
          id: 'usr_42',
          email: 'mark@example.com',
          roles: ['admin', 'editor'],
          metadata: {
            plan: 'pro',
            seats: 5,
            trial: false,
            features: ['streaming', 'tools', 'attachments'],
          },
        },
        status: 'success',
      },
      { type: 'chunk', delta: 'Here is what I found — open the tool panel to inspect the payload.' },
      { type: 'message_end' },
    ];
    return createMockTransport({ replies: [sequence], latencyMs: 40 });
  }, []);

  return (
    <Frame h={620}>
      <ChatRoot
        transport={transport}
        config={{
          greeting: 'JsonTree payload demo',
          placeholder: 'Ask "fetch user 42"…',
        }}
        toolCallsProps={{
          defaultExpanded: true,
          renderInput: (input) => <LazyJsonTree data={input} mode="compact" />,
          renderOutput: (output) => <LazyJsonTree data={output} mode="compact" />,
        }}
      />
    </Frame>
  );
};

// ---------------------------------------------------------------------------
// 8) WithAudio — chat-event sound triggers
// ---------------------------------------------------------------------------

export const WithAudio = () => {
  const [sentOn] = useBoolean('sent', { defaultValue: true, label: 'Play on sent' });
  const [receivedOn] = useBoolean('received', { defaultValue: true, label: 'Play on received' });
  const [errorOn] = useBoolean('error-toggle', { defaultValue: true, label: 'Play on error' });

  const transport = useMemo(
    () =>
      createMockTransport({
        replies: [
          'Pings! Listen for the sent / received cues. Try sending a couple of messages.',
          'Each event has its own sound — toggle them in the knobs above.',
          'The `error` event uses a longer drumroll so it stands out.',
        ],
        latencyMs: 35,
      }),
    [],
  );

  const sounds: ChatAudioSounds = useMemo(
    () => ({
      messageSent: sentOn ? CHAT_SOUNDS.messageSent : false,
      messageReceived: receivedOn ? CHAT_SOUNDS.messageReceived : false,
      streamStart: CHAT_SOUNDS.streamStart,
      error: errorOn ? CHAT_SOUNDS.error : false,
      notification: CHAT_SOUNDS.notification,
    }),
    [sentOn, receivedOn, errorOn],
  );

  return (
    <Frame>
      <ChatRoot
        transport={transport}
        config={{
          greeting: 'Audio triggers',
          description:
            'First click anywhere inside the chat unlocks audio (Safari/iOS quirk). After that, sounds play on send / receive.',
          placeholder: 'Send a message to hear the cue…',
        }}
        audio={{
          sounds,
          // Default off-when-hidden + reduced-motion / reduced-data respect.
        }}
        header={
          <header className="flex items-center justify-between border-b border-border bg-muted/30 px-3 py-1.5 text-[11px]">
            <AudioStatusBadge />
            <span className="text-muted-foreground">/audio/chat/*.mp3</span>
          </header>
        }
      />
    </Frame>
  );
};

function AudioStatusBadge() {
  const ctx = useChatContext();
  return (
    <button
      type="button"
      onClick={() => ctx.audio.setMuted(!ctx.audio.muted)}
      className="inline-flex items-center gap-1.5 rounded px-1.5 py-0.5 hover:bg-accent"
    >
      {ctx.audio.muted ? (
        <VolumeX aria-hidden className="size-3.5 text-muted-foreground" />
      ) : (
        <Volume2 aria-hidden className="size-3.5 text-primary" />
      )}
      <span className="font-mono">
        {ctx.audio.isUnlocked ? 'unlocked' : 'locked'} · {ctx.audio.muted ? 'muted' : `${Math.round(ctx.audio.volume * 100)}%`}
      </span>
    </button>
  );
}

// ---------------------------------------------------------------------------
// 9) WithAudioAttachment — registry mounts <LazyAudioPlayer> for audio msgs
// ---------------------------------------------------------------------------

const audioAttachmentMessages: ChatMessage[] = [
  {
    id: 'u1',
    role: 'user',
    content: "Here's the voice memo I recorded.",
    createdAt: Date.now() - 60_000,
    attachments: [
      {
        id: 'voice-1',
        type: 'audio',
        url: '/audio/voice.mp3',
        name: 'voice memo.mp3',
        mimeType: 'audio/mpeg',
      },
    ],
  },
  {
    id: 'a1',
    role: 'assistant',
    content: 'Got it — playing back inline. Click play to listen.',
    createdAt: Date.now() - 50_000,
    attachments: [
      {
        id: 'reply-1',
        type: 'audio',
        url: '/audio/short.mp3',
        name: 'reply.mp3',
        mimeType: 'audio/mpeg',
      },
    ],
  },
];

export const WithAudioAttachment = () => (
  <Frame h={620}>
    <div className="h-full overflow-y-auto py-2">
      {audioAttachmentMessages.map((m) => (
        <MessageBubble
          key={m.id}
          message={m}
          showActions={false}
          attachmentRenderers={{
            audio: ({ attachment }) => (
              <div className="my-1 w-full max-w-md">
                <LazyAudioPlayer
                  src={attachment.url}
                  title={attachment.name ?? 'audio'}
                  variant="compact"
                />
              </div>
            ),
          }}
        />
      ))}
    </div>
  </Frame>
);

// ---------------------------------------------------------------------------
// 10) WithImageLightbox — useChatLightbox + LazyImageViewer in a Dialog
// ---------------------------------------------------------------------------

const imageMessages: ChatMessage[] = [
  {
    id: 'u1',
    role: 'user',
    content: 'Found these in the assets folder.',
    createdAt: Date.now() - 60_000,
    attachments: [
      {
        id: 'img-1',
        type: 'image',
        url: 'https://images.unsplash.com/photo-1503023345310-bd7c1de61c7d?w=800',
        thumbnailUrl: 'https://images.unsplash.com/photo-1503023345310-bd7c1de61c7d?w=128',
        name: 'cliffside.jpg',
      },
      {
        id: 'img-2',
        type: 'image',
        url: 'https://images.unsplash.com/photo-1494526585095-c41746248156?w=800',
        thumbnailUrl: 'https://images.unsplash.com/photo-1494526585095-c41746248156?w=128',
        name: 'cabin.jpg',
      },
    ],
  },
  {
    id: 'a1',
    role: 'assistant',
    content: 'Here is one more from the same set.',
    createdAt: Date.now() - 50_000,
    attachments: [
      {
        id: 'img-3',
        type: 'image',
        url: 'https://images.unsplash.com/photo-1518791841217-8f162f1e1131?w=800',
        thumbnailUrl: 'https://images.unsplash.com/photo-1518791841217-8f162f1e1131?w=128',
        name: 'cat.jpg',
      },
    ],
  },
];

export const WithImageLightbox = () => {
  const lightbox = useChatLightbox();
  const gallery = useMemo(() => collectImageAttachments(imageMessages), []);

  return (
    <Frame h={560}>
      <div className="h-full overflow-y-auto py-2">
        {imageMessages.map((m) => (
          <MessageBubble
            key={m.id}
            message={m}
            showActions={false}
            onAttachmentOpen={(att) => {
              if (att.type === 'image') lightbox.open(att, gallery);
            }}
          />
        ))}
      </div>
      <Dialog open={lightbox.state !== null} onOpenChange={(open) => !open && lightbox.close()}>
        <DialogContent className="max-w-5xl">
          <DialogHeader>
            <DialogTitle>Image preview</DialogTitle>
          </DialogHeader>
          {lightbox.state ? (
            <div className="h-[60vh]">
              <LazyImageViewer
                images={lightbox.state.gallery.map((a: ChatAttachment) => ({
                  file: { name: a.name ?? a.id, path: a.id },
                  src: a.url,
                }))}
                initialIndex={lightbox.state.index}
                inDialog
              />
            </div>
          ) : null}
        </DialogContent>
      </Dialog>
    </Frame>
  );
};

// ---------------------------------------------------------------------------
// 11) WithMapPayload — dispatchToolPayload + JsonTree fallback
// ---------------------------------------------------------------------------

export const WithMapPayload = () => {
  const transport = useMemo(() => {
    const sequence: ChatStreamEvent[] = [
      { type: 'chunk', delta: 'Looking up the location.\n\n' },
      {
        type: 'tool_call_start',
        toolId: 't1',
        name: 'geocode',
        input: { query: 'Bali, Indonesia' },
      },
      {
        type: 'tool_call_end',
        toolId: 't1',
        output: { lat: -8.4095, lng: 115.1889, label: 'Bali, Indonesia' },
        status: 'success',
      },
      { type: 'chunk', delta: 'Here are the coordinates — opening the panel renders a tree by default. Map embed wiring is host-side via `dispatchToolPayload`.' },
      { type: 'message_end' },
    ];
    return createMockTransport({ replies: [sequence], latencyMs: 30 });
  }, []);

  const renderPayload = useMemo(
    () =>
      dispatchToolPayload(
        [
          {
            // Single point → host would mount a Map preview here.
            match: (v) => isLatLng(v),
            render: (v) => {
              const point = v as { lat: number; lng: number; label?: string };
              return (
                <div className="space-y-1">
                  <div className="rounded-md border border-border bg-emerald-500/5 px-2 py-1.5 text-[11px]">
                    <strong className="font-mono">{point.lat.toFixed(4)}, {point.lng.toFixed(4)}</strong>
                    {point.label ? <span className="ml-2 text-muted-foreground">— {point.label}</span> : null}
                    <span className="ml-2 text-emerald-500">→ would mount &lt;LazyMap&gt;</span>
                  </div>
                  <LazyJsonTree data={v} mode="inline" />
                </div>
              );
            },
          },
          {
            // Other objects → JsonTree.
            match: (v) => isPlainObject(v),
            render: (v) => <LazyJsonTree data={v} mode="compact" />,
          },
        ],
        (v) => <pre className="text-[11px]">{String(v)}</pre>,
      ),
    [],
  );

  return (
    <Frame>
      <ChatRoot
        transport={transport}
        config={{
          greeting: 'Tool payload dispatcher',
          placeholder: 'Ask "where is Bali"…',
        }}
        toolCallsProps={{ defaultExpanded: true, renderPayload }}
      />
    </Frame>
  );
};

// ---------------------------------------------------------------------------
// 12) WithPersonas — user / assistant identity (avatar, name, multi-user)
// ---------------------------------------------------------------------------

const MARK_PERSONA = {
  name: 'Mark',
  avatarUrl: 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=128',
  description: 'Senior engineer',
};
const CLAUDE_PERSONA = {
  name: 'Claude',
  avatarUrl: 'https://avatars.githubusercontent.com/u/76263028?s=128&v=4',
  description: 'Anthropic · Opus 4.7',
  model: 'claude-opus-4-7',
};

export const WithPersonas = () => {
  const transport = useMemo(
    () =>
      createMockTransport({
        replies: [
          'Hi Mark — happy to help. What are we working on?',
          'Got it. Streaming a response now…',
        ],
        latencyMs: 30,
      }),
    [],
  );
  return (
    <Frame>
      <ChatRoot
        transport={transport}
        config={{
          greeting: 'Hi Mark 👋',
          placeholder: 'Ask Claude anything…',
          user: MARK_PERSONA,
          assistant: CLAUDE_PERSONA,
        }}
      />
    </Frame>
  );
};

// Multi-user / multi-bot — per-message `sender` overrides config defaults.
const MULTI_USER_MESSAGES: ChatMessage[] = [
  {
    id: 'm1',
    role: 'user',
    content: '@claude can you summarise the spec?',
    createdAt: Date.now() - 90_000,
    sender: { name: 'Mark', avatarUrl: 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=128' },
  },
  {
    id: 'm2',
    role: 'assistant',
    content: "Sure — pulling the latest version from the docs index now.",
    createdAt: Date.now() - 80_000,
    sender: { ...CLAUDE_PERSONA, initials: 'CL' },
  },
  {
    id: 'm3',
    role: 'user',
    content: 'Make sure to flag the auth migration section.',
    createdAt: Date.now() - 60_000,
    sender: { name: 'Anna', avatarUrl: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=128' },
  },
  {
    id: 'm4',
    role: 'assistant',
    content: '👍 Flagged. Section 4.2 — needs DBA review before Friday.',
    createdAt: Date.now() - 30_000,
    sender: { ...CLAUDE_PERSONA, initials: 'CL' },
  },
  {
    id: 'm5',
    role: 'user',
    content: 'Thanks both — I will sync with Anna tomorrow.',
    createdAt: Date.now() - 10_000,
    sender: { name: 'Lukas', initials: 'LK' },
  },
];

export const MultiUser = () => (
  <Frame h={620}>
    <div className="h-full overflow-y-auto py-2">
      {MULTI_USER_MESSAGES.map((m) => (
        <MessageBubble key={m.id} message={m} showActions={false} showTimestamp />
      ))}
    </div>
  </Frame>
);

// ---------------------------------------------------------------------------
// 13) WithHideComposer — agent-pause pattern (approval gate, human-in-the-loop)
// ---------------------------------------------------------------------------

export const WithHideComposer = () => {
  const [paused, setPaused] = useState(false);

  const transport = useMemo(
    () =>
      createMockTransport({
        replies: [
          'Sure — I will send that email. Waiting for your approval before proceeding.',
        ],
        latencyMs: 30,
      }),
    [],
  );

  return (
    <Frame h={480}>
      <ChatRoot
        transport={transport}
        config={{
          greeting: 'Agent-pause demo',
          placeholder: 'Ask to do something requiring approval…',
        }}
        hideComposer={paused}
        footer={
          paused ? (
            <div className="flex flex-col gap-2 border-t bg-background px-3 py-2">
              <div className="rounded-lg border border-amber-200 bg-amber-50 dark:border-amber-800 dark:bg-amber-950/40 p-3 text-sm">
                <div className="mb-1 font-medium text-amber-900 dark:text-amber-200">
                  Approval required
                  <span className="ml-2 font-mono text-xs text-amber-700 dark:text-amber-400">send_email</span>
                </div>
                <pre className="mb-2 rounded bg-muted px-2 py-1.5 text-[11px] text-muted-foreground">
                  {`{ "to": "mark@example.com", "subject": "Hello" }`}
                </pre>
                <div className="flex gap-2">
                  <button
                    type="button"
                    onClick={() => setPaused(false)}
                    className="flex-1 rounded bg-amber-600 hover:bg-amber-700 px-3 py-1.5 text-xs font-medium text-white transition-colors"
                  >
                    Approve
                  </button>
                  <button
                    type="button"
                    onClick={() => setPaused(false)}
                    className="flex-1 rounded border border-amber-300 hover:bg-amber-100 px-3 py-1.5 text-xs font-medium text-amber-800 transition-colors"
                  >
                    Deny
                  </button>
                </div>
              </div>
            </div>
          ) : (
            <div className="border-t border-border bg-muted/20 px-3 py-1.5 text-center">
              <button
                type="button"
                onClick={() => setPaused(true)}
                className="text-[11px] text-muted-foreground hover:text-foreground underline"
              >
                Simulate approval gate
              </button>
            </div>
          )
        }
      />
    </Frame>
  );
};

// ---------------------------------------------------------------------------
// 14) Playground — knobs
// ---------------------------------------------------------------------------

export const Playground = () => {
  const [latencyStr] = useSelect('latency', {
    options: ['10', '35', '80', '200'] as const,
    defaultValue: '35',
    label: 'Latency (ms/chunk)',
  });
  const latency = Number(latencyStr);
  const [streaming] = useBoolean('streaming', {
    defaultValue: true,
    label: 'Streaming',
  });
  const [showSuggestions] = useBoolean('suggestions', {
    defaultValue: true,
    label: 'Show suggestions',
  });
  const [seed, setSeed] = useState(0);

  const transport = useMemo(
    () =>
      createMockTransport({
        latencyMs: latency,
        replies: [
          'Token by token, this reply streams in. Try interrupting with the Stop button mid-stream.',
          'Here is a longer reply with **markdown** including a code block:\n\n```ts\nfunction add(a: number, b: number) {\n  return a + b;\n}\n```\n\nAnd a list:\n- one\n- two\n- three',
          'Cancel mid-stream to keep partial text with a [cancelled] marker.',
        ],
      }),
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [latency, seed],
  );

  return (
    <div className="flex flex-col gap-2">
      <button
        type="button"
        className="self-start rounded border border-border bg-background px-2 py-0.5 text-xs hover:bg-accent"
        onClick={() => setSeed((n) => n + 1)}
      >
        Reset session
      </button>
      <Frame h={580}>
        <ChatRoot
          transport={transport}
          streaming={streaming}
          config={{
            greeting: 'Playground',
            description: 'Tweak knobs above to change behavior.',
            placeholder: 'Type a message…',
            suggestions: showSuggestions
              ? [
                  { label: 'Markdown sample', prompt: 'Show me markdown' },
                  { label: 'Long reply', prompt: 'Give me a long answer' },
                ]
              : undefined,
          }}
        />
      </Frame>
    </div>
  );
};
