import {
  ArticleIcon,
  MusicNotesIcon,
  PlayIcon,
  QuestionIcon,
} from '@phosphor-icons/react'
import type { Meta, StoryFn } from '@storybook/react'
import React from 'react'

import type { LinkAttachmentVariant } from './components/_shared/CardShell'
import { HEX_PATTERN, toCssHex } from './components/_shared/hex'

import LinkAttachment from '.'

const IMAGE_THUMBNAIL = '/image-thumbnail.jpg'
const AUDIO_THUMBNAIL = '/audio-thumbnail.jpg'
const AUDIO_SOURCE = '/audio-source.mp3'
const VIDEO_SOURCE = '/video-source.mp4'

const meta: Meta = {
  title: 'LinkAttachment',
  parameters: {
    layout: 'fullscreen',
    // The single LinkApps story is a large matrix of every link-app card
    // crossed with featured/classic layouts and locked/unlocked states.
    // It's intended as a design-review canvas, not a regression target —
    // a single pixel change in any cell would diff the whole thing. Skip
    // it in Chromatic; the per-app/state behavior is exercised
    // implicitly through the message-attachment stories that use these
    // cards.
    chromatic: { disableSnapshot: true },
  },
}
export default meta

const Table = ({ children }: { children: React.ReactNode }) => (
  <div className="min-h-screen w-full bg-[#F9F7F4] p-12">
    <table className="border-separate border-spacing-4">{children}</table>
  </div>
)

const TableHead = ({ columns }: { columns: string[] }) => (
  <thead>
    <tr>
      <th className="pb-2 text-left text-xs font-medium text-black/40" />
      {columns.map((column) => (
        <th
          key={column}
          className="pb-2 text-left text-xs font-medium text-black/40"
        >
          {column}
        </th>
      ))}
    </tr>
  </thead>
)

const RowLabel = ({ children }: { children: React.ReactNode }) => (
  <td className="pr-4 pt-2 text-right align-top text-xs font-medium text-black/40">
    {children}
  </td>
)

const APP_ICON_BASE =
  'inline-flex size-4 items-center justify-center overflow-hidden rounded-[4px]'

const SpotifyBadge = () => (
  <span
    aria-hidden
    className={`${APP_ICON_BASE} bg-[#1ed760] text-white`}
    title="Spotify"
  >
    <MusicNotesIcon className="size-3" weight="fill" />
  </span>
)

const TikTokBadge = () => (
  <span
    aria-hidden
    className={`${APP_ICON_BASE} bg-[#101211] text-white`}
    title="TikTok"
  >
    <PlayIcon className="size-3" weight="fill" />
  </span>
)

const FaqBadge = () => (
  <span
    aria-hidden
    className={`${APP_ICON_BASE} bg-[#061492] text-white`}
    title="FAQ"
  >
    <QuestionIcon className="size-3" weight="bold" />
  </span>
)

const FormBadge = () => (
  <span
    aria-hidden
    className={`${APP_ICON_BASE} bg-[#2665d6] text-white`}
    title="Form"
  >
    <ArticleIcon className="size-3" weight="fill" />
  </span>
)

const LINK_APPS: Array<{
  key: string
  appIcon: React.ReactNode
  title: string
  description: string
  url?: string
  ctaLabel?: string
}> = [
  {
    key: 'spotify',
    appIcon: <SpotifyBadge />,
    title: 'My Playlist',
    description: 'A handpicked workout mix I made for my clients.',
    url: 'tr.ee/briemix',
  },
  {
    key: 'tiktok',
    appIcon: <TikTokBadge />,
    title: 'My TikTok',
    description: 'New form-check clips every week — follow along.',
    url: 'tr.ee/brietok',
  },
  {
    key: 'faq',
    appIcon: <FaqBadge />,
    title: 'Brie’s FAQ',
    description: 'Get answers on my process and what to expect.',
    ctaLabel: 'View FAQs',
  },
  {
    key: 'form',
    appIcon: <FormBadge />,
    title: 'Fitness Assessment Fillout',
    description: 'Share information about your journey and I’ll work on...',
    ctaLabel: 'Complete form',
  },
]

