import { get as getProjection, transform } from 'ol/proj.js'
import { createXYZ } from 'ol/tilegrid.js'
import { WeatherLayerType } from '@hyperviz/weather'
import BaseLayer from 'ol/layer/Base.js'
import ImageLayer from 'ol/layer/Image.js'
import Static from 'ol/source/ImageStatic.js'
import Map from 'ol/Map.js'

/**
 * 바람 데이터를 캔버스에 래스터화하여 표시하는 레이어
 * 전체 데이터를 한 번에 처리하여 이미지로 변환해 표시하는 방식
 *
 * @class WindRasterizedLayer
 */

// --- 데이터 로딩 및 변환 로직 ---

/** 바람 데이터 이미지 로딩 Promise */
const windData: Promise<{ data: ArrayBuffer; width: number; height: number }> = new Promise((resolve, reject) => {
  const image = new Image()
  image.onload = () => {
    const canvas = document.createElement('canvas')
    const width = image.width
    const height = image.height
    canvas.width = width
    canvas.height = height

    const context = canvas.getContext('2d')!
    context.drawImage(image, 0, 0)
    const data = context.getImageData(0, 0, width, height).data.buffer
    resolve({ data, width, height })
  }
  image.onerror = () => {
    reject(new Error('바람 데이터 이미지 로드 실패'))
  }
  image.src = '/assets/images/wind.png'
})

/**
 * 바이리니어 보간법을 사용하여 픽셀 값 계산
 */
function bilinearInterpolation(
  xAlong: number,
  yAlong: number,
  v11: number,
  v21: number,
  v12: number,
  v22: number
): number {
  const q11 = (1 - xAlong) * (1 - yAlong) * v11
  const q21 = xAlong * (1 - yAlong) * v21
  const q12 = (1 - xAlong) * yAlong * v12
  const q22 = xAlong * yAlong * v22
  return q11 + q21 + q12 + q22
}

/**
 * 픽셀 배열에 대한 보간법 적용
 */
function interpolatePixels(
  xAlong: number,
  yAlong: number,
  p11: number[],
  p21: number[],
  p12: number[],
  p22: number[]
): number[] {
  return p11.map((_, i) => bilinearInterpolation(xAlong, yAlong, p11[i], p21[i], p12[i], p22[i]))
}

// 바람 데이터 관련 상수
const inputImageProjection = getProjection('EPSG:4326')!
const inputBands = 4 // RGBA

// 바람 속도 범위 (원본 이미지 값 기준)
const minU = -21.32
const maxU = 26.8
const deltaU = maxU - minU
const minV = -21.57
const maxV = 21.42
const deltaV = maxV - minV

// 바람 속도에 따른 색상 매핑 (속도, [R, G, B, A])
const windColorMap: Array<[number, [number, number, number, number]]> = [
  [0, [0, 50, 170, 100]], // 약한 바람 (파란색)
  [5, [0, 100, 220, 150]], // 중약한 바람
  [10, [0, 150, 200, 180]], // 중간 바람 (청록색)
  [15, [0, 200, 150, 200]], // 중강한 바람 (녹색)
  [20, [150, 250, 0, 220]], // 강한 바람 (노란색)
  [25, [200, 150, 0, 230]], // 매우 강한 바람 (주황색)
  [30, [250, 50, 0, 250]] // 극심한 바람 (빨간색)
]

/**
 * 바람 속도에 따른 색상 계산
 * @param speed 바람 속도 (m/s)
 * @returns RGBA 색상 배열 [r, g, b, a] (0-255)
 */
function getWindColor(speed: number): [number, number, number, number] {
  // 기본 색상 (약한 바람)
  let color: [number, number, number, number] = [0, 50, 170, 100]

  // 색상 맵에서 가장 가까운 색상 찾기
  for (let i = 0; i < windColorMap.length - 1; i++) {
    const [speed1, color1] = windColorMap[i]
    const [speed2, color2] = windColorMap[i + 1]

    if (speed >= speed1 && speed <= speed2) {
      // 두 색상 사이 보간
      const t = (speed - speed1) / (speed2 - speed1)
      color = [
        Math.floor(color1[0] * (1 - t) + color2[0] * t),
        Math.floor(color1[1] * (1 - t) + color2[1] * t),
        Math.floor(color1[2] * (1 - t) + color2[2] * t),
        Math.floor(color1[3] * (1 - t) + color2[3] * t)
      ]
      break
    }
  }

  // 최대 색상 (매우 강한 바람)
  if (speed > windColorMap[windColorMap.length - 1][0]) {
    color = [...windColorMap[windColorMap.length - 1][1]]
  }

  return color
}

