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

export interface MediaStackTile {
  /** Renderable tile content (image / video / poster). */
  content: React.ReactNode
  /** Pure-tile aria label (e.g. `'Photo 1'`). Used by `Pressable` mode. */
  ariaLabel?: string
  /**
   * Cursor utility for the pressable tile shell. Defaults to
   * `'cursor-zoom-in'` (correct for images); Video passes
   * `'cursor-pointer'` because activating a video tile starts playback
   * rather than magnifying a still.
   */
  cursorClassName?: string
}

/**
 * Tiles rendered before the last one collapses into a `+N` overflow
 * indicator. Exported so callers that need to style the overflow tile
 * differently can work out which index it lands on without restating
 * the number.
 */
export const MEDIA_STACK_MAX_VISIBLE = 4

export interface MediaStackGridProps {
  tiles: MediaStackTile[]
  /**
   * When set, every tile is wrapped in a `<button>` and forwards the
   * tile index to `onTileActivate`. Used by Image / Video so any tile
   * opens the lightbox / video viewer at the right index.
   */
  onTileActivate?: (index: number) => void
  /**
   * Maximum tiles to render before the last one collapses into an
   * "+N more" overflow indicator. Defaults to `4`.
   */
  maxVisible?: number
  /**
   * Aspect ratio (width / height) for the SINGLE-tile layout. Applies
   * to every media kind, not just images. When omitted the 1-tile
   * branch keeps today's `aspect-square`. Ignored for 2+ tiles, whose
   * grid aspect is fixed.
   */
  singleAspectRatio?: number
  className?: string
}

// The tile is the focusable element, and its focus ring has to be drawn
// INSIDE it. Figma leaves only a 2px frame around the media and `Bubble`
// clips its own overflow, so an outset ring is swallowed by the frame;
// an inset ring or a negative-offset `outline` on the tile is covered by
// the media, which is `absolute inset-0`. A positioned `::after` is the
// last child in tree order, so it paints above the media, inherits the
// tile's outer-corner radius, and ignores every ancestor clip. Same
// two-tone treatment as before — a dark band over a white ring — so it
// stays legible on light and dark photos alike. Verified in Chromium.
const TILE_SHELL =
  'relative block size-full overflow-hidden bg-black/5 outline-none after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] focus-visible:after:ring-2 focus-visible:after:ring-inset focus-visible:after:ring-white/80 focus-visible:after:ring-offset-2 focus-visible:after:ring-offset-black'

// Figma rounds only the four OUTER corners of a media cluster and leaves
// every inner edge square: on `attachment-single-image` 1973:13863 the
// `left` column carries `rectangleCornerRadii [18,0,0,18]` and `right`
// `[0,18,18,0]`, while the tiles themselves carry none.
//
// So the radius lives on the tiles, keyed by layout and DOM position.
// A single `overflow-hidden rounded-[18px]` container would be shorter,
// but it also clips the tiles' `focus-visible` ring — an outset
// `box-shadow` — and in the 1-tile layout the tile fills the container
// exactly, so keyboard users would get no focus indicator at all.
// Neither `ring-inset` nor a negative `outline-offset` rescues that:
// the tile's `<img>` is `absolute inset-0` and paints over both.
// Measured in Chromium, not assumed.
const OUTER_CORNER_CLASSES: Record<number, readonly string[]> = {
  1: ['rounded-[18px]'],
  2: ['rounded-l-[18px]', 'rounded-r-[18px]'],
  3: ['rounded-l-[18px]', 'rounded-tr-[18px]', 'rounded-br-[18px]'],
  4: [
    'rounded-tl-[18px]',
    'rounded-tr-[18px]',
    'rounded-bl-[18px]',
    'rounded-br-[18px]',
  ],
}

// Figma fits a single media item inside a max 326x436 content box on
// web and 309x436 on mobile, preserving its aspect ratio, and the card
// then adopts the resulting box — so `object-fit: cover` performs no
// crop and nothing is letterboxed.
//
// The fit is `width = min(widthCap, heightCap * ratio)`, with
// `aspect-ratio` supplying the other axis. Only the width cap is
// breakpoint-dependent, so it travels as a custom property while the
// ratio-derived term resolves to a concrete pixel value. Together they
// reproduce all eight measured frames (`2032:22651` / `23000` /
// `23353` / `23707` plus their mobile counterparts) to within Figma's
// float rounding — including the 9:16 portrait, which renders 246x436
// on web *and* mobile precisely because the height cap is NOT
// breakpoint-dependent: a width-only rule would give mobile 309x547.6.
//
// The property name is spelled out again inside `SINGLE_WIDTH_CAPS` on
// purpose. Tailwind extracts classes by scanning source text, so an
// interpolated `` `[${SINGLE_WIDTH_CAP_PROPERTY}:309px]` `` would emit
// no CSS at all and silently drop the width cap. Keep it literal.
const SINGLE_WIDTH_CAP_PROPERTY = '--mes-single-width-cap'
const SINGLE_WIDTH_CAPS =
  '[--mes-single-width-cap:309px] sm:[--mes-single-width-cap:326px]'