type LinkAppLayout = 'featured' | 'classic'
type LinkAppState = 'Received' | 'Sent' | 'Composer'

const LINK_APP_ROWS: Array<{ layout: LinkAppLayout; state: LinkAppState }> = [
  { layout: 'featured', state: 'Received' },
  { layout: 'featured', state: 'Sent' },
  { layout: 'featured', state: 'Composer' },
  { layout: 'classic', state: 'Received' },
  { layout: 'classic', state: 'Sent' },
  { layout: 'classic', state: 'Composer' },
]

const renderLinkAppCard = (
  layout: LinkAppLayout,
  state: LinkAppState,
  app: (typeof LINK_APPS)[number]
) => {
  const { key, appIcon, title, description, url, ctaLabel } = app
  const thumbnailUrl = layout === 'featured' ? IMAGE_THUMBNAIL : undefined
  const ctaWithHandler = ctaLabel
    ? { label: ctaLabel, onClick: () => alert(`Tapped ${ctaLabel}`) }
    : undefined
  const ctaSilent = ctaLabel ? { label: ctaLabel } : undefined

  if (state === 'Received') {
    return (
      <LinkAttachment.Received
        layout={layout}
        appIcon={appIcon}
        title={title}
        description={description}
        thumbnailUrl={thumbnailUrl}
        url={url}
        cta={ctaWithHandler}
      />
    )
  }
  if (state === 'Sent') {
    return (
      <LinkAttachment.Sent
        layout={layout}
        appIcon={appIcon}
        title={title}
        description={description}
        thumbnailUrl={thumbnailUrl}
        url={url}
        cta={ctaSilent}
      />
    )
  }
  return (
    <LinkAttachment.Composer
      layout={layout}
      appIcon={appIcon}
      title={title}
      description={description}
      thumbnailUrl={thumbnailUrl}
      url={url}
      cta={ctaSilent}
      onDismiss={() => alert(`Dismissed ${key}`)}
    />
  )
}

/**
 * "LinkApps" section of the Figma board — link previews with a brand
 * badge prefixing the title and either a URL footer (Spotify, TikTok)
 * or a CTA button (FAQ, Form). Each app is shown in both the
 * **Featured** (hero image) and **Classic** (compact, no hero) layouts
 * across all three messaging states.
 *
 * For chat **document / image / video / audio attachments**, see the
 * `MessageAttachment` stories instead.
 */
export const LinkApps: StoryFn = () => (
  <Table>
    <TableHead columns={LINK_APPS.map(({ key }) => key)} />
    <tbody>
      {LINK_APP_ROWS.map(({ layout, state }) => {
        const label = `${layout === 'featured' ? 'Featured' : 'Classic'} (${state})`
        return (
          <tr key={label}>
            <RowLabel>{label}</RowLabel>
            {LINK_APPS.map((app) => (
              <td key={app.key} className="align-top">
                {renderLinkAppCard(layout, state, app)}
              </td>
            ))}
          </tr>
        )
      })}
    </tbody>
  </Table>
)

/* ──────────────────────────────────────────────────────────────────
   Accent chin (MES-1349)
   ────────────────────────────────────────────────────────────────── */

const ACCENTS: Array<{ label: string; accentColor?: string }> = [
  // The Figma mock's own chin colour. Painted raw it measures 3.76:1 against
  // the sender's white ink, which is why the mock cannot simply be reproduced;
  // derived, its hue survives at a lightness both inks clear. The shipped sent
  // card is therefore darker than the mock on purpose (MES-1388).
  { label: '#738883 (mock)', accentColor: '#738883' },
  { label: '#AA9F93', accentColor: '#AA9F93' },
  { label: '#FFF099', accentColor: '#FFF099' },
  { label: '#767676', accentColor: '#767676' },
  { label: '#7008E7', accentColor: '#7008E7' },
  { label: '#1e2330', accentColor: '#1e2330' },
  { label: 'none (fallback)', accentColor: undefined },
  { label: 'malformed', accentColor: 'rebeccapurple' },
]

