import { ImageManipulator, SaveFormat } from 'expo-image-manipulator'

/** Image MIME types every Pixelfed/Mastodon backend we target accepts as-is.
 *
 *  - Pixelfed default `MEDIA_TYPES` is `image/jpeg,image/jpg,image/png,image/gif`
 *    (`config/pixelfed.php`). Admins can opt into webp/avif/heic in admin
 *    settings, but the *default* set is what an unconfigured instance ships.
 *  - Mastodon's `MediaAttachment::IMAGE_MIME_TYPES` is the superset
 *    (jpeg/png/gif + webp + heic/heif/avif), but heic/heif/avif sit in
 *    `IMAGE_CONVERTIBLE_MIME_TYPES` — the server transcodes them via libvips,
 *    which fails when the host lacks libheif. The "Error processing thumbnail
 *    for upload" report is exactly that path falling over.
 *
 *  GIF stays in this universal set so animated frames survive — re-encoding
 *  to JPEG/WebP-still would silently drop animation.
 */
const UNIVERSAL_PASSTHROUGH_IMAGE_MIME_TYPES = new Set([
  'image/jpeg',
  'image/jpg',
  'image/png',
  'image/gif',
])

/** Output format we re-encode to when the source isn't passthrough. WebP is
 *  preferred when the server's published `supported_mime_types` includes it —
 *  ~25–35% smaller than JPEG at equivalent quality, native on Mastodon, and
 *  opt-in on Pixelfed. JPEG is the safe fallback. */
export type UploadImageFormat = 'jpeg' | 'webp'

const FORMAT_TO_MIME: Record<UploadImageFormat, string> = {
  jpeg: 'image/jpeg',
  webp: 'image/webp',
}

const FORMAT_TO_SAVE_FORMAT: Record<UploadImageFormat, SaveFormat> = {
  jpeg: SaveFormat.JPEG,
  webp: SaveFormat.WEBP,
}

/** Picks the best output format given what the destination instance accepts.
 *  Optimistic for `webp` only when we have a positive signal — null
 *  (capability cache cold or fetch failed) defaults to `jpeg` so a fresh
 *  install never bricks its first upload against an instance that doesn't
 *  publish a list, or one that publishes a list without WebP. */
export function selectUploadFormat(
  supportedMimeTypes: string[] | null | undefined
): UploadImageFormat {
  if (!supportedMimeTypes) return 'jpeg'
  return supportedMimeTypes.includes('image/webp') ? 'webp' : 'jpeg'
}

/** True iff the source MIME can be uploaded as-is to the destination instance.
 *  jpeg/png/gif are always passthrough. webp is passthrough only when the
 *  server explicitly accepts it; otherwise we transcode to the picked format. */
export function isServerPassthroughImage(
  mimeType: string,
  supportedMimeTypes: string[] | null | undefined
): boolean {
  const lower = mimeType.toLowerCase()
  if (UNIVERSAL_PASSTHROUGH_IMAGE_MIME_TYPES.has(lower)) return true
  if (lower === 'image/webp' && supportedMimeTypes?.includes('image/webp')) return true
  return false
}

export function mimeTypeFor(format: UploadImageFormat): string {
  return FORMAT_TO_MIME[format]
}

export type TranscodeImageOptions = {
  /** Resize the longest edge to this width before encoding. Omit to skip. */
  resizeToWidth?: number
  /** Encoder quality 0..1. Default 0.85. JPEG/WebP both honour this. */
  quality?: number
}

export type TranscodeResult = {
  uri: string
  width: number
  height: number
  mimeType: string
}

const DEFAULT_QUALITY = 0.85

/** Re-encodes any expo-image-manipulator-readable image to the requested
 *  format. Resizes only when `resizeToWidth` is set. Returns the new URI plus
 *  output dimensions and the resolved MIME so callers can splat the result
 *  into a `RawAsset` without re-deriving any of those. */
export async function transcodeImage(
  uri: string,
  format: UploadImageFormat,
  opts: TranscodeImageOptions = {}
): Promise<TranscodeResult> {
  let pipeline = ImageManipulator.manipulate(uri)
  if (opts.resizeToWidth) {
    pipeline = pipeline.resize({ width: opts.resizeToWidth })
  }
  const ref = await pipeline.renderAsync()
  const saved = await ref.saveAsync({
    format: FORMAT_TO_SAVE_FORMAT[format],
    compress: opts.quality ?? DEFAULT_QUALITY,
  })
  return {
    uri: saved.uri,
    width: saved.width,
    height: saved.height,
    mimeType: FORMAT_TO_MIME[format],
  }
}
