/**
 * Offline-Karten: Kacheln des Einsatzgebiets vorladen.
 *
 * Der Service Worker (public/sw.js) cacht Karten-Kacheln bereits Cache-First —
 * aber nur solche, die man schon einmal angeschaut hat. Diese Bibliothek lädt
 * beim Öffnen eines Einsatzes das Gebiet rund um den Einsatzort proaktiv,
 * damit die Karte auch ohne Empfang (Keller, Funkloch) vollständig da ist.
 *
 * Wichtig: MapLibre wählt die OSM-Subdomain deterministisch über
 * (x + y) % anzahl — das Vorladen nutzt exakt dieselbe Formel, damit die
 * Cache-Keys übereinstimmen.
 */

// Muss mit TILE_CACHE in public/sw.js übereinstimmen (Fallback, falls der
// Service Worker die Seite (noch) nicht kontrolliert)
const TILE_CACHE = 'lageplan-tiles-v3'

const OSM_SUBDOMAINS = ['a', 'b', 'c']

/** Web-Mercator: Länge/Breite → Kachel-Koordinaten */
function lonToTileX(lon: number, z: number): number {
  return Math.floor(((lon + 180) / 360) * Math.pow(2, z))
}
function latToTileY(lat: number, z: number): number {
  const rad = (lat * Math.PI) / 180
  return Math.floor(((1 - Math.log(Math.tan(rad) + 1 / Math.cos(rad)) / Math.PI) / 2) * Math.pow(2, z))
}

/** Kachelbreite in Metern am gegebenen Breitengrad */
function tileSizeMeters(lat: number, z: number): number {
  return (40075016.686 * Math.cos((lat * Math.PI) / 180)) / Math.pow(2, z)
}

export interface PreloadResult {
  total: number
  cached: number
  failed: number
  skipped: boolean
}

export interface PreloadOptions {
  /** Radius um den Einsatzort in Metern (Standard 800) */
  radiusM?: number
  /** Zoom-Bereich (Standard 13–18) */
  minZoom?: number
  maxZoom?: number
  /** Obergrenze Kachelanzahl als Schutz (Standard 400) */
  maxTiles?: number
  /** Fortschritts-Callback (0..1) */
  onProgress?: (done: number, total: number) => void
}

/** Alle Kachel-URLs für das Gebiet berechnen (OSM, MapLibre-Subdomain-Schema) */
function computeTileUrls(lng: number, lat: number, opts: Required<Pick<PreloadOptions, 'radiusM' | 'minZoom' | 'maxZoom' | 'maxTiles'>>): string[] {
  const urls: string[] = []
  for (let z = opts.minZoom; z <= opts.maxZoom; z++) {
    const sizeM = tileSizeMeters(lat, z)
    const tileRadius = Math.max(1, Math.ceil(opts.radiusM / sizeM))
    const cx = lonToTileX(lng, z)
    const cy = latToTileY(lat, z)
    const max = Math.pow(2, z) - 1
    for (let x = cx - tileRadius; x <= cx + tileRadius; x++) {
      for (let y = cy - tileRadius; y <= cy + tileRadius; y++) {
        if (x < 0 || y < 0 || x > max || y > max) continue
        const sub = OSM_SUBDOMAINS[Math.abs(x + y) % OSM_SUBDOMAINS.length]
        urls.push(`https://${sub}.tile.openstreetmap.org/${z}/${x}/${y}.png`)
        if (urls.length >= opts.maxTiles) return urls
      }
    }
  }
  return urls
}

/**
 * Kacheln rund um den Einsatzort vorladen.
 * Läuft im Hintergrund mit begrenzter Parallelität; bereits gecachte Kacheln
 * werden übersprungen (kein unnötiger Traffic für den OSM-Server).
 */
export async function preloadTilesForArea(lng: number, lat: number, options: PreloadOptions = {}): Promise<PreloadResult> {
  const opts = {
    radiusM: options.radiusM ?? 800,
    minZoom: options.minZoom ?? 13,
    maxZoom: options.maxZoom ?? 18,
    maxTiles: options.maxTiles ?? 400,
  }

  if (typeof window === 'undefined' || !('caches' in window) || !navigator.onLine) {
    return { total: 0, cached: 0, failed: 0, skipped: true }
  }

  const urls = computeTileUrls(lng, lat, opts)
  const cache = await caches.open(TILE_CACHE)
  // Der SW cacht selbst, wenn er die Seite kontrolliert — sonst übernehmen wir das
  const swControls = !!navigator.serviceWorker?.controller

  let cached = 0
  let failed = 0
  let done = 0

  const worker = async (url: string) => {
    try {
      const existing = await cache.match(url)
      if (existing) {
        cached++
        return
      }
      const res = await fetch(url, { mode: 'cors' })
      if (res.ok) {
        if (!swControls) {
          await cache.put(url, res.clone())
        }
        cached++
      } else {
        failed++
      }
    } catch {
      failed++
    } finally {
      done++
      options.onProgress?.(done, urls.length)
    }
  }

  // Begrenzte Parallelität (6), um Gerät und Tile-Server zu schonen
  const CONCURRENCY = 6
  const queue = [...urls]
  await Promise.all(
    Array.from({ length: CONCURRENCY }, async () => {
      while (queue.length > 0) {
        const url = queue.shift()
        if (!url) break
        await worker(url)
      }
    })
  )

  return { total: urls.length, cached, failed, skipped: false }
}

/** Marker, damit pro Einsatz nur einmal (bzw. alle 14 Tage) vorgeladen wird */
export function shouldPreloadForProject(projectId: string): boolean {
  try {
    const raw = localStorage.getItem(`lageplan-tiles-${projectId}`)
    if (!raw) return true
    const ts = parseInt(raw, 10)
    return !Number.isFinite(ts) || Date.now() - ts > 14 * 24 * 60 * 60 * 1000
  } catch {
    return true
  }
}

export function markPreloadedForProject(projectId: string) {
  try {
    localStorage.setItem(`lageplan-tiles-${projectId}`, String(Date.now()))
  } catch { /* ignore */ }
}
