import type { RichCardImagePresentation } from '@linktr.ee/messaging-taxonomy'
import classNames from 'classnames'
import React from 'react'

import { optimizeMessagingAttachmentUrl } from '../../utils/cdnImageUrl'
// Reuse LinkAttachment's card chrome primitives — the rich card is a sibling
// renderer over the same building blocks, not a from-scratch card.
import type { LinkAttachmentVariant } from '../LinkAttachment/components/_shared/CardShell'
import CardThumbnail from '../LinkAttachment/components/_shared/CardThumbnail'

export interface RichCardImagesProps {
  variant: LinkAttachmentVariant
  presentation: RichCardImagePresentation
  images: string[]
  title?: string
  /** Hero-only: the palette-tracked URL (cleared when the hero image errors). */
  heroUrl?: string
  onHeroError?: () => void
}

/** Placeholder fill behind the fanned images, per surface. */
const FAN_BG: Record<LinkAttachmentVariant, string> = {
  dark: 'bg-white/[0.06]',
  light: 'bg-black/[0.04]',
}

// Deterministic fan geometry (no randomness — Chromatic snapshots must be
// stable). Rotation + horizontal offset per slot, keyed on how many images are
// actually shown (survivors after any load failures) — so a fan that drops to
// two or one re-centres rather than leaving a gap.
const FAN_ROTATION: Record<1 | 2 | 3, number[]> = {
  1: [0],
  2: [-10, 10],
  3: [-13, 0, 13],
}
// Offsets sit a hair inside the mobile renderer's so the outer tiles' rotated
// bounding box stays within the 290px shell's `overflow-hidden` edge (a `w-62%`
// tile at ±40 + 13° rotation lands ~6px shy of the boundary, vs the old ±44
// which clipped).
const FAN_OFFSET_PX: Record<1 | 2 | 3, number[]> = {
  1: [0],
  2: [-26, 26],
  3: [-40, 0, 40],
}
// Paint z-order per slot so the **centre** card sits on top. Survivors render
// left→right, so with the naive `zIndex: position` the right card overlaps the
// middle; here the centre (slot 1 of a 3-fan) gets the highest layer.
const FAN_ZORDER: Record<1 | 2 | 3, number[]> = {
  1: [0],
  2: [0, 1],
  3: [1, 3, 2],
}
// The centre card of a 3-fan is nudged larger so it reads as the top of the
// stack rather than one of three equals (mirrors the collection design).
const FAN_SCALE: Record<1 | 2 | 3, number[]> = {
  1: [1],
  2: [1, 1],
  3: [1, 1.08, 1],
}

/**
 * A fanned stack of 2–3 thumbnails for the `fan` presentation (collection
 * cards). An image that fails to load is dropped and the survivors re-fan (the
 * hero path recovers the same way via `useChinPalette`); if all fail the region
 * is left blank. An initial visual interpretation — the exact fan geometry
 * should be reconciled against the Figma collection frames when they land.
 */
const RichCardFan: React.FC<{
  variant: LinkAttachmentVariant
  images: string[]
}> = ({ variant, images }) => {
  const [failed, setFailed] = React.useState<Record<number, boolean>>({})

  // Reset the per-tile failure tracking when the image set changes, so a
  // recycled card (same element, new collection) doesn't carry a previous
  // array's failed indexes onto a valid replacement. Adjusting state during
  // render on a key change is the endorsed alternative to a set-state effect
  // (mirrors how `useChinPalette` clears `heroFailed` when `thumbnailUrl` changes).
  const imagesKey = images.join('\n')
  const [trackedKey, setTrackedKey] = React.useState(imagesKey)
  if (imagesKey !== trackedKey) {
    setTrackedKey(imagesKey)
    setFailed({})
  }

  const survivors = images
    .slice(0, 3)
    .map((src, index) => ({ src, index }))
    .filter(({ index }) => !failed[index])

  const count = (survivors.length >= 3 ? 3 : survivors.length === 2 ? 2 : 1) as
    1 | 2 | 3
  const rotations = FAN_ROTATION[count]
  const offsets = FAN_OFFSET_PX[count]
  const zorder = FAN_ZORDER[count]
  const scales = FAN_SCALE[count]

  return (
    <div
      className={classNames(
        'relative flex min-h-0 w-full flex-1 items-center justify-center overflow-hidden',
        FAN_BG[variant]
      )}
    >
      {survivors.map(({ src, index }, position) => (
        <img
          key={`${src}-${index}`}
          src={optimizeMessagingAttachmentUrl(src) ?? src}
          // Decorative: the card's title + subtitle carry the meaning, so the
          // fan thumbnails are illustration only. Empty alt keeps them out of
          // the accessibility tree (the accessible content is the text).
          alt=""
          draggable={false}
          loading="lazy"
          decoding="async"
          onError={() => setFailed((prev) => ({ ...prev, [index]: true }))}
          // A uniform, slightly-landscape tile (`w-62% aspect-[3/2]`, matching
          // the mobile renderer) with `object-cover` so the fan stays tidy
          // whatever the source thumbnails are (OG scrape, upload, video frame)
          // — the tiles are decorative, so an even cover crop reads better than
          // per-image aspect. `rounded-[12px]` explicit, NOT `rounded-xl` — this
          // preset's `xl` radius is 4rem, which rounds a tile into an oval.
          // `bg-[#ffffff]` gives each tile an opaque holder so a transparent
          // (PNG/WebP) source composites over white instead of letting the
          // fan-region tint and the tiles beneath bleed through — and so the
          // drop shadow reads against a solid card, not the see-through image.
          // Arbitrary hex, not `bg-white`, since this package ships no Tailwind
          // utilities and consumers scan `dist` against their own theme (same
          // reason CardShell uses `bg-[#…]`). `shadow-md` (not `shadow-lg`)
          // keeps the elevation subtle, matching mobile's `Shadow100`.
          className="absolute left-1/2 top-1/2 w-[62%] aspect-[3/2] rounded-[12px] bg-[#ffffff] object-cover shadow-md"
          style={{
            transform: `translate(-50%, -50%) translateX(${offsets[position]}px) rotate(${rotations[position]}deg) scale(${scales[position]})`,
            zIndex: zorder[position],
          }}
        />
      ))}
    </div>
  )
}

/** Fixed image-region height. The chin flows below at its natural height (the
 * card grows rather than clipping tall content), so this is a fixed height, not
 * the 250px "hero absorbs the remainder" lock LinkAttachment uses. */
const IMAGE_REGION_CLASS =
  'flex h-[180px] w-full shrink-0 flex-col overflow-hidden'

/**
 * The rich card's image region. `hero` reuses `CardThumbnail` (single image, the
 * official/link-share card); `fan` renders the fanned stack (collection card).
 * Both fill a fixed-height flex column so `CardThumbnail`'s `flex-1` hero has a
 * height to occupy without the whole card being pinned to a fixed box.
 */
const RichCardImages: React.FC<RichCardImagesProps> = ({
  variant,
  presentation,
  images,
  title,
  heroUrl,
  onHeroError,
}) => (
  <div className={IMAGE_REGION_CLASS}>
    {presentation === 'fan' ? (
      <RichCardFan variant={variant} images={images} />
    ) : (
      // `heroUrl` is the palette-tracked source: it becomes `undefined` when the
      // hero image errors, so `CardThumbnail` falls through to its placeholder.
      // Do NOT `?? images[0]` here — that would re-instate the failed URL.
      <CardThumbnail
        variant={variant}
        thumbnailUrl={heroUrl}
        title={title}
        mimeType="image/*"
        onImageError={onHeroError}
      />
    )}
  </div>
)

export default RichCardImages