const SINGLE_HEIGHT_CAP_PX = 436

/**
 * Adaptive grid used by stacked image / video attachments. Layouts:
 *
 *   - 1 tile  — full-bleed
 *   - 2 tiles — equal-width side-by-side row
 *   - 3 tiles — one large left tile + two stacked right tiles
 *   - 4 tiles — 2×2 grid
 *   - 5+      — 2×2 grid with the bottom-right tile showing "+N more"
 *
 * A single tile adopts `singleAspectRatio` when supplied and otherwise
 * stays square; 2 tiles sit in a 16:9 row, 3 in a 4:3 box, and 4 in
 * the 379x286 content box Figma draws for a stacked media card. Only
 * the cluster's four outer corners round — see `OUTER_CORNER_CLASSES`.
 */
const MediaStackGrid: React.FC<MediaStackGridProps> = ({
  tiles,
  onTileActivate,
  maxVisible = MEDIA_STACK_MAX_VISIBLE,
  singleAspectRatio,
  className,
}) => {
  const total = tiles.length
  if (total === 0) return null

  const visible = tiles.slice(0, Math.min(total, maxVisible))
  const overflow = total - visible.length
  const overflowBadge =
    overflow > 0 ? (
      // `aria-hidden` because the tile's own label already reads
      // "… 4 of 9" — announcing "+5" on top of that just makes the
      // user do the arithmetic twice.
      <div
        aria-hidden
        className="absolute inset-0 flex items-center justify-center bg-[#040403]/50 text-2xl font-bold leading-[28px] tracking-[-0.24px] text-white"
      >
        +{overflow}
      </div>
    ) : null

  const renderTile = (
    tile: MediaStackTile,
    index: number,
    extra?: React.ReactNode
  ) => {
    const sharedClass = classNames(
      TILE_SHELL,
      'h-full w-full',
      OUTER_CORNER_CLASSES[visible.length]?.[index]
    )
    if (onTileActivate) {
      return (
        <button
          type="button"
          key={index}
          onClick={() => onTileActivate(index)}
          aria-label={tile.ariaLabel ?? `Open media ${index + 1}`}
          className={classNames(
            sharedClass,
            tile.cursorClassName ?? 'cursor-zoom-in'
          )}
        >
          {tile.content}
          {extra}
        </button>
      )
    }
    return (
      <div key={index} className={sharedClass}>
        {tile.content}
        {extra}
      </div>
    )
  }

  let singleTileStyle: React.CSSProperties | undefined
  if (singleAspectRatio) {
    singleTileStyle = {
      aspectRatio: singleAspectRatio,
      // Hundredths keep the emitted declaration readable in devtools;
      // the rounding error is well under a device pixel.
      width: `min(var(${SINGLE_WIDTH_CAP_PROPERTY}), ${
        Math.round(SINGLE_HEIGHT_CAP_PX * singleAspectRatio * 100) / 100
      }px)`,
      // Only bites in a container narrower than the width cap.
      maxWidth: '100%',
    }
  }

  if (visible.length === 1) {
    return (
      <div
        className={classNames(
          singleAspectRatio ? SINGLE_WIDTH_CAPS : 'aspect-square w-full',
          className
        )}
        style={singleTileStyle}
      >
        {renderTile(visible[0], 0, overflowBadge)}
      </div>
    )
  }

  if (visible.length === 2) {
    return (
      <div
        className={classNames(
          'grid aspect-[16/9] w-full grid-cols-2 gap-0.5',
          className
        )}
      >
        {visible.map((tile, index) =>
          renderTile(
            tile,
            index,
            index === visible.length - 1 ? overflowBadge : null
          )
        )}
      </div>
    )
  }

  if (visible.length === 3) {
    return (
      <div
        className={classNames(
          'grid aspect-[4/3] w-full grid-cols-2 grid-rows-2 gap-0.5',
          className
        )}
      >
        <div className="row-span-2">{renderTile(visible[0], 0)}</div>
        {renderTile(visible[1], 1)}
        {renderTile(visible[2], 2, overflowBadge)}
      </div>
    )
  }

  // 4 tiles (or 4 visible with overflow on the last)
  return (
    <div
      className={classNames(
        // 379x286 is the content box of Figma's 383x290 stacked-media
        // card (`attachment-single-image` 1973:13863) once its 2px
        // frame is removed. Pinning the aspect on the container — as
        // this branch has always done — makes the card resolve to
        // exactly 383x290, and the 2px gutters then split it into
        // 188.5x142 tiles (Figma draws 188x142; its own 383 does not
        // divide evenly, so half a pixel goes to the columns).
        'grid aspect-[379/286] w-full grid-cols-2 grid-rows-2 gap-0.5',
        className
      )}
    >
      {visible.map((tile, index) => {
        const isLastWithOverflow = overflow > 0 && index === visible.length - 1
        return renderTile(
          tile,
          index,
          isLastWithOverflow ? overflowBadge : null
        )
      })}
    </div>
  )
}

export default MediaStackGrid
