import type { Meta, StoryFn } from '@storybook/react'
import classNames from 'classnames'
import React from 'react'

import {
  currentUserArgType,
  StoryUser,
  storyUsers,
} from '../../stories/decorators/storyUser'
import { Avatar } from '../Avatar'
import MessageAttachment, {
  type BubbleGroupPosition,
} from '../MessageAttachment'

/* ──────────────────────────────────────────────────────────────────
 * Lightweight conversation mock used only by these stories.
 *
 * The production `CustomMessage` flow needs the full stream-chat-react
 * machinery (Channel + MessageList + the `Attachment` override pipe).
 * That's overkill for a visual showcase, so these stories render a
 * bare conversation timeline that mimics the same layout primitives:
 *
 *   - Sender (current user) bubbles align right, no avatar.
 *   - Recipient bubbles align left with a 28px circle avatar.
 *   - Adjacent bubbles from the same user gap by 2px (matches the
 *     `.str-chat__message-bubble-wrapper { gap: 2px }` rule in
 *     `styles.css`), separate-author boundaries get more breathing
 *     room.
 *   - Text bubbles inherit the same `MessageAttachment.Bubble` chrome
 *     (background, padding, radius, font) so attachments and text sit
 *     consistently inside the same conversation column.
 * ────────────────────────────────────────────────────────────────── */

type ChatRole = 'sender' | 'receiver'

interface ChatNode {
  id: string
  role: ChatRole
  /** Either a string (rendered as a text bubble) or a custom React node
   *  (e.g. `<MessageAttachment.Image.Sent ... />`). */
  content: React.ReactNode
}

const TEXT_BG_BY_ROLE: Record<ChatRole, string> = {
  sender: 'bg-[#1e2330] text-white',
  receiver: 'bg-[#f2f1ef] text-[#000000]',
}

const TextBubble: React.FC<{ role: ChatRole; children: React.ReactNode }> = ({
  role,
  children,
}) => (
  <div
    className={classNames(
      // Same 18px radius + 8px / 16px padding as
      // `MessageAttachment.Bubble`, so text bubbles share the same
      // chrome as attachment bubbles.
      'max-w-[280px] whitespace-pre-wrap break-words rounded-[18px] px-4 py-2',
      TEXT_BG_BY_ROLE[role]
    )}
  >
    {children}
  </div>
)

const ChatRow: React.FC<{
  role: ChatRole
  showAvatar: boolean
  user: StoryUser
  children: React.ReactNode
}> = ({ role, showAvatar, user, children }) => (
  <div
    className={classNames(
      'flex items-end gap-2',
      role === 'sender' ? 'justify-end' : 'justify-start'
    )}
  >
    {role === 'receiver' ? (
      <div className="w-7 shrink-0">
        {showAvatar ? (
          <Avatar
            id={user.id}
            name={user.name}
            image={user.image}
            size={28}
            shape="circle"
          />
        ) : null}
      </div>
    ) : null}
    <div
      className={role === 'sender' ? 'flex justify-end' : 'flex justify-start'}
    >
      {children}
    </div>
  </div>
)

const ChatMock: React.FC<{
  currentUser: StoryUser
  receiver: StoryUser
  messages: ChatNode[]
}> = ({ currentUser, receiver, messages }) => {
  // Render an avatar on the *last* message of each receiver run — same
  // grouping rule the real chat uses so consecutive bubbles from one
  // sender share avatar real estate.
  const isLastInRun = (index: number): boolean => {
    const next = messages[index + 1]
    return !next || next.role !== messages[index].role
  }

  return (
    <div className="mx-auto flex max-w-2xl flex-col gap-1 p-12">
      {messages.map((msg, index) => {
        const prev = messages[index - 1]
        const newAuthorBoundary = !prev || prev.role !== msg.role
        return (
          <div
            key={msg.id}
            className={newAuthorBoundary && index > 0 ? 'mt-3' : undefined}
          >
            <ChatRow
              role={msg.role}
              user={msg.role === 'sender' ? currentUser : receiver}
              showAvatar={msg.role === 'receiver' && isLastInRun(index)}
            >
              {typeof msg.content === 'string' ? (
                <TextBubble role={msg.role}>{msg.content}</TextBubble>
              ) : (
                msg.content
              )}
            </ChatRow>
          </div>
        )
      })}
    </div>
  )
}