// --- 레이어 클래스 구현 ---

export class WindRasterizedLayer {
  private layer: ImageLayer<Static> | null = null
  private dataPromise: Promise<{ data: ArrayBuffer; width: number; height: number }> | null = null
  private canvas: HTMLCanvasElement | null = null
  private ctx: CanvasRenderingContext2D | null = null
  private width = 800
  private height = 400
  private map: Map | null = null
  private useEnhancedColors: boolean = true
  private isDisposed: boolean = false

  /**
   * 바람 래스터화 레이어 생성자
   * @param options 옵션 객체
   */
  constructor(
    options: {
      width?: number
      height?: number
      useEnhancedColors?: boolean
    } = {}
  ) {
    this.dataPromise = windData

    // 캔버스 크기 설정
    if (options.width) this.width = options.width
    if (options.height) this.height = options.height

    // 색상 매핑 옵션
    if (options.useEnhancedColors !== undefined) {
      this.useEnhancedColors = options.useEnhancedColors
    }

    // 사전에 캔버스 준비
    this.canvas = document.createElement('canvas')
    this.canvas.width = this.width
    this.canvas.height = this.height
    this.ctx = this.canvas.getContext('2d')
  }

  /**
   * 레이어 타입 반환 (호환성용)
   */
  getType(): WeatherLayerType {
    return 'wind'
  }

  /**
   * 향상된 색상 매핑 사용 여부 설정
   */
  setUseEnhancedColors(use: boolean): void {
    this.useEnhancedColors = use
    this.updateLayer() // 변경사항 적용을 위해 레이어 업데이트
  }

  /**
   * OpenLayers 레이어 객체 생성 또는 반환
   */
  createLayer(): BaseLayer {
    if (this.isDisposed) {
      throw new Error('레이어가 이미 폐기되었습니다')
    }

    if (!this.canvas || !this.ctx) {
      throw new Error('캔버스가 초기화되지 않았습니다')
    }

    // 초기 이미지 렌더링
    this.renderWindData()

    // 정적 이미지 소스 생성
    const source = new Static({
      url: this.canvas.toDataURL(),
      imageExtent: [-180, -90, 180, 90],
      projection: 'EPSG:4326',
      interpolate: true
    })

    // 레이어 생성
    this.layer = new ImageLayer({
      source: source,
      opacity: 0.7,
      properties: {
        useEnhancedColors: this.useEnhancedColors
      }
    })

    return this.layer
  }

