import {
  BufferAttribute,
  CanvasTexture,
  ClampToEdgeWrapping,
  LinearFilter,
  SRGBColorSpace,
} from "three"
import { geoMercator } from "d3-geo"

const TILE_SIZE = 256
const SATELLITE_TILE_URL = "https://map.shuxitech.com/vt/lyrs=s&hl=zh-CN&x={x}&y={y}&z={z}"

const HONGYUAN_BOUNDS = {
  west: 101.852492,
  south: 31.851389,
  east: 103.347518,
  north: 33.319796,
}

function lngLatToTile(lng, lat, zoom) {
  const scale = 2 ** zoom
  const sinLat = Math.sin((lat * Math.PI) / 180)
  return {
    x: ((lng + 180) / 360) * scale,
    y: (0.5 - Math.log((1 + sinLat) / (1 - sinLat)) / (4 * Math.PI)) * scale,
  }
}

function tileToLngLat(x, y, zoom) {
  const scale = 2 ** zoom
  const lng = (x / scale) * 360 - 180
  const n = Math.PI - (2 * Math.PI * y) / scale
  const lat = (180 / Math.PI) * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)))
  return [lng, lat]
}

function getTileRange(bounds, zoom) {
  const northWest = lngLatToTile(bounds.west, bounds.north, zoom)
  const southEast = lngLatToTile(bounds.east, bounds.south, zoom)
  const minX = Math.floor(northWest.x)
  const maxX = Math.floor(southEast.x)
  const minY = Math.floor(northWest.y)
  const maxY = Math.floor(southEast.y)

  return {
    zoom,
    minX,
    maxX,
    minY,
    maxY,
    cols: maxX - minX + 1,
    rows: maxY - minY + 1,
  }
}

function createProjectedBounds(tileRange, projection) {
  const [west, north] = tileToLngLat(tileRange.minX, tileRange.minY, tileRange.zoom)
  const [east, south] = tileToLngLat(tileRange.maxX + 1, tileRange.maxY + 1, tileRange.zoom)
  const [left] = projection([west, HONGYUAN_BOUNDS.south])
  const [right] = projection([east, HONGYUAN_BOUNDS.south])
  const [, projectedNorth] = projection([HONGYUAN_BOUNDS.west, north])
  const [, projectedSouth] = projection([HONGYUAN_BOUNDS.west, south])

  return {
    left,
    right,
    bottom: -projectedSouth,
    top: -projectedNorth,
  }
}

function drawSatelliteTone(ctx, canvas) {
  const { width, height } = canvas
  ctx.save()
  ctx.globalCompositeOperation = "multiply"
  ctx.fillStyle = "rgba(5, 24, 34, 0.22)"
  ctx.fillRect(0, 0, width, height)
  ctx.globalCompositeOperation = "screen"
  const radial = ctx.createRadialGradient(
    width * 0.5,
    height * 0.48,
    width * 0.1,
    width * 0.5,
    height * 0.5,
    width * 0.72,
  )
  radial.addColorStop(0, "rgba(80, 230, 255, 0.16)")
  radial.addColorStop(1, "rgba(0, 18, 36, 0.03)")
  ctx.fillStyle = radial
  ctx.fillRect(0, 0, width, height)
  ctx.restore()
}

function drawPlaceholder(ctx, canvas) {
  const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height)
  gradient.addColorStop(0, "#061927")
  gradient.addColorStop(0.5, "#0b2c3c")
  gradient.addColorStop(1, "#07131f")
  ctx.fillStyle = gradient
  ctx.fillRect(0, 0, canvas.width, canvas.height)
}

function loadImage(url) {
  return new Promise((resolve) => {
    const image = new Image()
    image.crossOrigin = "anonymous"
    image.onload = () => resolve(image)
    image.onerror = () => resolve(null)
    image.src = url
  })
}

function tileUrl(x, y, z) {
  return SATELLITE_TILE_URL.replace("{x}", x).replace("{y}", y).replace("{z}", z)
}

async function loadTiles(ctx, canvas, texture, tileRange) {
  const tasks = []
  ctx.save()
  ctx.filter = "brightness(0.92) contrast(1.22) saturate(1.06)"
  for (let x = tileRange.minX; x <= tileRange.maxX; x += 1) {
    for (let y = tileRange.minY; y <= tileRange.maxY; y += 1) {
      tasks.push(
        loadImage(tileUrl(x, y, tileRange.zoom)).then((image) => {
          if (!image) return
          ctx.drawImage(image, (x - tileRange.minX) * TILE_SIZE, (y - tileRange.minY) * TILE_SIZE, TILE_SIZE, TILE_SIZE)
          texture.needsUpdate = true
        }),
      )
    }
  }
  await Promise.allSettled(tasks)
  ctx.restore()
  drawSatelliteTone(ctx, canvas)
  texture.needsUpdate = true
}

export function createHongyuanSatelliteTexture({
  geoProjectionCenter,
  geoProjectionScale,
  zoom = 11,
} = {}) {
  const tileRange = getTileRange(HONGYUAN_BOUNDS, zoom)
  const canvas = document.createElement("canvas")
  canvas.width = tileRange.cols * TILE_SIZE
  canvas.height = tileRange.rows * TILE_SIZE
  const ctx = canvas.getContext("2d")
  drawPlaceholder(ctx, canvas)

  const texture = new CanvasTexture(canvas)
  texture.colorSpace = SRGBColorSpace
  texture.wrapS = ClampToEdgeWrapping
  texture.wrapT = ClampToEdgeWrapping
  texture.minFilter = LinearFilter
  texture.magFilter = LinearFilter
  texture.needsUpdate = true

  const projection = geoMercator()
    .center(geoProjectionCenter)
    .scale(geoProjectionScale)
    .translate([0, 0])
  const uvBounds = createProjectedBounds(tileRange, projection)

  loadTiles(ctx, canvas, texture, tileRange).catch((error) => {
    console.warn("load satellite tiles failed:", error)
  })

  return {
    texture,
    uvBounds,
  }
}

export function applyMapTextureUv(map, bounds) {
  const width = bounds.right - bounds.left
  const height = bounds.top - bounds.bottom
  map.mapGroup.traverse((child) => {
    const position = child.geometry?.attributes?.position
    if (!child.isMesh || !position) return
    const uv = new Float32Array(position.count * 2)
    for (let index = 0; index < position.count; index += 1) {
      const x = position.getX(index)
      const y = position.getY(index)
      uv[index * 2] = (x - bounds.left) / width
      uv[index * 2 + 1] = (y - bounds.bottom) / height
    }
    child.geometry.setAttribute("uv", new BufferAttribute(uv, 2))
    child.geometry.attributes.uv.needsUpdate = true
  })
}