/* ──────────────────────────────────────────────────────────────────
 * Multi-attachment message wrapper.
 *
 * Real chat UIs let one logical "message" carry several attachments
 * of different kinds — e.g. a voice memo + a PDF, or two photos + an
 * audio note. Each attachment renders as its own `MessageAttachment.*`
 * bubble, but the whole set should read as a single visual cluster
 * from the same author. To do that we:
 *
 *   - Stack the bubbles vertically with `gap-[2px]` (the same gap
 *     `.str-chat__message-bubble-wrapper` uses between grouped text
 *     bubbles in `styles.css`).
 *   - Inject `groupPosition='first' | 'middle' | 'end'` on each child
 *     so the corners flatten on the edges that face siblings inside
 *     the cluster — same merging rule the `Bubble` component already
 *     applies to consecutive text messages.
 *   - Right-align for sender clusters / left-align for receiver
 *     clusters so the run anchors to the correct chat edge.
 *
 * The wrapper is intentionally local to this stories file: the real
 * `CustomMessage` integration derives `groupPosition` from
 * stream-chat-react's `firstOfGroup` / `endOfGroup` flags via the
 * `bubbleGroupPositionFromStream` helper instead. This wrapper just
 * gives the story author a clean way to express "one message, many
 * attachments" without wiring up the full message-context plumbing.
 * ────────────────────────────────────────────────────────────────── */

interface MultiAttachmentMessageProps {
  align: 'sender' | 'receiver'
  children: React.ReactNode
}

const MultiAttachmentMessage: React.FC<MultiAttachmentMessageProps> = ({
  align,
  children,
}) => {
  const items = React.Children.toArray(children).filter(
    React.isValidElement
  ) as React.ReactElement<{ groupPosition?: BubbleGroupPosition }>[]
  const total = items.length

  return (
    <div
      className={classNames(
        'flex flex-col gap-[2px]',
        align === 'sender' ? 'items-end' : 'items-start'
      )}
    >
      {items.map((child, index) => {
        const groupPosition: BubbleGroupPosition =
          total === 1
            ? 'single'
            : index === 0
              ? 'first'
              : index === total - 1
                ? 'end'
                : 'middle'
        return React.cloneElement(child, {
          key: child.key ?? index,
          groupPosition,
        })
      })}
    </div>
  )
}

const meta: Meta = {
  title: 'CustomMessage',
  argTypes: currentUserArgType,
  args: { currentUser: storyUsers.creator },
  parameters: {
    layout: 'fullscreen',
    // The render functions build a `messages` array containing JSX
    // attachment elements (with live React fiber `_owner` refs once
    // mounted) and hand it to `<ChatMock messages={…}>`. Storybook's
    // default `dynamic` source extractor recurses through that prop
    // via `react-element-to-jsx-string` → `prettyPrint`, walks each
    // element's fiber graph, and blows the call stack. Pin source
    // generation to the static story code instead.
    docs: { source: { type: 'code' } },
  },
}
export default meta

interface ConversationStoryArgs {
  currentUser: StoryUser
}

// Single-attachment-type conversations (`ConversationWithImages`,
// `ConversationWithVideos`, etc.) are useful for design review but the
// per-attachment-type RENDERING is already covered by the dedicated
// `MessageAttachment/{Image,Video,Audio,Pdf,File}` stories. The
// "mixed" conversations below exercise composition that the per-type
// stories don't, so those keep their snapshots.
const skipInChromatic = { chromatic: { disableSnapshot: true } }

const HERO_PHOTO = 'https://picsum.photos/seed/portrait/720/720'
const STUDIO_PHOTOS = [
  { src: 'https://picsum.photos/seed/studio-1/720/720', alt: 'Studio shot 1' },
  { src: 'https://picsum.photos/seed/studio-2/720/720', alt: 'Studio shot 2' },
  { src: 'https://picsum.photos/seed/studio-3/720/720', alt: 'Studio shot 3' },
]
const VIDEO_SRC = '/video-source.mp4'
const VIDEO_POSTER = '/video-thumbnail.jpg'
const AUDIO_SRC =
  'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3'
