import { type BackendPreviewSummary } from './backendApi'
import { lonLatToWorldTile, type LonLatPoint } from './projection'
import { isTileWithinBounds, type RemoteMapConfig } from './remoteMapConfig'
import { buildTileUrl } from './tile'

export interface PreparePreviewTile {
  url: string
  x: number
  y: number
  leftPx: number
  topPx: number
  sizePx: number
}

export interface PreparePreviewControl {
  kind: 'start' | 'control' | 'finish'
  label: string
  x: number
  y: number
}

export interface PreparePreviewLeg {
  fromX: number
  fromY: number
  toX: number
  toY: number
}

export interface PreparePreviewScene {
  width: number
  height: number
  zoom: number
  tiles: PreparePreviewTile[]
  controls: PreparePreviewControl[]
  legs: PreparePreviewLeg[]
  overlayAvailable: boolean
}

interface PreviewPointSeed {
  kind: 'start' | 'control' | 'finish'
  label: string
  point: LonLatPoint
}

function resolvePreviewTileTemplate(tileBaseUrl: string): string {
  if (tileBaseUrl.indexOf('{z}') >= 0 && tileBaseUrl.indexOf('{x}') >= 0 && tileBaseUrl.indexOf('{y}') >= 0) {
    return tileBaseUrl
  }
  const normalizedBase = tileBaseUrl.replace(/\/+$/, '')
  return `${normalizedBase}/{z}/{x}/{y}.png`
}

function clamp(value: number, min: number, max: number): number {
  return Math.max(min, Math.min(max, value))
}

function collectCoursePoints(config: RemoteMapConfig): LonLatPoint[] {
  if (!config.course) {
    return []
  }

  const points: LonLatPoint[] = []
  config.course.layers.starts.forEach((item) => {
    points.push(item.point)
  })
  config.course.layers.controls.forEach((item) => {
    points.push(item.point)
  })
  config.course.layers.finishes.forEach((item) => {
    points.push(item.point)
  })
  return points
}

function collectPreviewPointSeeds(items: Array<{
  kind?: string | null
  label?: string | null
  lon?: number | null
  lat?: number | null
}>): PreviewPointSeed[] {
  const seeds: PreviewPointSeed[] = []
  items.forEach((item, index) => {
    if (typeof item.lon !== 'number' || typeof item.lat !== 'number') {
      return
    }
    const kind = item.kind === 'start' || item.kind === 'finish' ? item.kind : 'control'
    seeds.push({
      kind,
      label: item.label || String(index + 1),
      point: {
        lon: item.lon,
        lat: item.lat,
      },
    })
  })
  return seeds
}

function computePointBounds(points: LonLatPoint[]): { minLon: number; minLat: number; maxLon: number; maxLat: number } | null {
  if (!points.length) {
    return null
  }

  let minLon = points[0].lon
  let maxLon = points[0].lon
  let minLat = points[0].lat
  let maxLat = points[0].lat
  points.forEach((point) => {
    minLon = Math.min(minLon, point.lon)
    maxLon = Math.max(maxLon, point.lon)
    minLat = Math.min(minLat, point.lat)
    maxLat = Math.max(maxLat, point.lat)
  })

  return {
    minLon,
    minLat,
    maxLon,
    maxLat,
  }
}

function resolvePreviewZoom(config: RemoteMapConfig, width: number, height: number, points: LonLatPoint[]): number {
  const upperZoom = clamp(config.defaultZoom > 0 ? config.defaultZoom : config.maxZoom, config.minZoom, config.maxZoom)
  if (!points.length) {
    return clamp(upperZoom - 1, config.minZoom, config.maxZoom)
  }

  const bounds = computePointBounds(points)
  if (!bounds) {
    return clamp(upperZoom - 1, config.minZoom, config.maxZoom)
  }

  let fittedZoom = config.minZoom
  for (let zoom = upperZoom; zoom >= config.minZoom; zoom -= 1) {
    const northWest = lonLatToWorldTile({ lon: bounds.minLon, lat: bounds.maxLat }, zoom)
    const southEast = lonLatToWorldTile({ lon: bounds.maxLon, lat: bounds.minLat }, zoom)
    const widthPx = Math.abs(southEast.x - northWest.x) * config.tileSize
    const heightPx = Math.abs(southEast.y - northWest.y) * config.tileSize
    if (widthPx <= width * 0.9 && heightPx <= height * 0.9) {
      fittedZoom = zoom
      break
    }
  }

  return clamp(fittedZoom, config.minZoom, config.maxZoom)
}