const ACCENT_CARD = {
  title: 'Yoga on Ice with Brie Nala — where ice meets inner calm.',
  url: 'tr.ee/briewebsite',
  thumbnailUrl: IMAGE_THUMBNAIL,
}

/**
 * CTA emphasis — `cta.variant`. `primary` (default) is the filled button;
 * `secondary` is the subtle/grey button used by the official-channel cards.
 * Shown on both surfaces, and over an accent-derived chin: the translucent
 * `secondary` fills (`bg-white/15`, `bg-black/[0.06]`) composite against the
 * extracted colour, whereas the opaque `primary` fill is accent-independent —
 * the bottom rows are the Chromatic guard for that legibility difference.
 */
export const CtaEmphasis: StoryFn = () => {
  const cta = (label: string, variant?: 'primary' | 'secondary') => ({
    label,
    onClick: () => alert(`Tapped ${label}`),
    ...(variant ? { variant } : {}),
  })
  return (
    <Table>
      <TableHead columns={['primary (default)', 'secondary']} />
      <tbody>
        <tr>
          <RowLabel>Received (light)</RowLabel>
          <td className="align-top">
            <LinkAttachment.Received {...ACCENT_CARD} cta={cta('Add now')} />
          </td>
          <td className="align-top">
            <LinkAttachment.Received
              {...ACCENT_CARD}
              cta={cta('Add now', 'secondary')}
            />
          </td>
        </tr>
        <tr>
          <RowLabel>Sent (dark)</RowLabel>
          <td className="align-top">
            <LinkAttachment.Sent {...ACCENT_CARD} cta={cta('Add now')} />
          </td>
          <td className="align-top">
            <LinkAttachment.Sent
              {...ACCENT_CARD}
              cta={cta('Add now', 'secondary')}
            />
          </td>
        </tr>
        <tr>
          <RowLabel>Received + accent chin</RowLabel>
          <td className="align-top">
            <LinkAttachment.Received
              {...ACCENT_CARD}
              accentColor="#7008E7"
              cta={cta('Add now')}
            />
          </td>
          <td className="align-top">
            <LinkAttachment.Received
              {...ACCENT_CARD}
              accentColor="#7008E7"
              cta={cta('Add now', 'secondary')}
            />
          </td>
        </tr>
        <tr>
          <RowLabel>Sent + accent chin</RowLabel>
          <td className="align-top">
            <LinkAttachment.Sent
              {...ACCENT_CARD}
              accentColor="#7008E7"
              cta={cta('Add now')}
            />
          </td>
          <td className="align-top">
            <LinkAttachment.Sent
              {...ACCENT_CARD}
              accentColor="#7008E7"
              cta={cta('Add now', 'secondary')}
            />
          </td>
        </tr>
      </tbody>
    </Table>
  )
}

/**
 * The dominant-colour card and its WCAG AA gate (MES-1349, corrected MES-1388).
 *
 * Each column is one extracted accent. Ink is *fixed per side* — `text-white`
 * when sent, `text-black/90` when received — so contrast decides whether the
 * accent may paint at all rather than which text colour to use. An accent under
 * 4.5:1 against that side's ink is discarded for the plain message bubble fill
 * (`#1e2330` sent, `#f1f0ee` received), so a card without a usable colour reads
 * like the text bubbles around it.
 *
 * Each column is a synthetic base colour chosen to probe the derivation, not a
 * colour taken from the hero — in production `accent_color` is extracted from
 * the attachment image itself, so a photograph's own hue is what arrives here.
 * Every column paints on both sides: the derivation moves lightness, never hue,
 * so a base is never refused for contrast.
 *
 * The bottom rows cover the states where the accent is deliberately ignored:
 * `classic` has no image to extract from, and `loading` has no image yet.
 */
