import type { MediaLimits } from './limits'
import { isServerPassthroughImage } from './transcode'
import type { RawAsset } from './types'

/** Photos: how many ×limit a source can be before lossy resize is no longer
 *  a sane shrink. Above this, re-export at lower quality is the right answer.
 *  Empirically `expo-image-manipulator` at q=0.85 + longest-edge 4096 reliably
 *  takes a 30 MB source down to ~5 MB; beyond that the JPEG quantisation
 *  starts losing perceptible detail before the byte target is hit. */
export const RESIZE_REASONABLE_RATIO = 6

/** Videos: react-native-compressor's `compressionMethod: 'auto'` tops out
 *  around a 4× shrink before the bitrate hits a quality cliff. Above this we
 *  surface the trim hint instead of grinding for 120 s and failing. */
export const COMPRESS_REASONABLE_RATIO = 4

/** Tagged outcome of `classifyForUpload`. Each tag carries the data the
 *  consent sheet needs to render the row without re-deriving sizes. */
export type Classification =
  | { tag: 'within_limits'; raw: RawAsset; sizeBytes: number | null }
  | { tag: 'will_transcode_only'; raw: RawAsset; sizeBytes: number | null }
  | {
      tag: 'will_resize'
      raw: RawAsset
      sizeBytes: number
      targetBytes: number
    }
  | {
      tag: 'will_compress'
      raw: RawAsset
      sizeBytes: number
      targetBytes: number
    }
  | {
      tag: 'too_big_to_resize'
      raw: RawAsset
      sizeBytes: number
      targetBytes: number
      ratio: number
      isAnimatedGif: boolean
    }
  | {
      tag: 'too_big_to_compress'
      raw: RawAsset
      sizeBytes: number
      targetBytes: number
      ratio: number
    }
  | {
      tag: 'unsupported_mime'
      raw: RawAsset
      sizeBytes: number | null
    }

export type ClassifyOptions = {
  /** The destination instance's `media_attachments.supported_mime_types`. Pass
   *  through from `useInstanceCapabilities()` so we know whether HEIC/AVIF
   *  will land via transcode or be hard-rejected. */
  supportedImageMimeTypes?: string[] | null
  /** Override the photo "too big to resize" ratio. Default `RESIZE_REASONABLE_RATIO`. */
  resizeReasonableRatio?: number
  /** Override the video "too big to compress" ratio. Default `COMPRESS_REASONABLE_RATIO`. */
  compressReasonableRatio?: number
}

/** Pure classifier — no FS reads, no React, no expo. The caller fills
 *  `raw.size` from the picker (`asset.fileSize`) before calling; when the
 *  picker can't stat (Android edge case, sandboxed share intent) we treat
 *  the asset as `within_limits` rather than false-positive on a transient
 *  miss. Mirrors `resizeImage`'s "size === 0 → ship it" guard at
 *  `src/lib/media/resize.ts:50`. */
export function classifyForUpload(
  raw: RawAsset,
  limits: MediaLimits,
  opts: ClassifyOptions = {}
): Classification {
  const sizeBytes = typeof raw.size === 'number' && raw.size > 0 ? raw.size : null

  if (raw.kind === 'photo') {
    return classifyPhoto(raw, sizeBytes, limits, opts)
  }
  return classifyVideo(raw, sizeBytes, limits, opts)
}

