import type { VideoSource } from 'expo-video'
import type { MediaAttachment, Status } from './api-types'

/**
 * Mastodon Pass §3.4: Pixelfed branches the carousel on `Status.pf_type`
 * (`'video'` / `'video:album'`) — Mastodon never populates `pf_type`, so
 * Mastodon-context posts must fall back to the standard
 * `MediaAttachment.type` field (`'video'` / `'gifv'`).
 *
 * `gifv` is included because Mastodon's `gifv` looks like a silent looping
 * video to the user and renders through the same `<VideoPlayer>` path; on
 * Pixelfed `gifv` is rare and the existing carousel already treats it as a
 * video source via the same code path.
 */
export function isVideoPost(
  post: Pick<Status, 'pf_type' | 'media_attachments'>
): boolean {
  if (post.pf_type === 'video') return true
  const t = post.media_attachments?.[0]?.type
  return t === 'video' || t === 'gifv'
}

// When a federated post crosses servers, Mastodon caches the upstream
// Pixelfed media at `MediaAttachment.url` and keeps the original at
// `remote_url`. The local cache 404s while the worker hasn't pulled yet,
// after the media-pruning sweep, or whenever object storage is misconfigured
// — every one of which renders as the white-image symptom users see.
// `remote_url` is the Pixelfed CDN URL the device can fetch directly; for
// Pixelfed-local media it's null and we fall back to `url`.
//
// Image-only on purpose: video / gifv `remote_url` may be a format the
// player can't decode, and Mastodon usually serves the transcoded MP4
// locally. Cache-key impact is benign and intentional — two accounts on
// different home servers viewing the same upstream Pixelfed post share the
// disk cache (URL-derived `cacheKey`, `__tests__/cacheKeyContract.ts`).
export function toImageUri(m: MediaAttachment): string {
  if ((m.type === 'image' || m.type === 'unknown') && m.remote_url) {
    return m.remote_url
  }
  return m.url
}

// Intrinsic aspect ratio (width / height) of an attachment, derived from
// the server-reported `meta.original` dimensions. Callers size containers
// from this via `style={{ width: '100%', aspectRatio }}` so the renderer
// picks up the actual parent width without re-render math.
//
// Why this exists: every "post-shaped" media slot — single feed image,
// carousel slides, quoted-status thumb, event cover, article hero — needs
// the same derivation. Hard-coded heights produce one of two failures:
// black bars (contentFit=contain on an over-tall container) or cutoff
// (contentFit=cover on an under-tall container). Sizing by intrinsic
// aspect sidesteps both.
// Intrinsic aspect ratio (width / height) from the server-reported
// `meta.original` dimensions, or `null` when those dims are missing/invalid.
// The `null` is the signal a caller needs when it can recover a real aspect
// some other way — VideoPlayer measures its poster frame when this returns
// `null`, so a video whose server dims are absent still fills the width at its
// true aspect instead of pillarboxing inside a guessed box. Callers that just
// need a number up front use `getMediaAspectRatio`, which folds in a fallback.
export function getIntrinsicAspectRatio(
  attachment: MediaAttachment | null | undefined
): number | null {
  const w = attachment?.meta?.original?.width
  const h = attachment?.meta?.original?.height
  if (typeof w === 'number' && typeof h === 'number' && w > 0 && h > 0) {
    return w / h
  }
  return null
}

export function getMediaAspectRatio(
  attachment: MediaAttachment | null | undefined,
  fallback = 4 / 3
): number {
  return getIntrinsicAspectRatio(attachment) ?? fallback
}

// Pixel height for a "post-shaped" media slot at a known pixel `width`.
// Single sizing path for every full-width / leaf renderer: derive the
// intrinsic aspect via `getMediaAspectRatio`, then `height = width / aspect`,
// optionally capped at `maxHeight` so portrait media can't dominate a narrow
// embedded card (quoted status, thread comment).
//
// `fallbackAspect` (default 4/3, matching `getMediaAspectRatio`) is the one
// documented fallback for attachments missing server-reported dimensions. It
// replaces FeedPost's old hand-rolled 430/500 pixel-height fallbacks, so a
// feed post and its own quoted/comment embed now size through identical math
// instead of two constants.
export function mediaHeight(
  attachment: MediaAttachment | null | undefined,
  width: number,
  opts?: { maxHeight?: number; fallbackAspect?: number }
): number {
  const aspect = getMediaAspectRatio(attachment, opts?.fallbackAspect)
  const height = width / aspect
  const maxHeight = opts?.maxHeight
  return typeof maxHeight === 'number' && height > maxHeight ? maxHeight : height
}

// Builds an expo-video VideoSource from a Pixelfed MediaAttachment. Callers
// stay agnostic of stream type — this helper is the only place that decides
// HLS vs progressive. Pixelfed's current Mastodon-compat payload exposes only
// `url`, but some forks add an `.m3u8` manifest or a dedicated manifest field;
// we try to be forward-compatible without branching at call sites.
//
// expo-video autodetects HLS when the URL ends in `.m3u8`; for non-suffixed
// manifest URLs we set contentType explicitly. Accepted caveat: on iOS the
// persistent video cache does not apply to HLS sources — progressive `.mp4`
// keeps the offline cache, HLS trades the cache for adaptive bitrate.
//
// `useCaching: true` opts the source into expo-video's persistent LRU disk
// cache (cap set centrally by `applyVideoCacheCap` at boot). Pass
// unconditionally — the platform makes the right call: no-op on iOS HLS,
// honoured on Android HLS, honoured on progressive MP4 on both.
export function toVideoSource(attachment: MediaAttachment): VideoSource {
  const manifestField = (attachment as unknown as { manifest_url?: string }).manifest_url
  if (manifestField) {
    return manifestField.endsWith('.m3u8')
      ? { uri: manifestField, useCaching: true }
      : { uri: manifestField, contentType: 'hls', useCaching: true }
  }
  return { uri: attachment.url, useCaching: true }
}