const PDF_SRC = 'https://www.w3.org/WAI/WCAG21/wcag21.pdf'

/**
 * Image attachments inside a conversation — the receiver shares a
 * single hero shot (with caption) and the sender replies with a
 * stacked album. Click any bubble to open the lightbox viewer.
 */
export const ConversationWithImages: StoryFn<ConversationStoryArgs> = ({
  currentUser,
}) => {
  const isSenderTheCreator = currentUser.id === storyUsers.creator.id
  const receiver = isSenderTheCreator ? storyUsers.visitor : storyUsers.creator
  return (
    <ChatMock
      currentUser={currentUser}
      receiver={receiver}
      messages={[
        {
          id: 'msg-1',
          role: 'receiver',
          content: 'Got the new headshots back — thoughts?',
        },
        {
          id: 'msg-2',
          role: 'receiver',
          content: (
            <MessageAttachment.Image.Received
              src={HERO_PHOTO}
              alt="Headshot pick"
              filename="headshot-pick.jpg"
              text="This one's my favorite"
            />
          ),
        },
        {
          id: 'msg-3',
          role: 'sender',
          content: 'Love it! Here are a few more from the studio set.',
        },
        {
          id: 'msg-4',
          role: 'sender',
          content: (
            <MessageAttachment.Image.Sent
              items={STUDIO_PHOTOS}
              filename="studio-album"
            />
          ),
        },
        {
          id: 'msg-5',
          role: 'receiver',
          content: 'These are great — let me pick two for the launch page.',
        },
      ]}
    />
  )
}
ConversationWithImages.parameters = skipInChromatic

/**
 * Video attachments — receiver sends a single clip with a caption,
 * sender replies and shares back a stacked set.
 */
export const ConversationWithVideos: StoryFn<ConversationStoryArgs> = ({
  currentUser,
}) => {
  const isSenderTheCreator = currentUser.id === storyUsers.creator.id
  const receiver = isSenderTheCreator ? storyUsers.visitor : storyUsers.creator
  const stack = Array.from({ length: 3 }, () => ({
    src: VIDEO_SRC,
    poster: VIDEO_POSTER,
    mimeType: 'video/mp4',
  }))
  return (
    <ChatMock
      currentUser={currentUser}
      receiver={receiver}
      messages={[
        {
          id: 'msg-1',
          role: 'receiver',
          content: (
            <MessageAttachment.Image.Received
              src={VIDEO_POSTER}
              alt="Reference still"
            />
          ),
        },
        {
          id: 'msg-2',
          role: 'receiver',
          content: 'And here is the rough cut from yesterday.',
        },
        {
          id: 'msg-3',
          role: 'receiver',
          content: (
            <MessageAttachment.Video.Received
              src={VIDEO_SRC}
              poster={VIDEO_POSTER}
              mimeType="video/mp4"
              filename="rough-cut.mp4"
              text="Open ending — tell me which version lands."
            />
          ),
        },
        {
          id: 'msg-4',
          role: 'sender',
          content: 'On it. Sending three angles back.',
        },
        {
          id: 'msg-5',
          role: 'sender',
          content: (
            <MessageAttachment.Video.Sent items={stack} filename="angles" />
          ),
        },
      ]}
    />
  )
}
ConversationWithVideos.parameters = skipInChromatic

/**
 * Audio attachments — voice memo back-and-forth, plus a stacked
 * studio takes message.
 */
