import { DownloadSimpleIcon } from '@phosphor-icons/react'
import classNames from 'classnames'
import React from 'react'

import Bubble from '../_shared/Bubble'
import CompactDocumentRow from '../_shared/CompactDocumentRow'
import DismissButton from '../_shared/DismissButton'
import { filenameFromUrl } from '../_shared/fileMeta'
import { triggerDownload } from '../_shared/triggerDownload'
import {
  bubbleVariantForState,
  type BubbleVariant,
  type ComposerExtras,
  type FileItem,
  type MessageAttachmentBaseProps,
  type MessageAttachmentState,
} from '../types'

export interface FileAttachmentSharedProps extends MessageAttachmentBaseProps {
  /** Source URL of the file (used as the download target). */
  src?: string
  /** Filename — drives the title + the meta line + the download default name. */
  filename?: string
  fileSize?: number
  /** MIME type — drives the type icon. Defaults to `application/octet-stream`. */
  mimeType?: string
  /**
   * Override displayed title (defaults to `filename`). Useful when a
   * sender renames the attachment before sending.
   */
  title?: string
  /**
   * Stacked files. Takes precedence over `src` when set. Each item
   * renders as its own row inside the bubble; clicking a row downloads
   * that file. Sent + Received only — the composer surface accepts a
   * single attachment at a time.
   */
  items?: FileItem[]
  /**
   * Forwarded to the row trigger. When omitted the click still
   * downloads the file. Supply this for analytics, or return `false`
   * to fully intercept the download (e.g. show a confirmation modal,
   * route to a custom preview, throttle large files). Any other
   * return value — including `void`/`undefined` — lets the built-in
   * download proceed. For stacked attachments the `index` argument
   * identifies the row.
   */
  onClick?: (index: number) => boolean | void
}

const resolveItems = ({
  src,
  filename,
  fileSize,
  mimeType,
  title,
  items,
}: {
  src?: string
  filename?: string
  fileSize?: number
  mimeType?: string
  title?: string
  items?: FileItem[]
}): FileItem[] => {
  if (items && items.length > 0) return items
  if (src) return [{ src, filename, fileSize, mimeType, title }]
  return []
}

interface FileRowButtonProps {
  variant: BubbleVariant
  item: FileItem
  index: number
  onActivate: (index: number) => void
  trailingAction: React.ReactNode
}

/**
 * Single tappable file row — used both for the lone file case and
 * for each child of a stacked attachment. The icon + filename area
 * is the click target (downloads the asset); the trailing slot is
 * decorative on Sent / Received and hosts the dismiss control on
 * Composer.
 */
const FileRowButton: React.FC<FileRowButtonProps> = ({
  variant,
  item,
  index,
  onActivate,
  trailingAction,
}) => {
  const resolvedFilename = item.filename ?? filenameFromUrl(item.src)
  return (
    <CompactDocumentRow
      variant={variant}
      filename={resolvedFilename}
      title={item.title}
      mimeType={item.mimeType ?? 'application/octet-stream'}
      fileSize={item.fileSize}
      onActivate={() => onActivate(index)}
      activateLabel={`Download ${resolvedFilename}`}
      trailingAction={trailingAction}
    />
  )
}

interface InternalFileRowProps extends FileAttachmentSharedProps {
  state: MessageAttachmentState
  onDismiss?: () => void
}

const FileAttachmentRow: React.FC<InternalFileRowProps> = ({
  state,
  src,
  filename,
  fileSize,
  mimeType,
  title,
  items,
  text,
  groupPosition,
  onClick,
  onDismiss,
}) => {
  const variant = bubbleVariantForState(state)
  const showDismiss = state === 'composer' && !!onDismiss
  const resolvedItems = resolveItems({
    src,
    filename,
    fileSize,
    mimeType,
    title,
    items,
  })

  // `useCallback` would be pointless here — `resolvedItems` is rebuilt
  // every render, so memoizing on it produces a fresh function every
  // render anyway. Keep this as a plain function.
  const handleActivate = (index: number) => {
    // `onClick` returning `false` cancels the built-in download so
    // consumers can route the click to a confirmation modal, custom
    // preview, etc. Any other return value (including void) lets the
    // default download proceed.
    if (onClick?.(index) === false) return
    const item = resolvedItems[index]
    if (!item) return
    const resolvedFilename = item.filename ?? filenameFromUrl(item.src)
    void triggerDownload(item.src, resolvedFilename)
  }

  if (resolvedItems.length === 0) {
    return null
  }

  const downloadIcon = (
    <span
      className={classNames(
        'flex size-8 items-center justify-center rounded-full',
        variant === 'dark' ? 'text-white/70' : 'text-black/70'
      )}
      aria-hidden
    >
      <DownloadSimpleIcon className="size-5" weight="bold" />
    </span>
  )

  return (
    <Bubble
      variant={variant}
      text={text}
      groupPosition={groupPosition}
      data-testid="file-attachment"
    >
      {/* Stacked rows get a small vertical gap so each file reads as
          a discrete attachment rather than one merged blob. Single
          row falls through to a no-op `gap-0`. */}
      <div className="flex flex-col gap-2">
        {resolvedItems.map((item, index) => {
          // Composer only supports a single attachment so the dismiss
          // control sits on the only row; otherwise every row gets
          // the trailing download icon hint.
          const trailingAction =
            showDismiss && index === 0 ? (
              <DismissButton onClick={onDismiss!} variant="inline" />
            ) : (
              downloadIcon
            )
          return (
            <FileRowButton
              key={`${item.src}-${index}`}
              variant={variant}
              item={item}
              index={index}
              onActivate={handleActivate}
              trailingAction={trailingAction}
            />
          )
        })}
      </div>
    </Bubble>
  )
}

/**
 * Composer-only props. Single file (`src`) only — the composer surface
 * accepts a single attachment at a time, so `items` is not supported.
 * Captions (`text`) and `groupPosition` are also dropped: the composer
 * renders a standalone draft, not part of a same-author run, and
 * captions live in the message-input textarea, not inside the draft
 * attachment preview.
 */
export interface FileComposerProps extends ComposerExtras {
  src?: string
  filename?: string
  fileSize?: number
  mimeType?: string
  title?: string
  onClick?: (index: number) => boolean | void
}
export type FileSentProps = FileAttachmentSharedProps
export type FileReceivedProps = FileAttachmentSharedProps

const FileComposer: React.FC<FileComposerProps> = (props) => (
  <FileAttachmentRow {...props} state="composer" />
)
const FileSent: React.FC<FileSentProps> = (props) => (
  <FileAttachmentRow {...props} state="sent" />
)
const FileReceived: React.FC<FileReceivedProps> = (props) => (
  <FileAttachmentRow {...props} state="received" />
)

const FileAttachment = {
  Composer: FileComposer,
  Sent: FileSent,
  Received: FileReceived,
}

export default FileAttachment
