export const MAX_BBOX_DEGREES = 0.5

export function tileKey(row, col) {
  return `${row},${col}`
}

export function bboxToTileKey(bbox) {
  const row = Math.floor(bbox.south / MAX_BBOX_DEGREES)
  const col = Math.floor(bbox.west / MAX_BBOX_DEGREES)
  return tileKey(row, col)
}

function tileBox(row, col, step = MAX_BBOX_DEGREES) {
  const south = row * step
  const west = col * step
  return { south, west, north: south + step, east: west + step }
}

export function bboxFetchKey(bounds) {
  const zoom = bounds.zoom ?? 14
  const step = zoom >= 14 ? 0.025 : zoom >= 11 ? 0.1 : zoom >= 8 ? 0.25 : 1
  const q = v => Math.round(v / step) * step
  return [q(bounds.south), q(bounds.west), q(bounds.north), q(bounds.east)].join(',')
}

function ringOffsets(radius) {
  if (radius === 0) return [[0, 0]]
  return Array.from({ length: 2 * radius + 1 }, (_, i) => i - radius)
    .flatMap(dr => Array.from({ length: 2 * radius + 1 }, (_, j) => j - radius)
      .filter(dc => Math.abs(dr) === radius || Math.abs(dc) === radius)
      .map(dc => [dr, dc]))
}

function collectTiles(state) {
  const {
    centerRow, centerCol, minRow, maxRow, minCol, maxCol, step, limit, radius, seen, tiles,
  } = state
  if (tiles.length >= limit) return tiles

  const inBounds = (row, col) => row >= minRow && row <= maxRow && col >= minCol && col <= maxCol
  const { nextSeen, added } = ringOffsets(radius)
    .map(([dr, dc]) => [centerRow + dr, centerCol + dc])
    .filter(([row, col]) => inBounds(row, col))
    .reduce((acc, [row, col]) => {
      const key = `${row},${col}`
      if (acc.nextSeen.has(key)) return acc
      return {
        nextSeen: new Set([...acc.nextSeen, key]),
        added: [...acc.added, tileBox(row, col, step)],
      }
    }, { nextSeen: seen, added: [] })

  if (added.length === 0 && radius > 0) return tiles

  const nextTiles = [...tiles, ...added].slice(0, limit)
  if (nextTiles.length >= limit) return nextTiles
  return collectTiles({ ...state, radius: radius + 1, seen: nextSeen, tiles: nextTiles })
}

export function tilesNearCenter(bounds, limit) {
  const step = MAX_BBOX_DEGREES
  const lat = (bounds.south + bounds.north) / 2
  const lng = (bounds.west + bounds.east) / 2
  return collectTiles({
    centerRow: Math.floor(lat / step),
    centerCol: Math.floor(lng / step),
    minRow: Math.floor(bounds.south / step),
    maxRow: Math.ceil(bounds.north / step) - 1,
    minCol: Math.floor(bounds.west / step),
    maxCol: Math.ceil(bounds.east / step) - 1,
    step,
    limit,
    radius: 0,
    seen: new Set(),
    tiles: [],
  })
}
