import { Command } from '@cliffy/command'
import { walk } from '@std/fs'
import { join, relative } from '@std/path'
import { POLY_DIR, TILES_MANIFEST, TILES_OUT_DIR } from '../shared/paths.ts'

type TileEntry = {
  id: string
  label: string
  description: string
  filename: string
  path: string
  group: string
  bounds?: [number, number, number, number]
}

type GroupEntry = {
  id: string
  label: string
  group: string
  bounds?: [number, number, number, number]
}

function slugToLabel(slug: string): string {
  if (slug === 'us') return 'US'
  return slug.split('-').map((w) => w[0].toUpperCase() + w.slice(1)).join(' ')
}

function formatSize(bytes: number): string {
  if (bytes < 1024 * 1024) {
    return `~${Math.round(bytes / 1024)} KB`
  }
  return `~${Math.round(bytes / (1024 * 1024))} MB`
}

async function bboxFromPoly(
  region: string,
): Promise<[number, number, number, number] | null> {
  const parts = region.split('/')
  const polyPath = join(
    POLY_DIR,
    ...parts.slice(0, -1),
    `${parts[parts.length - 1]}.poly`,
  )
  try {
    const text = await Deno.readTextFile(polyPath)
    const coords = text.split('\n').flatMap((line) => {
      const parts = line.trim().split(/\s+/)
      if (parts.length !== 2) return []
      const lon = parseFloat(parts[0])
      const lat = parseFloat(parts[1])
      return isNaN(lon) || isNaN(lat) ? [] : [{ lon, lat }]
    })
    if (!coords.length) return null
    const west = Math.min(...coords.map((c) => c.lon))
    const south = Math.min(...coords.map((c) => c.lat))
    const east = Math.max(...coords.map((c) => c.lon))
    const north = Math.max(...coords.map((c) => c.lat))
    return [west, south, east, north]
  } catch { /* no poly file */ }
  return null
}

function buildGroupEntries(tiles: TileEntry[]): GroupEntry[] {
  const childGroups = new Set<string>()
  for (const tile of tiles) {
    if (tile.group) childGroups.add(tile.group)
  }

  const groupEntries: GroupEntry[] = []
  const seen = new Set<string>()

  for (const childGroup of childGroups) {
    const parts = childGroup.split('/')
    if (parts.length < 2) continue

    const parentGroup = parts.slice(0, -1).join('/')
    const id = parts[parts.length - 1]
    const key = `${parentGroup}/${id}`
    if (seen.has(key)) continue
    seen.add(key)

    const groupTiles = tiles.filter((t) => t.group === childGroup)
    let west = 180, south = 90, east = -180, north = -90
    let hasBounds = false
    for (const tile of groupTiles) {
      if (tile.bounds) {
        const [w, s, e, n] = tile.bounds
        west = Math.min(west, w)
        south = Math.min(south, s)
        east = Math.max(east, e)
        north = Math.max(north, n)
        hasBounds = true
      }
    }

    groupEntries.push({
      id,
      label: slugToLabel(id),
      group: parentGroup,
      bounds: hasBounds ? [west, south, east, north] : undefined,
    })
  }

  return groupEntries
}

async function rebuildManifest(): Promise<void> {
  const tiles: TileEntry[] = []

  for await (const entry of walk(TILES_OUT_DIR)) {
    if (!entry.isFile || !entry.name.endsWith('.pmtiles')) continue

    const rel = relative(TILES_OUT_DIR, entry.path)
    const parts = rel.split('/')
    const basename = parts[parts.length - 1].replace('.pmtiles', '')
    const id = basename.replaceAll('_', '-')
    const group = parts.slice(0, -1).join('/')

    const { size } = await Deno.stat(entry.path)
    const region = [...parts.slice(0, -1), basename].join('/')
    const bounds = await bboxFromPoly(region)

    tiles.push({
      id,
      label: slugToLabel(id),
      description: `Detailed street map · ${formatSize(size)}`,
      filename: rel,
      path: `/static/tiles/${rel}`,
      group,
      bounds: bounds ?? undefined,
    })
  }

  tiles.sort((a, b) => a.filename.localeCompare(b.filename))

  const groupEntries = buildGroupEntries(tiles)
  const allEntries = [...tiles, ...groupEntries]

  await Deno.writeTextFile(TILES_MANIFEST, JSON.stringify(allEntries) + '\n')

  console.log(`Wrote ${tiles.length} tiles and ${groupEntries.length} groups`)
}

export const updateCmd = new Command()
  .name('update')
  .description(
    'Rebuild tiles.json from .pmtiles files in www/static/tiles. ' +
      'Overwrites the entire manifest with entries derived from the file tree.',
  )
  .action(rebuildManifest)