export const ConversationWithAudio: StoryFn<ConversationStoryArgs> = ({
  currentUser,
}) => {
  const isSenderTheCreator = currentUser.id === storyUsers.creator.id
  const receiver = isSenderTheCreator ? storyUsers.visitor : storyUsers.creator
  return (
    <ChatMock
      currentUser={currentUser}
      receiver={receiver}
      messages={[
        {
          id: 'msg-1',
          role: 'receiver',
          content: 'Quick voice memo with the new hook idea ↓',
        },
        {
          id: 'msg-2',
          role: 'receiver',
          content: (
            <MessageAttachment.Audio.Received
              src={AUDIO_SRC}
              mimeType="audio/mpeg"
              filename="hook-idea.mp3"
            />
          ),
        },
        {
          id: 'msg-3',
          role: 'sender',
          content: 'Love that. Here are three takes I cut tonight.',
        },
        {
          id: 'msg-4',
          role: 'sender',
          content: (
            <MessageAttachment.Audio.Sent
              items={[
                {
                  src: AUDIO_SRC,
                  mimeType: 'audio/mpeg',
                  filename: 'Take 1 — full mix.mp3',
                },
                {
                  src: AUDIO_SRC,
                  mimeType: 'audio/mpeg',
                  filename: 'Take 2 — vocals isolated.mp3',
                },
                {
                  src: AUDIO_SRC,
                  mimeType: 'audio/mpeg',
                  filename: 'Take 3 — instrumental only.mp3',
                },
              ]}
              text="Pick whichever lands; happy to keep iterating."
            />
          ),
        },
        {
          id: 'msg-5',
          role: 'receiver',
          content: 'Take 2 is the one. Shipping it.',
        },
      ]}
    />
  )
}
ConversationWithAudio.parameters = skipInChromatic

/**
 * PDF attachments — single document with a caption and a stacked
 * package of related documents.
 */
export const ConversationWithPdfs: StoryFn<ConversationStoryArgs> = ({
  currentUser,
}) => {
  const isSenderTheCreator = currentUser.id === storyUsers.creator.id
  const receiver = isSenderTheCreator ? storyUsers.visitor : storyUsers.creator
  return (
    <ChatMock
      currentUser={currentUser}
      receiver={receiver}
      messages={[
        {
          id: 'msg-1',
          role: 'receiver',
          content: 'Can you send the ESOP summary before the call?',
        },
        {
          id: 'msg-2',
          role: 'sender',
          content: 'Yep, here it is.',
        },
        {
          id: 'msg-3',
          role: 'sender',
          content: (
            <MessageAttachment.Pdf.Sent
              src={PDF_SRC}
              filename="ESOP-summary.pdf"
              fileSize={388_658}
              text="Section 4 has the vesting cliff details."
            />
          ),
        },
        {
          id: 'msg-4',
          role: 'receiver',
          content: 'Got it. Sending back the full package for tonight.',
        },
        {
          id: 'msg-5',
          role: 'receiver',
          content: (
            <MessageAttachment.Pdf.Received
              items={[
                {
                  src: PDF_SRC,
                  filename: 'ESOP-summary.pdf',
                  fileSize: 388_658,
                },
                {
                  src: PDF_SRC,
                  filename: 'Vesting-schedule.pdf',
                  fileSize: 142_336,
                },
                {
                  src: PDF_SRC,
                  filename: 'Cap-table-snapshot.pdf',
                  fileSize: 96_021,
                },
              ]}
              text="Full package — review whatever is most relevant."
            />
          ),
        },
      ]}
    />
  )
}
ConversationWithPdfs.parameters = skipInChromatic

/**
 * Generic file attachments — non-PDF documents with a download
 * affordance on each row. Includes a stacked-files example where
 * three documents share a single bubble with an 8px gap between
 * rows, matching the stacked PDF / Audio treatment.
 */