  /**
   * 바람 데이터 렌더링
   */
  async renderWindData(): Promise<void> {
    if (!this.ctx || !this.canvas || this.isDisposed) return

    try {
      const { data, width, height } = await this.dataPromise!

      // 캔버스 지우기
      this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)

      // 바람 데이터 시각화
      const imgData = this.ctx.createImageData(this.canvas.width, this.canvas.height)

      for (let y = 0; y < this.canvas.height; y++) {
        for (let x = 0; x < this.canvas.width; x++) {
          const idx = (y * this.canvas.width + x) * 4

          // 위치 계산
          const lon = -180 + (x / this.canvas.width) * 360
          const lat = 90 - (y / this.canvas.height) * 180

          // 원본 이미지에서의 위치 계산
          const imgX = Math.floor((width * (lon + 180)) / 360)
          const imgY = Math.floor((height * (90 - lat)) / 180)

          // 경계 체크
          if (imgX < 0 || imgX >= width || imgY < 0 || imgY >= height) {
            continue
          }

          // 원본 데이터 인덱스
          const dataIdx = (imgY * width + imgX) * inputBands

          // 바람 속도 계산
          const u = minU + (deltaU * data[dataIdx]) / 255
          const v = minV + (deltaV * data[dataIdx + 1]) / 255
          const speed = Math.sqrt(u * u + v * v)

          // 색상 계산: 향상된 색상 매핑 또는 기본 스키마 사용
          let r, g, b, a

          if (this.useEnhancedColors) {
            // 향상된 색상 매핑 사용
            const color = getWindColor(speed)
            ;[r, g, b, a] = color
          } else {
            // 기본 색상 매핑 (녹청색 그라데이션)
            const normalizedSpeed = Math.min(speed / 20, 1)
            r = Math.floor(normalizedSpeed * 0)
            g = Math.floor(normalizedSpeed * 150)
            b = Math.floor(normalizedSpeed * 255)
            a = Math.floor(normalizedSpeed * 200)
          }

          // 이미지 데이터에 색상 설정
          imgData.data[idx] = r
          imgData.data[idx + 1] = g
          imgData.data[idx + 2] = b
          imgData.data[idx + 3] = a
        }
      }

      // 캔버스에 이미지 데이터 적용
      this.ctx.putImageData(imgData, 0, 0)

      // 소스 업데이트
      if (this.layer && !this.isDisposed) {
        try {
          const newSource = new Static({
            url: this.canvas.toDataURL(),
            imageExtent: [-180, -90, 180, 90],
            projection: 'EPSG:4326',
            interpolate: true
          })

          this.layer.setSource(newSource)
        } catch (error) {
          console.error('소스 업데이트 실패:', error)
        }
      }
    } catch (error) {
      console.error('바람 데이터 렌더링 실패:', error)
    }
  }

  /**
   * 레이어 업데이트 요청
   */
  updateLayer(): void {
    if (this.isDisposed) return

    this.renderWindData()

    // 속성 업데이트 (향상된 색상 매핑 옵션)
    if (this.layer) {
      this.layer.set('useEnhancedColors', this.useEnhancedColors)
    }
  }

  /**
   * 레이어를 맵에 추가
   */
  addToMap(map: Map): void {
    if (this.isDisposed) {
      console.warn('폐기된 레이어는 맵에 추가할 수 없습니다')
      return
    }

    this.map = map

    if (!this.layer) {
      this.layer = this.createLayer() as ImageLayer<Static>
    }

    this.map.addLayer(this.layer)
  }

  /**
   * 레이어를 맵에서 제거
   */
  removeFromMap(): void {
    if (!this.layer || !this.map || this.isDisposed) {
      return
    }

    try {
      // 렌더링 플래그 설정 (렌더링 방지)
      if (this.layer['rendered'] !== undefined) {
        this.layer['rendered'] = false
      }

      // 소스 정리
      const source = this.layer.getSource()
      if (source) {
        try {
          // setUrl 메서드가 없으므로 소스 자체를 null로 설정
          this.layer.setSource(null)
        } catch (e) {
          // 오류 무시
        }
      }

      // 맵에서 레이어 제거
      this.map.removeLayer(this.layer)
    } catch (e) {
      console.error('맵에서 레이어 제거 중 오류:', e)
    } finally {
      // 맵 참조 해제
      this.map = null
    }
  }

  /**
   * 레이어 관련 리소스 해제
   */
  dispose(): void {
    if (this.isDisposed) {
      return
    }

    // 이미 맵에서 제거되지 않았다면 제거
    if (this.map && this.layer) {
      this.removeFromMap()
    }

    try {
      // 레이어 정리
      if (this.layer) {
        const source = this.layer.getSource()
        if (source) {
          try {
            source.dispose()
          } catch (e) {
            // 오류 무시
          }
        }

        try {
          // 레이어 자체 dispose 호출
          this.layer.dispose()
        } catch (e) {
          // 오류 무시
        }

        this.layer = null
      }

      // 캔버스 정리
      if (this.canvas) {
        // 캔버스 메모리 해제 (크기를 최소화)
        this.canvas.width = 1
        this.canvas.height = 1
        this.canvas = null
        this.ctx = null
      }

      // 데이터 참조 정리
      this.dataPromise = null

      // 폐기 플래그 설정
      this.isDisposed = true
    } catch (e) {
      console.error('리소스 해제 중 오류:', e)
    }
  }

  /**
   * 데이터 설정 메서드 (호환성용)
   */
  setWeatherData(data: any): void {
    // 이 구현에서는 외부 데이터를 직접 설정하지 않음
    console.warn('WindRasterizedLayer에서 setWeatherData가 호출되었지만, 데이터는 정적입니다.')
    this.updateLayer() // 데이터가 변경된 것처럼 레이어 갱신
  }
}
