import { createTileGrid, type TileItem } from '../../utils/tile'
import { isTileWithinBounds } from '../../utils/remoteMapConfig'
import { getTileSizePx, type CameraState } from '../camera/camera'
import { type MapScene } from '../renderer/mapRenderer'
import { type TileStore } from '../tile/tileStore'
import { type MapLayer, type LayerRenderContext } from './mapLayer'

function buildGridKey(scene: MapScene, tileSize: number): string {
  return [
    scene.tileSource,
    scene.zoom,
    scene.centerTileX,
    scene.centerTileY,
    scene.viewportWidth,
    scene.viewportHeight,
    tileSize,
    scene.overdraw,
  ].join('|')
}

export class TileLayer implements MapLayer {
  lastVisibleTileCount: number
  lastReadyTileCount: number
  cachedGridKey: string
  cachedTiles: TileItem[]

  constructor() {
    this.lastVisibleTileCount = 0
    this.lastReadyTileCount = 0
    this.cachedGridKey = ''
    this.cachedTiles = []
  }

  prepareTiles(scene: MapScene, camera: CameraState, tileStore: TileStore): TileItem[] {
    const tileSize = getTileSizePx(camera)
    if (!tileSize) {
      this.lastVisibleTileCount = 0
      this.lastReadyTileCount = 0
      this.cachedGridKey = ''
      this.cachedTiles = []
      return []
    }

    const gridKey = buildGridKey(scene, tileSize)
    if (gridKey !== this.cachedGridKey) {
      this.cachedGridKey = gridKey
      this.cachedTiles = createTileGrid({
        urlTemplate: scene.tileSource,
        zoom: scene.zoom,
        centerTileX: scene.centerTileX,
        centerTileY: scene.centerTileY,
        viewportWidth: scene.viewportWidth,
        viewportHeight: scene.viewportHeight,
        tileSize,
        overdraw: scene.overdraw,
      }).filter((tile) => isTileWithinBounds(scene.tileBoundsByZoom, scene.zoom, tile.x, tile.y))
    }

    tileStore.queueVisibleTiles(this.cachedTiles, scene, gridKey)
    this.lastVisibleTileCount = this.cachedTiles.length
    this.lastReadyTileCount = 0
    return this.cachedTiles
  }

  draw(context: LayerRenderContext): void {
    const { ctx, scene, camera, tileStore } = context
    const tiles = this.prepareTiles(scene, camera, tileStore)

    for (const tile of tiles) {
      const entry = tileStore.getEntry(tile.url)
      const drawLeft = tile.leftPx + scene.translateX
      const drawTop = tile.topPx + scene.translateY

      if (entry && entry.status === 'ready' && entry.image) {
        ctx.drawImage(entry.image, drawLeft, drawTop, tile.sizePx, tile.sizePx)
        this.lastReadyTileCount += 1
        continue
      }

      const parentFallback = tileStore.getParentFallbackSlice(tile, scene)
      let drewFallback = false
      if (parentFallback) {
        ctx.drawImage(
          parentFallback.entry.image,
          parentFallback.sourceX,
          parentFallback.sourceY,
          parentFallback.sourceWidth,
          parentFallback.sourceHeight,
          drawLeft,
          drawTop,
          tile.sizePx,
          tile.sizePx,
        )
        drewFallback = true
      }

      const childFallback = tileStore.getChildFallback(tile, scene)
      if (childFallback) {
        const cellWidth = tile.sizePx / childFallback.division
        const cellHeight = tile.sizePx / childFallback.division
        for (const child of childFallback.children) {
          const childImageWidth = child.entry.image.width || 256
          const childImageHeight = child.entry.image.height || 256
          ctx.drawImage(
            child.entry.image,
            0,
            0,
            childImageWidth,
            childImageHeight,
            drawLeft + child.offsetX * cellWidth,
            drawTop + child.offsetY * cellHeight,
            cellWidth,
            cellHeight,
          )
        }
        drewFallback = true
      }

      if (!drewFallback) {
        ctx.fillStyle = entry && entry.status === 'error' ? '#d9b2b2' : '#dbeed4'
        ctx.fillRect(drawLeft, drawTop, tile.sizePx, tile.sizePx)
      }
    }
  }
}