function resolvePreviewCenter(config: RemoteMapConfig, zoom: number, points: LonLatPoint[]): { x: number; y: number } {
  const bounds = computePointBounds(points)
  if (bounds) {
    const center = lonLatToWorldTile(
      {
        lon: (bounds.minLon + bounds.maxLon) / 2,
        lat: (bounds.minLat + bounds.maxLat) / 2,
      },
      zoom,
    )
    return {
      x: center.x,
      y: center.y,
    }
  }

  return {
    x: config.initialCenterTileX,
    y: config.initialCenterTileY,
  }
}

function buildPreviewTiles(
  config: RemoteMapConfig,
  zoom: number,
  width: number,
  height: number,
  centerWorldX: number,
  centerWorldY: number,
): PreparePreviewTile[] {
  const halfWidthInTiles = width / 2 / config.tileSize
  const halfHeightInTiles = height / 2 / config.tileSize
  const minTileX = Math.floor(centerWorldX - halfWidthInTiles) - 1
  const maxTileX = Math.ceil(centerWorldX + halfWidthInTiles) + 1
  const minTileY = Math.floor(centerWorldY - halfHeightInTiles) - 1
  const maxTileY = Math.ceil(centerWorldY + halfHeightInTiles) + 1
  const tiles: PreparePreviewTile[] = []

  for (let tileY = minTileY; tileY <= maxTileY; tileY += 1) {
    for (let tileX = minTileX; tileX <= maxTileX; tileX += 1) {
      if (!isTileWithinBounds(config.tileBoundsByZoom, zoom, tileX, tileY)) {
        continue
      }

      tiles.push({
        url: buildTileUrl(config.tileSource, zoom, tileX, tileY),
        x: tileX,
        y: tileY,
        leftPx: Math.round(width / 2 + (tileX - centerWorldX) * config.tileSize),
        topPx: Math.round(height / 2 + (tileY - centerWorldY) * config.tileSize),
        sizePx: config.tileSize,
      })
    }
  }

  return tiles
}

function applyFitTransform(
  scene: PreparePreviewScene,
  paddingRatio: number,
): PreparePreviewScene {
  if (!scene.controls.length) {
    return scene
  }

  let minX = scene.controls[0].x
  let maxX = scene.controls[0].x
  let minY = scene.controls[0].y
  let maxY = scene.controls[0].y

  scene.controls.forEach((control) => {
    minX = Math.min(minX, control.x)
    maxX = Math.max(maxX, control.x)
    minY = Math.min(minY, control.y)
    maxY = Math.max(maxY, control.y)
  })

  const boundsWidth = Math.max(1, maxX - minX)
  const boundsHeight = Math.max(1, maxY - minY)
  const targetWidth = scene.width * paddingRatio
  const targetHeight = scene.height * paddingRatio
  const scale = Math.max(1, Math.min(targetWidth / boundsWidth, targetHeight / boundsHeight))
  const centerX = (minX + maxX) / 2
  const centerY = (minY + maxY) / 2

  const transformX = (value: number) => ((value - centerX) * scale) + scene.width / 2
  const transformY = (value: number) => ((value - centerY) * scale) + scene.height / 2

  return {
    ...scene,
    tiles: scene.tiles.map((tile) => ({
      ...tile,
      leftPx: transformX(tile.leftPx),
      topPx: transformY(tile.topPx),
      sizePx: tile.sizePx * scale,
    })),
    controls: scene.controls.map((control) => ({
      ...control,
      x: transformX(control.x),
      y: transformY(control.y),
    })),
    legs: scene.legs.map((leg) => ({
      fromX: transformX(leg.fromX),
      fromY: transformY(leg.fromY),
      toX: transformX(leg.toX),
      toY: transformY(leg.toY),
    })),
  }
}