function classifyPhoto(
  raw: RawAsset,
  sizeBytes: number | null,
  limits: MediaLimits,
  opts: ClassifyOptions
): Classification {
  const passthrough = isServerPassthroughImage(raw.mimeType, opts.supportedImageMimeTypes)
  const lower = raw.mimeType.toLowerCase()
  const isAnimatedGif = lower === 'image/gif'

  // Hard reject only fires if the server publishes a `supported_mime_types`
  // list AND ours isn't on it AND the format isn't transcodable. Today
  // `transcodeImage` produces JPEG or WebP; if the server lists neither we
  // can't help, so reject. In practice this is virtually never hit (every
  // sane Pixelfed/Mastodon publishes JPEG); kept for the misconfigured edge.
  if (
    !passthrough &&
    opts.supportedImageMimeTypes &&
    !opts.supportedImageMimeTypes.includes('image/jpeg') &&
    !opts.supportedImageMimeTypes.includes('image/webp')
  ) {
    return { tag: 'unsupported_mime', raw, sizeBytes }
  }

  if (sizeBytes === null) {
    // Couldn't stat — match the resize.ts:50 fallback: ship it, server's own
    // limit is the safety net. A non-passthrough format still needs transcode
    // (HEIC → JPEG); we just can't promise a resize.
    return passthrough
      ? { tag: 'within_limits', raw, sizeBytes }
      : { tag: 'will_transcode_only', raw, sizeBytes }
  }

  if (sizeBytes <= limits.maxImageBytes) {
    return passthrough
      ? { tag: 'within_limits', raw, sizeBytes }
      : { tag: 'will_transcode_only', raw, sizeBytes }
  }

  // Oversized animated GIF: re-encoding silently drops frames (resize.ts:46).
  // Treat as too-big — the user must convert to MP4 or pick a smaller GIF.
  if (isAnimatedGif) {
    return {
      tag: 'too_big_to_resize',
      raw,
      sizeBytes,
      targetBytes: limits.maxImageBytes,
      ratio: sizeBytes / limits.maxImageBytes,
      isAnimatedGif: true,
    }
  }

  const ratio = sizeBytes / limits.maxImageBytes
  const reasonable = opts.resizeReasonableRatio ?? RESIZE_REASONABLE_RATIO
  if (ratio > reasonable) {
    return {
      tag: 'too_big_to_resize',
      raw,
      sizeBytes,
      targetBytes: limits.maxImageBytes,
      ratio,
      isAnimatedGif: false,
    }
  }

  return {
    tag: 'will_resize',
    raw,
    sizeBytes,
    targetBytes: limits.maxImageBytes,
  }
}

function classifyVideo(
  raw: RawAsset,
  sizeBytes: number | null,
  limits: MediaLimits,
  opts: ClassifyOptions
): Classification {
  // Video transcode-only case: non-mp4 source within the byte cap. The
  // pipeline still runs `compressVideo` (forceMp4 remux), but the user
  // doesn't need to consent to that — it's housekeeping like HEIC→JPEG.
  const isMp4 = raw.mimeType.toLowerCase() === 'video/mp4'

  if (sizeBytes === null) {
    // Picker didn't report size (Android edge). Don't false-positive — let
    // the post-compression `video_too_large` path handle the fallback.
    return isMp4
      ? { tag: 'within_limits', raw, sizeBytes }
      : { tag: 'will_transcode_only', raw, sizeBytes }
  }

  if (sizeBytes <= limits.maxVideoBytes) {
    return isMp4
      ? { tag: 'within_limits', raw, sizeBytes }
      : { tag: 'will_transcode_only', raw, sizeBytes }
  }

  const ratio = sizeBytes / limits.maxVideoBytes
  const reasonable = opts.compressReasonableRatio ?? COMPRESS_REASONABLE_RATIO
  if (ratio > reasonable) {
    return {
      tag: 'too_big_to_compress',
      raw,
      sizeBytes,
      targetBytes: limits.maxVideoBytes,
      ratio,
    }
  }

  return {
    tag: 'will_compress',
    raw,
    sizeBytes,
    targetBytes: limits.maxVideoBytes,
  }
}

/** True iff at least one item needs the user's input before processing.
 *  Used by the consent-sheet gate in `useComposer`. Transcode-only items
 *  (HEIC→JPEG, MOV→MP4 within budget) are silent housekeeping. */
export function needsConsent(items: Classification[]): boolean {
  return items.some((c) => c.tag !== 'within_limits' && c.tag !== 'will_transcode_only')
}

/** True iff at least one item is unrecoverable without the user removing or
 *  re-acquiring it — the sheet's primary commit button stays disabled while
 *  any of these are present. */
export function hasBlockingItem(items: Classification[]): boolean {
  return items.some(
    (c) =>
      c.tag === 'too_big_to_resize' ||
      c.tag === 'too_big_to_compress' ||
      c.tag === 'unsupported_mime'
  )
}
