import { appStorage } from 'src/state/storage'

// Two-layer cache lets the hot render path stay synchronous: read one MMKV
// string, decide render-or-not. Bytes live in expo-image's URL-keyed disk
// cache (same cache as avatars and post media), so we don't reinvent image
// storage. Host-keyed, not account-keyed — two signed-in accounts seeing the
// same external link share one fetch and one cached entry.

const CACHE_PREFIX = 'app.favicon.'
const CHECKED_AT_PREFIX = 'app.favicon.checkedAt.'

// Re-probe negatives after 30 days — servers add favicons over time.
const UNAVAILABLE_TTL_MS = 30 * 24 * 60 * 60 * 1000

export type FaviconStatus = 'unknown' | 'available' | 'unavailable'

// In-flight coalescing: every cell that mentions example.com on the first
// frame after an unknown-state read would otherwise fire a parallel probe.
const inflight = new Map<string, Promise<FaviconStatus>>()

export function faviconKey(host: string): string {
  return `${CACHE_PREFIX}${host}`
}

function checkedKey(host: string): string {
  return `${CHECKED_AT_PREFIX}${host}`
}

/**
 * The well-known `/favicon.ico` URL on `host`. expo-image follows redirects,
 * so a server that redirects `/favicon.ico` → `/favicons/32.png` is handled
 * transparently.
 */
export function faviconUrlFor(host: string): string {
  return `https://${host}/favicon.ico`
}

/** Synchronous status read for the hot render path. */
export function getFaviconStatus(host: string): FaviconStatus {
  const v = appStorage.getString(faviconKey(host))
  if (v === 'available') return 'available'
  if (v === 'unavailable') {
    // Re-probe stale negatives. The server may have added a favicon since.
    const checked = appStorage.getNumber(checkedKey(host)) ?? 0
    if (Date.now() - checked > UNAVAILABLE_TTL_MS) return 'unknown'
    return 'unavailable'
  }
  return 'unknown'
}

export function markFaviconAvailable(host: string): void {
  if (!host) return
  appStorage.set(faviconKey(host), 'available')
  appStorage.set(checkedKey(host), Date.now())
}

export function markFaviconUnavailable(host: string): void {
  if (!host) return
  appStorage.set(faviconKey(host), 'unavailable')
  appStorage.set(checkedKey(host), Date.now())
}

/**
 * Async probe — fire-and-forget from the inline component when it sees an
 * `unknown` host. HEAD avoids paying for the byte transfer on negative hits,
 * but some servers refuse HEAD on `/favicon.ico` (CDN quirks); fall back to a
 * `Range: bytes=0-0` GET on 405/501. Coalesced per host so a single feed with
 * 20 cells mentioning the same host triggers one round-trip, not 20.
 */
export async function probeFavicon(host: string): Promise<FaviconStatus> {
  if (!host) return 'unavailable'
  const existing = inflight.get(host)
  if (existing) return existing

  const probe = (async (): Promise<FaviconStatus> => {
    const url = faviconUrlFor(host)
    try {
      let res = await fetch(url, { method: 'HEAD' })
      if (res.status === 405 || res.status === 501) {
        // HEAD unsupported — fall through to a single-byte GET.
        res = await fetch(url, {
          method: 'GET',
          headers: { Range: 'bytes=0-0' },
        })
      }
      const ct = res.headers.get('content-type') ?? ''
      const ok =
        res.ok &&
        // Reject HTML 200s (a SPA index page served at every path is the
        // classic false-positive).
        !ct.includes('text/html') &&
        // Anything image-ish, `application/octet-stream` (some servers
        // serve ICO as that), or no content-type at all counts.
        (ct.startsWith('image/') || ct.includes('application/octet-stream') || ct === '')
      if (ok) {
        markFaviconAvailable(host)
        return 'available'
      }
      markFaviconUnavailable(host)
      return 'unavailable'
    } catch {
      markFaviconUnavailable(host)
      return 'unavailable'
    }
  })()

  inflight.set(host, probe)
  try {
    return await probe
  } finally {
    inflight.delete(host)
  }
}