export function buildPreparePreviewScene(
  config: RemoteMapConfig,
  width: number,
  height: number,
  overlayEnabled: boolean,
): PreparePreviewScene {
  const normalizedWidth = Math.max(240, Math.round(width))
  const normalizedHeight = Math.max(140, Math.round(height))
  const points = collectCoursePoints(config)
  const zoom = resolvePreviewZoom(config, normalizedWidth, normalizedHeight, points)
  const center = resolvePreviewCenter(config, zoom, points)
  const tiles = buildPreviewTiles(config, zoom, normalizedWidth, normalizedHeight, center.x, center.y)

  const controls: PreparePreviewControl[] = []
  const legs: PreparePreviewLeg[] = []

  if (overlayEnabled && config.course) {
    const projectPoint = (point: LonLatPoint) => {
      const world = lonLatToWorldTile(point, zoom)
      return {
        x: normalizedWidth / 2 + (world.x - center.x) * config.tileSize,
        y: normalizedHeight / 2 + (world.y - center.y) * config.tileSize,
      }
    }

    config.course.layers.legs.forEach((leg) => {
      const from = projectPoint(leg.fromPoint)
      const to = projectPoint(leg.toPoint)
      legs.push({
        fromX: from.x,
        fromY: from.y,
        toX: to.x,
        toY: to.y,
      })
    })

    config.course.layers.starts.forEach((item) => {
      const point = projectPoint(item.point)
      controls.push({
        kind: 'start',
        label: item.label,
        x: point.x,
        y: point.y,
      })
    })
    config.course.layers.controls.forEach((item) => {
      const point = projectPoint(item.point)
      controls.push({
        kind: 'control',
        label: item.label,
        x: point.x,
        y: point.y,
      })
    })
    config.course.layers.finishes.forEach((item) => {
      const point = projectPoint(item.point)
      controls.push({
        kind: 'finish',
        label: item.label,
        x: point.x,
        y: point.y,
      })
    })
  }

  const baseScene: PreparePreviewScene = {
    width: normalizedWidth,
    height: normalizedHeight,
    zoom,
    tiles,
    controls,
    legs,
    overlayAvailable: overlayEnabled && !!config.course,
  }

  return applyFitTransform(baseScene, 0.88)
}

export function buildPreparePreviewSceneFromVariantControls(
  config: RemoteMapConfig,
  width: number,
  height: number,
  controlsInput: Array<{
    kind?: string | null
    label?: string | null
    lon?: number | null
    lat?: number | null
  }>,
): PreparePreviewScene | null {
  const seeds = collectPreviewPointSeeds(controlsInput)
  if (!seeds.length) {
    return null
  }

  const normalizedWidth = Math.max(240, Math.round(width))
  const normalizedHeight = Math.max(140, Math.round(height))
  const points = seeds.map((item) => item.point)
  const zoom = resolvePreviewZoom(config, normalizedWidth, normalizedHeight, points)
  const center = resolvePreviewCenter(config, zoom, points)
  const tiles = buildPreviewTiles(config, zoom, normalizedWidth, normalizedHeight, center.x, center.y)

  const controls: PreparePreviewControl[] = seeds.map((item) => {
    const world = lonLatToWorldTile(item.point, zoom)
    return {
      kind: item.kind,
      label: item.label,
      x: normalizedWidth / 2 + (world.x - center.x) * config.tileSize,
      y: normalizedHeight / 2 + (world.y - center.y) * config.tileSize,
    }
  })

  const scene: PreparePreviewScene = {
    width: normalizedWidth,
    height: normalizedHeight,
    zoom,
    tiles,
    controls,
    legs: [],
    overlayAvailable: true,
  }

  return applyFitTransform(scene, 0.88)
}

