import classNames from 'classnames'
import React from 'react'

import type { BubbleGroupPosition, BubbleVariant } from '../types'

export interface BubbleProps {
  variant: BubbleVariant
  /** Optional message text rendered below the attachment slot. */
  text?: React.ReactNode
  /**
   * Renders a hairline border around the bubble. Defaults to `true` —
   * matches the design system's "small border around link / message
   * attachments" treatment from the mobile spec.
   */
  bordered?: boolean
  /**
   * Position of this bubble inside a same-author message run. Drives
   * the corner-flattening that visually merges consecutive bubbles
   * (matches the grouping stream-chat-react applies to text bubbles).
   * Defaults to `'single'` so standalone usage keeps every corner
   * fully rounded.
   */
  groupPosition?: BubbleGroupPosition
  /**
   * Width utility that REPLACES the default `'w-[280px]'`. Supply the
   * whole declaration (`'w-fit'`, `'w-[383px] max-w-full'`) — nothing
   * is merged with the default.
   */
  widthClassName?: string
  /**
   * Padding utility that REPLACES the default `'px-2 py-2'`. Supply
   * the full inset (`'p-0.5'`, `'py-5 pl-5 pr-3'`).
   */
  paddingClassName?: string
  /**
   * Corner utility that REPLACES the `groupPosition`-derived corner
   * table entirely. Opting in means opting OUT of same-author corner
   * flattening — the bubble renders the supplied radius on all four
   * corners. That is what the attachment redesign wants: Figma draws
   * media cards at a uniform r20 even when a caption bubble sits 4px
   * below (nodes `1973:12776`, `1973:20239`).
   *
   * It replaces rather than appends because appending cannot be made
   * reliable — an appended `rounded-[20px]` and the corner table have
   * identical CSS specificity, so Tailwind's emitted source order,
   * not argument order, would pick the winner.
   */
  radiusClassName?: string
  className?: string
  children: React.ReactNode
  'data-testid'?: string
}

// Colors and metrics tracked from `stream-chat-react`'s default
// `.str-chat__message-bubble` token set so attachments visually drop
// into a normal `CustomMessage` thread without any seams:
//   - light  → `--str-chat__message-bubble-background-color` in `styles.css` (#f2f1ef)
//   - dark   → `.str-chat__message--me` override in `styles.css` (#1e2330)
//   - text padding → `--str-chat__spacing-2 --str-chat__spacing-4` = 8px 16px
//   - border-radius → `--str-chat__border-radius-md` = 18px
//
// TODO: migrate to Tailwind theme tokens once they exist for the
// `linktree.dark` / `linktree.surface.secondary` palette — these hex
// literals are repeated across LinkAttachment / LockedAttachment /
// MediaMessage / CustomMessageInput / DownloadAction etc., so a
// rebrand would need to touch every site. A central token would
// collapse the migration to one definition.
const BUBBLE_BG_BY_VARIANT: Record<BubbleVariant, string> = {
  dark: 'bg-[#1e2330]',
  light: 'bg-[#f2f1ef]',
}

const BUBBLE_TEXT_BY_VARIANT: Record<BubbleVariant, string> = {
  dark: 'text-white',
  light: 'text-[#000000]',
}

const BUBBLE_BORDER_BY_VARIANT: Record<BubbleVariant, string> = {
  dark: 'border-white/[0.08]',
  light: 'border-black/[0.08]',
}

/**
 * Per-corner rounding tables that mirror stream-chat-react's default
 * bubble grouping. Sender bubbles cluster against the right edge so
 * the right corners get flattened in a run; receiver bubbles cluster
 * against the left edge so the left corners get flattened.
 *
 * Rounded corners use `--str-chat__border-radius-md` (18px) and
 * flattened corners use `--str-chat__border-radius-sm` (4px) — same
 * tokens stream's stylesheet uses for `.str-chat__message-bubble`
 * inside a `--first` / `--group` / `--end` wrapper.
 */
type BubbleSide = 'sender' | 'receiver'

const sideForVariant = (variant: BubbleVariant): BubbleSide =>
  variant === 'dark' ? 'sender' : 'receiver'

const CORNER_CLASSES_BY_SIDE_AND_POSITION: Record<
  BubbleSide,
  Record<BubbleGroupPosition, string>
