// Save a single image or video to the device photo library (Camera Roll on
// iOS, Gallery on Android). Remote Pixelfed/Mastodon CDN media can't be saved
// directly, so the file is downloaded into the cache dir, handed to the OS
// library, then the temp copy is removed.
//
// The service is intentionally side-effect-light: it returns a `SaveResult`
// rather than showing toasts/haptics, so it stays testable and the feedback
// idiom lives at the call site (`useSaveMedia` / `SaveableMedia`). Pure URL +
// extension resolution lives in `./saveTarget` (no native imports).

import { File, Paths } from 'expo-file-system'
import * as MediaLibrary from 'expo-media-library'
import { ensureSavePhotosPermission, PermissionDeniedError } from './permissions'
import {
  inferExtension,
  resolveSaveTarget,
  type SaveInput,
  type SaveMediaKind,
} from './saveTarget'

export type { SaveInput, SaveMediaKind } from './saveTarget'

export type SaveResult =
  | { ok: true }
  // permission: add-only grant denied · download: fetch to cache failed ·
  // save: MediaLibrary write failed · unsupported: nothing savable (e.g. HLS).
  | { ok: false; reason: 'permission' | 'download' | 'save' | 'unsupported' }

// djb2 — a stable, filesystem-safe token when the attachment has no id.
function hashToken(input: string): string {
  let h = 5381
  for (let i = 0; i < input.length; i++) h = (h * 33) ^ input.charCodeAt(i)
  return (h >>> 0).toString(36)
}

function cacheFilename(url: string, kind: SaveMediaKind, id?: string): string {
  const token = (id || hashToken(url)).replace(/[^a-zA-Z0-9_-]/g, '') || 'media'
  return `halflight-save-${token}.${inferExtension(url, kind)}`
}

async function downloadToCache(url: string, filename: string): Promise<File> {
  const dest = new File(Paths.cache, filename)
  if (dest.exists) {
    try {
      dest.delete()
    } catch {}
  }
  try {
    return await File.downloadFileAsync(url, dest)
  } catch {
    // Legacy fallback for builds/devices where the static API throws.
    const legacy = await import('expo-file-system/legacy')
    const res = await legacy.downloadAsync(url, dest.uri)
    return new File(res.uri)
  }
}

export async function saveMediaToPhotos(input: SaveInput): Promise<SaveResult> {
  const target = resolveSaveTarget(input)
  if ('unsupported' in target) return { ok: false, reason: 'unsupported' }

  try {
    await ensureSavePhotosPermission()
  } catch (e) {
    // PermissionDeniedError or any unexpected permission failure → treat as a
    // denied grant; the call site routes the user toward Settings.
    if (!(e instanceof PermissionDeniedError)) {
      // eslint-disable-next-line no-console
      console.warn('[saveToPhotos] permission error', e)
    }
    return { ok: false, reason: 'permission' }
  }

  const filename = cacheFilename(target.url, target.kind, input.attachment?.id)
  let file: File | null = null
  try {
    try {
      file = await downloadToCache(target.url, filename)
    } catch {
      return { ok: false, reason: 'download' }
    }
    try {
      // saveToLibraryAsync is the add-only-compatible write (works with the
      // write-only permission). It is marked deprecated in favour of
      // Asset.create(), but that needs FULL library access (it reads the asset
      // back) — so for add-only we must keep this. Do not "fix" the deprecation.
      await MediaLibrary.saveToLibraryAsync(file.uri)
    } catch {
      return { ok: false, reason: 'save' }
    }
    return { ok: true }
  } finally {
    // Best-effort cleanup so saves don't accumulate in the LRU media cache.
    if (file?.exists) {
      try {
        file.delete()
      } catch {}
    }
  }
}