export function buildPreparePreviewSceneFromBackendPreview(
  preview: BackendPreviewSummary,
  width: number,
  height: number,
  variantId?: string | null,
  tileUrlTemplateOverride?: string | null,
): PreparePreviewScene | null {
  if (!preview.baseTiles || !preview.viewport || !preview.baseTiles.tileBaseUrl || typeof preview.baseTiles.zoom !== 'number') {
    return null
  }

  const viewport = preview.viewport
  if (
    typeof viewport.minLon !== 'number'
    || typeof viewport.minLat !== 'number'
    || typeof viewport.maxLon !== 'number'
    || typeof viewport.maxLat !== 'number'
  ) {
    return null
  }

  const normalizedWidth = Math.max(240, Math.round(width))
  const normalizedHeight = Math.max(140, Math.round(height))
  const zoom = Math.round(preview.baseTiles.zoom)
  const tileSize = typeof preview.baseTiles.tileSize === 'number' && preview.baseTiles.tileSize > 0
    ? preview.baseTiles.tileSize
    : 256
  const template = resolvePreviewTileTemplate(tileUrlTemplateOverride || preview.baseTiles.tileBaseUrl)

  const center = lonLatToWorldTile(
    {
      lon: (viewport.minLon + viewport.maxLon) / 2,
      lat: (viewport.minLat + viewport.maxLat) / 2,
    },
    zoom,
  )

  const northWest = lonLatToWorldTile({ lon: viewport.minLon, lat: viewport.maxLat }, zoom)
  const southEast = lonLatToWorldTile({ lon: viewport.maxLon, lat: viewport.minLat }, zoom)
  const boundsWidthPx = Math.max(1, Math.abs(southEast.x - northWest.x) * tileSize)
  const boundsHeightPx = Math.max(1, Math.abs(southEast.y - northWest.y) * tileSize)
  const scale = Math.min(normalizedWidth / boundsWidthPx, normalizedHeight / boundsHeightPx)

  const minTileX = Math.floor(Math.min(northWest.x, southEast.x)) - 1
  const maxTileX = Math.ceil(Math.max(northWest.x, southEast.x)) + 1
  const minTileY = Math.floor(Math.min(northWest.y, southEast.y)) - 1
  const maxTileY = Math.ceil(Math.max(northWest.y, southEast.y)) + 1

  const tiles: PreparePreviewTile[] = []
  for (let tileY = minTileY; tileY <= maxTileY; tileY += 1) {
    for (let tileX = minTileX; tileX <= maxTileX; tileX += 1) {
      const leftPx = ((tileX - center.x) * tileSize * scale) + normalizedWidth / 2
      const topPx = ((tileY - center.y) * tileSize * scale) + normalizedHeight / 2
      tiles.push({
        url: buildTileUrl(template, zoom, tileX, tileY),
        x: tileX,
        y: tileY,
        leftPx,
        topPx,
        sizePx: tileSize * scale,
      })
    }
  }

  const normalizedVariantId = variantId || preview.selectedVariantId || ''
  const previewVariant = (preview.variants || []).find((item) => {
    const candidateId = item.variantId || item.id || ''
    return candidateId === normalizedVariantId
  }) || (preview.variants && preview.variants[0] ? preview.variants[0] : null)

  const controls: PreparePreviewControl[] = []
  if (previewVariant && previewVariant.controls && previewVariant.controls.length) {
    previewVariant.controls.forEach((item, index) => {
      if (typeof item.lon !== 'number' || typeof item.lat !== 'number') {
        return
      }
      const world = lonLatToWorldTile({ lon: item.lon, lat: item.lat }, zoom)
      const x = ((world.x - center.x) * tileSize * scale) + normalizedWidth / 2
      const y = ((world.y - center.y) * tileSize * scale) + normalizedHeight / 2
      const normalizedKind = item.kind === 'start' || item.kind === 'finish' ? item.kind : 'control'
      controls.push({
        kind: normalizedKind,
        label: item.label || String(index + 1),
        x,
        y,
      })
    })
  }

  return {
    width: normalizedWidth,
    height: normalizedHeight,
    zoom,
    tiles,
    controls,
    legs: [],
    overlayAvailable: controls.length > 0,
  }
}