> = {
  sender: {
    single:
      'rounded-tl-[18px] rounded-tr-[18px] rounded-bl-[18px] rounded-br-[18px]',
    first:
      'rounded-tl-[18px] rounded-tr-[18px] rounded-bl-[18px] rounded-br-[4px]',
    middle:
      'rounded-tl-[18px] rounded-tr-[4px] rounded-bl-[18px] rounded-br-[4px]',
    end: 'rounded-tl-[18px] rounded-tr-[4px] rounded-bl-[18px] rounded-br-[18px]',
  },
  receiver: {
    single:
      'rounded-tl-[18px] rounded-tr-[18px] rounded-bl-[18px] rounded-br-[18px]',
    first:
      'rounded-tl-[18px] rounded-tr-[18px] rounded-bl-[4px] rounded-br-[18px]',
    middle:
      'rounded-tl-[4px] rounded-tr-[18px] rounded-bl-[4px] rounded-br-[18px]',
    end: 'rounded-tl-[4px] rounded-tr-[18px] rounded-bl-[18px] rounded-br-[18px]',
  },
}

/**
 * Chat-bubble container shared by every `MessageAttachment.*` card.
 *
 * Holds the attachment slot (image / video / row) and an optional
 * `text` caption rendered below it — mirrors the mobile chat layout
 * where a sender can attach a caption alongside an attachment ('Here
 * is the file', 'Here is the image').
 *
 * Two visual variants:
 *   - `dark` — sender-side (Composer / Sent), white text on `#1e2330`.
 *   - `light` — recipient-side (Received), dark text on `#f2f1ef`.
 *
 * Background colors, padding, border-radius, and inherited font come
 * from the stream-chat-react `.str-chat__message-bubble` token set so
 * attachments line up with the `CustomMessage` text bubbles in a real
 * conversation. Padding is uniform across every variant — text rows,
 * audio rows, document rows, and media bubbles all get the same
 * 8px / 16px inset, with media inside getting its own rounded corners.
 */
const Bubble: React.FC<BubbleProps> = ({
  variant,
  text,
  bordered = true,
  groupPosition = 'single',
  widthClassName = 'w-[280px]',
  paddingClassName = 'px-2 py-2',
  radiusClassName,
  className,
  children,
  'data-testid': dataTestId,
}) => {
  const hasText = text != null && text !== ''
  const cornerClasses =
    radiusClassName ??
    CORNER_CLASSES_BY_SIDE_AND_POSITION[sideForVariant(variant)][groupPosition]

  return (
    <div
      data-testid={dataTestId}
      data-group-position={groupPosition}
      className={classNames(
        // Defaults describe a 280px-wide document / audio / link
        // bubble: that width matches the mobile chat attachment and
        // the 8px / 16px inset matches `--str-chat__spacing-2
        // --str-chat__spacing-4`, so attachments share the same hit /
        // negative-space rhythm as the surrounding `CustomMessage`
        // text bubbles. Media cards override both — Figma sizes them
        // to the media (330 / 383px) with a 2px frame instead.
        'relative',
        widthClassName,
        'overflow-hidden',
        paddingClassName,
        // The caption below owns the bubble's whole bottom inset when
        // it is present, so the container hands it over. Without this
        // the inset is whatever `paddingClassName` happens to supply,
        // which is 2px for the media cards' `p-0.5` frame and leaves
        // caption text sitting on the card edge. Safe to append: this
        // is shorthand-then-longhand (`p-*`/`py-*` then `pb-*`), which
        // Tailwind always emits in that order inside its `padding`
        // plugin — unlike the same-utility `rounded-*` clash above,
        // there is no ambiguity to resolve.
        hasText && 'pb-0',
        cornerClasses,
        BUBBLE_BG_BY_VARIANT[variant],
        BUBBLE_TEXT_BY_VARIANT[variant],
        bordered && 'border',
        bordered && BUBBLE_BORDER_BY_VARIANT[variant],
        className
      )}
    >
      {children}

      {hasText ? (
        <p
          className={classNames(
            // No `text-*` / `font-*` overrides here — caption inherits
            // the same font family + size as `.str-chat__message-text`
            // so it matches the surrounding `CustomMessage` bubbles.
            'whitespace-pre-wrap break-words leading-snug hyphens-manual',
            // Owns the full vertical inset: an 8px gutter above (the
            // children render flush against their own bottom edge)
            // and the bubble's 8px bottom inset below, which the
            // container gave up via `pb-0`. Horizontally this stacks
            // on the container's inset so a default bubble's caption
            // lands at the 16px `--str-chat__spacing-4` text inset.
            'pb-2 pt-2',
            'px-2'
          )}
        >
          {text}
        </p>
      ) : null}
    </div>
  )
}

export default Bubble
