import { latToWorldY, lonToWorldX, worldYToLat } from "../geometry/mapProjection";
import type { MapProjection } from "../geometry/mapProjection";
import type { MapSize, MapViewState } from "../model/mapTypes";
import { getTile } from "./tileLoader";

const TILE_PX = 256;

export function drawTiles(
  ctx: CanvasRenderingContext2D,
  view: MapViewState,
  projection: MapProjection,
  size: MapSize,
  onLoad: () => void
): void {
  // Snap fractional zoom to nearest integer for tile selection.
  // Tiles are rendered at the correct scale via screenTileW.
  const z = Math.max(1, Math.min(18, Math.round(view.zoom)));
  const tileCount = 2 ** z;
  const worldSize_z = TILE_PX * tileCount;

  // Width of one tile in screen pixels at the current (possibly fractional) zoom
  const screenTileW = TILE_PX * (projection.worldSize / worldSize_z);

  // Center of the viewport in tile x-coordinates (float)
  const centerTileX = lonToWorldX(view.centerLon, worldSize_z) / TILE_PX;
  // Center in projection world pixels (used for screen x formula below)
  const centerWorldX = lonToWorldX(view.centerLon, projection.worldSize);

  // X tile range: continuously-indexed (can be negative or > tileCount).
  // wrappedTx = ((tx % tileCount) + tileCount) % tileCount for the actual URL.
  const tilesHalf = Math.ceil(size.width / (2 * screenTileW)) + 1;
  const txMin = Math.floor(centerTileX) - tilesHalf;
  const txMax = Math.ceil(centerTileX) + tilesHalf;

  // Y tile range: clamped to [0, tileCount-1] (no world repeat in y)
  const topLat = projection.unproject({ x: size.width / 2, y: 0 }).lat;
  const bottomLat = projection.unproject({ x: size.width / 2, y: size.height }).lat;
  const tyMin = Math.max(0, Math.floor(latToWorldY(topLat, worldSize_z) / TILE_PX) - 1);
  const tyMax = Math.min(tileCount - 1, Math.ceil(latToWorldY(bottomLat, worldSize_z) / TILE_PX) + 1);

  for (let ty = tyMin; ty <= tyMax; ty++) {
    // Mercator-correct tile top/bottom screen positions
    const tileTopLat = worldYToLat(ty * TILE_PX, worldSize_z);
    const tileBottomLat = worldYToLat((ty + 1) * TILE_PX, worldSize_z);
    // lon arg is irrelevant for y — use centerLon to avoid antimeridian edge cases
    const screenY = projection.project({ lon: view.centerLon, lat: tileTopLat }).y;
    const screenYBottom = projection.project({ lon: view.centerLon, lat: tileBottomLat }).y;

    if (screenYBottom < 0 || screenY > size.height) continue;
    const screenH = screenYBottom - screenY;

    for (let tx = txMin; tx <= txMax; tx++) {
      // Screen x is linear in tile x (Mercator is linear in longitude)
      const screenX = size.width / 2 + tx * screenTileW - centerWorldX;
      if (screenX + screenTileW < 0 || screenX > size.width) continue;

      const wrappedTx = ((tx % tileCount) + tileCount) % tileCount;
      const img = getTile(z, wrappedTx, ty, onLoad);
      if (!img) continue;

      ctx.drawImage(img, screenX, screenY, screenTileW, screenH);
    }
  }
}