export const AccentChin: StoryFn = () => (
  <Table>
    <TableHead columns={ACCENTS.map(({ label }) => label)} />
    <tbody>
      <tr>
        <RowLabel>Sent (last in run)</RowLabel>
        {ACCENTS.map(({ label, accentColor }) => (
          <td key={label} className="align-top">
            <LinkAttachment.Sent {...ACCENT_CARD} accentColor={accentColor} />
          </td>
        ))}
      </tr>
      <tr>
        <RowLabel>Sent (mid-run, no tail)</RowLabel>
        {ACCENTS.map(({ label, accentColor }) => (
          <td key={label} className="align-top">
            <LinkAttachment.Sent
              {...ACCENT_CARD}
              accentColor={accentColor}
              groupPosition="middle"
            />
          </td>
        ))}
      </tr>
      <tr>
        <RowLabel>Received</RowLabel>
        {ACCENTS.map(({ label, accentColor }) => (
          <td key={label} className="align-top">
            <LinkAttachment.Received
              {...ACCENT_CARD}
              accentColor={accentColor}
            />
          </td>
        ))}
      </tr>
      <tr>
        <RowLabel>Composer</RowLabel>
        {ACCENTS.map(({ label, accentColor }) => (
          <td key={label} className="align-top">
            <LinkAttachment.Composer
              {...ACCENT_CARD}
              accentColor={accentColor}
              onDismiss={() => undefined}
            />
          </td>
        ))}
      </tr>
      <tr>
        <RowLabel>1-line title (hero 188px)</RowLabel>
        {ACCENTS.map(({ label, accentColor }) => (
          <td key={label} className="align-top">
            <LinkAttachment.Sent
              {...ACCENT_CARD}
              title="Brie’s website"
              accentColor={accentColor}
            />
          </td>
        ))}
      </tr>
      <tr>
        <RowLabel>Accent ignored</RowLabel>
        <td className="align-top">
          <LinkAttachment.Sent
            {...ACCENT_CARD}
            layout="classic"
            accentColor="#738883"
          />
        </td>
        <td className="align-top">
          <LinkAttachment.Composer
            status="loading"
            onDismiss={() => undefined}
          />
        </td>
        <td className="align-top">
          <LinkAttachment.Received status="loading" />
        </td>
      </tr>
    </tbody>
  </Table>
)

// Unlike the LinkApps canvas above, this matrix *is* the regression target for
// the accent geometry and the contrast flip — snapshot it.
AccentChin.parameters = { chromatic: { disableSnapshot: false } }

/** A link preview whose clip is playable audio, with cover artwork attached. */
const AUDIO_PREVIEW = {
  mimeType: 'audio/mpeg',
  sourceUrl: AUDIO_SOURCE,
  thumbnailUrl: AUDIO_THUMBNAIL,
}

/**
 * Accents that must **not** paint, because the hero image they claim to have
 * been extracted from is not on screen (MES-1349).
 *
 * Every card here is handed `#7008E7` — a violent purple, picked so a
 * regression is impossible to miss — and every card must render its own
 * variant fill instead. `useChinPalette` gates the accent on `CardThumbnail`
 * actually mounting the image, so:
 *
 * - **Playable audio** replaces the hero wholesale with the native `<audio>`
 *   player. The artwork is still handed over as `thumbnailUrl` and there is
 *   still no `<img>`, so `onImageError` can never clear the accent — the gate
 *   has to be up front. These three cells are the regression that prompted the
 *   rule: the derived surface outranks the `bg-[#F2F3F4]` audio fill, so a
 *   purple card would ship around a native player with the artwork nowhere in
 *   sight.
 * - **No `thumbnailUrl`** leaves the type-icon placeholder, which is not an
 *   image the accent could have come from. Stream reaches this whenever a link
 *   preview carries a playable clip but no `image_url`.
 * - **A playable video with no poster** is that same case wearing a `bg-black`
 *   box.
 */