export const ConversationWithFiles: StoryFn<ConversationStoryArgs> = ({
  currentUser,
}) => {
  const isSenderTheCreator = currentUser.id === storyUsers.creator.id
  const receiver = isSenderTheCreator ? storyUsers.visitor : storyUsers.creator
  return (
    <ChatMock
      currentUser={currentUser}
      receiver={receiver}
      messages={[
        {
          id: 'msg-1',
          role: 'receiver',
          content: 'Do you still have the workout plan archive?',
        },
        {
          id: 'msg-2',
          role: 'sender',
          content:
            'Sending it now — let me know if you need the source files too.',
        },
        {
          id: 'msg-3',
          role: 'sender',
          content: (
            <MessageAttachment.File.Sent
              src="https://cdn.example.com/workout-plan.zip"
              filename="workout-plan.zip"
              mimeType="application/zip"
              fileSize={2_457_600}
            />
          ),
        },
        {
          id: 'msg-4',
          role: 'receiver',
          content: 'Perfect — also got the brand kit handy?',
        },
        {
          id: 'msg-5',
          role: 'sender',
          content: (
            <MessageAttachment.File.Sent
              items={[
                {
                  src: 'https://cdn.example.com/brand-guidelines.zip',
                  filename: 'brand-guidelines.zip',
                  mimeType: 'application/zip',
                  fileSize: 11_500_000,
                },
                {
                  src: 'https://cdn.example.com/Q4-revenue.xlsx',
                  filename: 'Q4-revenue.xlsx',
                  mimeType:
                    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
                  fileSize: 146_432,
                },
                {
                  src: 'https://cdn.example.com/meeting-notes.docx',
                  filename: 'meeting-notes.docx',
                  mimeType:
                    'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
                  fileSize: 28_672,
                },
              ]}
              text="Here's everything in one go."
            />
          ),
        },
        {
          id: 'msg-6',
          role: 'receiver',
          content: 'Amazing, thanks!',
        },
      ]}
    />
  )
}
ConversationWithFiles.parameters = skipInChromatic

/**
 * Mixed conversation — a realistic chat where the same thread carries
 * a text exchange, a single image, a stacked video set, an audio memo,
 * and a PDF handoff. Useful for sanity-checking spacing and bubble
 * rhythm when multiple attachment types coexist.
 */
export const ConversationWithMixedAttachments: StoryFn<
  ConversationStoryArgs
> = ({ currentUser }) => {
  const isSenderTheCreator = currentUser.id === storyUsers.creator.id
  const receiver = isSenderTheCreator ? storyUsers.visitor : storyUsers.creator
  return (
    <ChatMock
      currentUser={currentUser}
      receiver={receiver}
      messages={[
        {
          id: 'msg-1',
          role: 'receiver',
          content: 'Hey! Got a sec to look at the launch package?',
        },
        {
          id: 'msg-2',
          role: 'sender',
          content: 'Yep, send it over.',
        },
        {
          id: 'msg-3',
          role: 'receiver',
          content: 'Here is the hero shot for the page',
        },
        {
          id: 'msg-4',
          role: 'receiver',
          content: (
            <MessageAttachment.Image.Received
              src={HERO_PHOTO}
              alt="Hero shot"
              filename="hero.jpg"
            />
          ),
        },
        {
          id: 'msg-5',
          role: 'receiver',
          content: (
            <MessageAttachment.Video.Received
              src={VIDEO_SRC}
              poster={VIDEO_POSTER}
              mimeType="video/mp4"
              filename="trailer.mp4"
              text="And the rough trailer ↑"
            />
          ),
        },
        {
          id: 'msg-6',
          role: 'sender',
          content: 'Looks great. Quick voice memo with notes ↓',
        },
        {
          id: 'msg-7',
          role: 'sender',
          content: (
            <MessageAttachment.Audio.Sent
              src={AUDIO_SRC}
              mimeType="audio/mpeg"
              filename="notes.mp3"
            />
          ),
        },
        {
          id: 'msg-8',
          role: 'sender',
          content: (
            <MessageAttachment.Pdf.Sent
              src={PDF_SRC}
              filename="launch-checklist.pdf"
              fileSize={142_336}
              text="And the launch checklist for the dry run."
            />
          ),
        },
        {
          id: 'msg-9',
          role: 'receiver',
          content: 'Got everything. Will sync back in the morning.',
        },
      ]}
    />
  )
}

/**
 * Multi-attachment message — audio + PDF.
 *
 * Mirrors the mobile picker UX where the composer can hold a single
 * attachment but the recipient sees them sent together as one
 * cluster. The voice memo sits on top (`groupPosition='first'`, flat
 * bottom-right) and the PDF carries the caption + the cluster's tail
 * corner (`groupPosition='end'`, flat top-right).
 */
export const ConversationWithAudioAndPdfAttached: StoryFn<
  ConversationStoryArgs