export const AccentWithoutVisibleHero: StoryFn = () => (
  <Table>
    <TableHead
      columns={[
        'Sent — audio + artwork',
        'Received — audio + artwork',
        'Composer — audio + artwork',
        'Sent — no thumbnail',
        'Sent — video, no poster',
      ]}
    />
    <tbody>
      <tr>
        <RowLabel>accent #7008E7</RowLabel>
        <td className="align-top">
          <LinkAttachment.Sent
            {...ACCENT_CARD}
            {...AUDIO_PREVIEW}
            accentColor="#7008E7"
          />
        </td>
        <td className="align-top">
          <LinkAttachment.Received
            {...ACCENT_CARD}
            {...AUDIO_PREVIEW}
            accentColor="#7008E7"
          />
        </td>
        <td className="align-top">
          <LinkAttachment.Composer
            {...ACCENT_CARD}
            {...AUDIO_PREVIEW}
            accentColor="#7008E7"
            onDismiss={() => undefined}
          />
        </td>
        <td className="align-top">
          <LinkAttachment.Sent
            {...ACCENT_CARD}
            thumbnailUrl={undefined}
            accentColor="#7008E7"
          />
        </td>
        <td className="align-top">
          <LinkAttachment.Sent
            {...ACCENT_CARD}
            thumbnailUrl={undefined}
            mimeType="video/mp4"
            sourceUrl={VIDEO_SOURCE}
            accentColor="#7008E7"
          />
        </td>
      </tr>
    </tbody>
  </Table>
)

AccentWithoutVisibleHero.parameters = { chromatic: { disableSnapshot: false } }

/* ──────────────────────────────────────────────────────────────────
   Accent derivation swatches
   ────────────────────────────────────────────────────────────────── */

/**
 * Max-chroma sRGB colour for a hue — three phase-shifted cosines, so stepping
 * the hue walks the wheel at the most saturated triple each hue has. Many of
 * these sit outside sRGB at the surface stops, where the browser gamut-maps.
 */
const vividHex = (hue: number): string =>
  `#${[0, 120, 240]
    .map((offset) =>
      Math.round(127 + 127 * Math.cos(((hue + offset) * Math.PI) / 180))
        .toString(16)
        .padStart(2, '0')
    )
    .join('')}`

const HUE_SWEEP = Array.from({ length: 24 }, (_, i) => ({
  label: `${i * 15}°`,
  accentColor: vividHex(i * 15),
}))

// Neutrals and near-limits where the ink and the fallback actually decide
// something, plus the two inputs that must never derive a surface.
const EDGE_SAMPLES: Array<{ label: string; accentColor?: string }> = [
  { label: 'mock #738883', accentColor: '#738883' },
  { label: 'pale #FFF099', accentColor: '#FFF099' },
  { label: 'near-white #FCF5E3', accentColor: '#FCF5E3' },
  { label: 'near-black #1e2330', accentColor: '#1e2330' },
  { label: 'neutral #767676', accentColor: '#767676' },
  { label: 'vivid #7008E7', accentColor: '#7008E7' },
  { label: 'none', accentColor: undefined },
  { label: 'malformed', accentColor: 'rebeccapurple' },
]

// Ink is fixed per side — white on the dark stop, near-black on the pale stop.
const INK_BY_VARIANT: Record<LinkAttachmentVariant, string> = {
  dark: '#FFFFFF',
  light: '#040403',
}
// The plain bubble fill each side falls back to, as a self-contained arbitrary
// value (mirrors `CardShell`). The accent rule in `styles.css` paints over it.
const FALLBACK_BG_CLASS: Record<LinkAttachmentVariant, string> = {
  dark: 'bg-[#1e2330]',
  light: 'bg-[#f1f0ee]',
}

/**
 * One derivation cell driven by the *same* CSS the card uses: a
 * `.messaging-link-card` publishing `--accent`, so `styles.css` derives the
 * surface via `oklch(from …)`. A refused accent (`none` / `malformed`) publishes
 * nothing and shows the fallback fill. The resolved surface hex lives only in
 * the browser, so the cell labels its input.
 */
const DerivedSwatch = ({
  accentColor,
  variant,
}: {
  accentColor?: string
  variant: LinkAttachmentVariant
}) => {
  const accentHex =
    accentColor != null && HEX_PATTERN.test(accentColor.trim())
      ? toCssHex(accentColor)
      : undefined
  return (
    <div
      className={`messaging-link-card flex h-12 flex-col justify-between rounded-md px-2 py-1.5 text-[11px] font-medium ${FALLBACK_BG_CLASS[variant]}`}
      data-variant={variant}
      style={
        {
          color: INK_BY_VARIANT[variant],
          ...(accentHex ? { '--accent': accentHex } : {}),
        } as React.CSSProperties
      }
    >
      <span>Yoga on Ice</span>
      <span className="font-mono opacity-80">{accentHex ?? 'fallback'}</span>
    </div>
  )
}

const GridHead = ({ children }: { children: React.ReactNode }) => (
  <div className="text-xs font-medium text-black/40">{children}</div>
)

/**
 * Isolates the accent-chin surface: for every hue at maximum chroma and every
 * neutral/edge input, the `dark` (Sent) and `light` (Received) surfaces CSS
 * derives from the published `--accent`. One snapshot, no hero pixels — the
 * dense diff target for the derivation. In-gamut inputs (the neutral/edge rows)
 * are stable; the max-chroma hue-sweep rows depend on the browser's gamut map.
 */
export const AccentDerivation: StoryFn = () => (
  <div className="min-h-screen w-full bg-[#F9F7F4] p-8">
    <div className="grid grid-cols-[6rem_1fr_1fr] items-center gap-x-4 gap-y-1.5">
      <div />
      <GridHead>Sent (dark stop)</GridHead>
      <GridHead>Received (light stop)</GridHead>
      {[...HUE_SWEEP, ...EDGE_SAMPLES].map(({ label, accentColor }) => (
        <React.Fragment key={label}>
          <div className="text-right text-xs font-medium text-black/40">
            {label}
          </div>
          <DerivedSwatch accentColor={accentColor} variant="dark" />
          <DerivedSwatch accentColor={accentColor} variant="light" />
        </React.Fragment>
      ))}
    </div>
  </div>
)

AccentDerivation.parameters = { chromatic: { disableSnapshot: false } }

/* ──────────────────────────────────────────────────────────────────
   Description line clamp (descriptionLines)
   ────────────────────────────────────────────────────────────────── */

const CLAMP_CARD = {
  title: 'A message from Brie Nala',
  description:
    'Thanks for reaching out! This is a longer prose body of the kind an official message carries — it runs past a single line and should wrap up to the clamp instead of clipping to one truncated line like a link preview.',
  url: 'tr.ee/brienala',
}

const CLAMP_STATES = ['Received', 'Sent', 'Composer'] as const

const renderClampCard = (
  state: (typeof CLAMP_STATES)[number],
  descriptionLines?: number
) => {
  if (state === 'Received') {
    return (
      <LinkAttachment.Received
        layout="classic"
        {...CLAMP_CARD}
        descriptionLines={descriptionLines}
      />
    )
  }
  if (state === 'Sent') {
    return (
      <LinkAttachment.Sent
        layout="classic"
        {...CLAMP_CARD}
        descriptionLines={descriptionLines}
      />
    )
  }
  return (
    <LinkAttachment.Composer
      layout="classic"
      {...CLAMP_CARD}
      descriptionLines={descriptionLines}
      onDismiss={() => undefined}
    />
  )
}

/**
 * `descriptionLines` — how many lines the description body renders before it
 * clamps. The default (`1`) keeps the single-line `truncate` link-preview
 * behaviour; `> 1` wraps via a webkit line-clamp so prose bodies (official
 * messages) render multi-line like the mobile card. The URL footer stays
 * single-line regardless. Snapshot target for that split across all states.
 */
export const DescriptionLines: StoryFn = () => (
  <Table>
    <TableHead columns={['default (1)', '2 lines', '3 lines']} />
    <tbody>
      {CLAMP_STATES.map((state) => (
        <tr key={state}>
          <RowLabel>{state}</RowLabel>
          <td className="align-top">{renderClampCard(state)}</td>
          <td className="align-top">{renderClampCard(state, 2)}</td>
          <td className="align-top">{renderClampCard(state, 3)}</td>
        </tr>
      ))}
    </tbody>
  </Table>
)

DescriptionLines.parameters = { chromatic: { disableSnapshot: false } }