> = ({ currentUser }) => {
  const isSenderTheCreator = currentUser.id === storyUsers.creator.id
  const receiver = isSenderTheCreator ? storyUsers.visitor : storyUsers.creator
  return (
    <ChatMock
      currentUser={currentUser}
      receiver={receiver}
      messages={[
        {
          id: 'msg-1',
          role: 'receiver',
          content: 'Did you get a chance to record the walkthrough?',
        },
        {
          id: 'msg-2',
          role: 'sender',
          content: (
            <MultiAttachmentMessage align="sender">
              <MessageAttachment.Audio.Sent
                src={AUDIO_SRC}
                mimeType="audio/mpeg"
                filename="walkthrough.mp3"
              />
              <MessageAttachment.Pdf.Sent
                src={PDF_SRC}
                filename="walkthrough-notes.pdf"
                fileSize={142_336}
                text="Voice memo + the written notes that go with it."
              />
            </MultiAttachmentMessage>
          ),
        },
        {
          id: 'msg-3',
          role: 'receiver',
          content: 'Perfect — listening now.',
        },
      ]}
    />
  )
}

/**
 * Multi-attachment message — two photos + a voice memo.
 *
 * Demonstrates a mixed sensory recap: the receiver shares two
 * reference photos in one stacked image bubble (`groupPosition='first'`)
 * paired with an audio clip explaining what to look at
 * (`groupPosition='end'`, carries the caption). All three pieces feel
 * like one unit instead of three loose bubbles.
 */
export const ConversationWithPhotosAndAudioAttached: StoryFn<
  ConversationStoryArgs
> = ({ currentUser }) => {
  const isSenderTheCreator = currentUser.id === storyUsers.creator.id
  const receiver = isSenderTheCreator ? storyUsers.visitor : storyUsers.creator
  return (
    <ChatMock
      currentUser={currentUser}
      receiver={receiver}
      messages={[
        {
          id: 'msg-1',
          role: 'sender',
          content: 'Anything stand out from the studio set?',
        },
        {
          id: 'msg-2',
          role: 'receiver',
          content: (
            <MultiAttachmentMessage align="receiver">
              <MessageAttachment.Image.Received
                items={STUDIO_PHOTOS.slice(0, 2)}
                filename="studio-picks"
              />
              <MessageAttachment.Audio.Received
                src={AUDIO_SRC}
                mimeType="audio/mpeg"
                filename="reactions.mp3"
                text="Two favorites — voice note has the why."
              />
            </MultiAttachmentMessage>
          ),
        },
        {
          id: 'msg-3',
          role: 'sender',
          content: 'Great picks. Locking those in.',
        },
      ]}
    />
  )
}

/**
 * Multi-attachment message — image + video + PDF (3 different types).
 *
 * Stress-tests the cluster with three different bubble shapes back to
 * back. Image first (cropped poster, flat bottom-right), video middle
 * (poster tile with play badge, flats on top-right + bottom-right),
 * PDF last (compact row with download trailing button + caption,
 * flat top-right). The whole stack reads as one message run.
 */
export const ConversationWithImageVideoPdfAttached: StoryFn<
  ConversationStoryArgs
> = ({ currentUser }) => {
  const isSenderTheCreator = currentUser.id === storyUsers.creator.id
  const receiver = isSenderTheCreator ? storyUsers.visitor : storyUsers.creator
  return (
    <ChatMock
      currentUser={currentUser}
      receiver={receiver}
      messages={[
        {
          id: 'msg-1',
          role: 'receiver',
          content: 'Send everything you have for the launch deck.',
        },
        {
          id: 'msg-2',
          role: 'sender',
          content: (
            <MultiAttachmentMessage align="sender">
              <MessageAttachment.Image.Sent
                src={HERO_PHOTO}
                alt="Hero shot"
                filename="hero.jpg"
              />
              <MessageAttachment.Video.Sent
                src={VIDEO_SRC}
                poster={VIDEO_POSTER}
                mimeType="video/mp4"
                filename="trailer.mp4"
              />
              <MessageAttachment.Pdf.Sent
                src={PDF_SRC}
                filename="launch-deck.pdf"
                fileSize={428_032}
                text="Hero, trailer, deck — everything in one drop."
              />
            </MultiAttachmentMessage>
          ),
        },
        {
          id: 'msg-3',
          role: 'receiver',
          content: 'Got it. Reviewing now.',
        },
      ]}
    />
  )
}
