import { get as getProjection, transform } from 'ol/proj.js';
import DataTileSource from 'ol/source/DataTile.js';
import { createXYZ, wrapX } from 'ol/tilegrid.js';
import Layer from 'ol/layer/Layer.js';
/**
 * 바람 데이터를 표시하기 위한 타일 기반 레이어
 * PNG 이미지 데이터를 DataTileSource로 변환하여 효율적인 렌더링 제공
 *
 * @class WindDataTileLayer
 */
// --- 데이터 로딩 및 변환 로직 ---
/** 바람 데이터 이미지 로딩 Promise */
const windData = 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, yAlong, v11, v21, v12, v22) {
    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, yAlong, p11, p21, p12, p22) {
    return p11.map((_, i) => bilinearInterpolation(xAlong, yAlong, p11[i], p21[i], p12[i], p22[i]));
}
// 타일 그리드 및 투영 설정
const dataTileGrid = createXYZ();
const dataTileSize = 256;
const inputImageProjection = getProjection('EPSG:4326');
const dataTileProjection = getProjection('EPSG:3857');
const inputBands = 4; // RGBA
const dataBands = 3; // u, v, magnitude
// 바람 속도 범위 (원본 이미지 값 기준)
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 = [
    [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]] // 극심한 바람 (빨간색)
];
// --- 레이어 클래스 구현 ---
export class WindDataTileLayer {
    /**
     * 바람 데이터 타일 레이어 생성
     * @param options 옵션 객체
     */
    constructor(options = {}) {
        this.layer = null; // 실제 OpenLayers 레이어
        this.map = null;
        this.useEnhancedColors = true; // 향상된 색상 매핑 사용 여부
        this.__disposed = false; // 레이어가 폐기되었는지 여부를 추적하기 위한 플래그
        // 향상된 색상 매핑 옵션 설정
        if (options.useEnhancedColors !== undefined) {
            this.useEnhancedColors = options.useEnhancedColors;
        }
        // DataTileSource 생성
        this.source = new DataTileSource({
            // transition must be 0 for DataTileSource, see https://github.com/openlayers/openlayers/issues/16119
            transition: 0,
            wrapX: true,
            loader: this._tileLoader.bind(this)
        });
    }
    /**
     * 타일 데이터 로딩 함수
     * @private
     */
    async _tileLoader(z, x, y) {
        // 타일 경계 체크: 요청된 타일이 유효한 범위를 벗어나면 빈 데이터 반환
        // z=0일 때 x와 y는 모두 0이어야 함
        // z=1일 때 x와 y는 0 또는 1
        // z=2일 때 x와 y는 0, 1, 2, 3 중 하나
        // 즉, z에 대해 x와 y는 0부터 2^z-1 사이의 값이어야 함
        const maxTileCoord = Math.pow(2, z) - 1;
        if (x < 0 || y < 0 || x > maxTileCoord || y > maxTileCoord) {
            console.warn(`🚨 타일 범위 초과: z=${z}, x=${x}, y=${y}, 최대값=${maxTileCoord}`);
            return new Float32Array(dataTileSize * dataTileSize * dataBands);
        }
        try {
            // 폐기된 레이어의 경우 빈 데이터 반환
            if (this.__disposed) {
                return new Float32Array(dataTileSize * dataTileSize * dataBands);
            }
            const { data: inputData, width: inputWidth, height: inputHeight } = await windData;
            // wrapX를 통해 날짜변경선 처리
            const tileCoord = wrapX(dataTileGrid, [z, x, y], dataTileProjection);
            const extent = dataTileGrid.getTileCoordExtent(tileCoord);
            const resolution = dataTileGrid.getResolution(z);
            // 타일 데이터 배열 (Float32Array 사용)
            const data = new Float32Array(dataTileSize * dataTileSize * dataBands);
            for (let row = 0; row < dataTileSize; ++row) {
                let offset = row * dataTileSize * dataBands;
                const mapY = extent[3] - row * resolution;
                for (let col = 0; col < dataTileSize; ++col) {
                    const mapX = extent[0] + col * resolution;
                    try {
                        // 현재 픽셀의 좌표를 EPSG:4326으로 변환
                        const [lon, lat] = transform([mapX, mapY], dataTileProjection, inputImageProjection);
                        // 유효한 좌표 범위 확인 (-180 ~ 180, -90 ~ 90)
                        if (lon < -180 || lon > 180 || lat < -90 || lat > 90) {
                            // 범위를 벗어나는 좌표는 기본값으로 처리
                            data[offset] = 0; // u (동서 방향 바람)
                            data[offset + 1] = 0; // v (남북 방향 바람)
                            data[offset + 2] = 0; // magnitude (바람 세기)
                            offset += dataBands;
                            continue;
                        }
                        // 원본 이미지에서의 상대적 위치 계산
                        const x = (inputWidth * (lon + 180)) / 360;
                        let x1 = Math.floor(x);
                        let x2 = Math.ceil(x);
                        const xAlong = x - x1;
                        // 경도 랩핑 처리 (날짜변경선)
                        if (x1 < 0) {
                            x1 += inputWidth;
                        }
                        if (x2 >= inputWidth) {
                            x2 -= inputWidth;
                        }
                        const y = (inputHeight * (90 - lat)) / 180;
                        let y1 = Math.floor(y);
                        let y2 = Math.ceil(y);
                        const yAlong = y - y1;
                        // 위도 범위 제한
                        if (y1 < 0) {
                            y1 = 0;
                        }
                        if (y2 >= inputHeight) {
                            y2 = inputHeight - 1;
                        }
                        const corners = [
                            [x1, y1],
                            [x2, y1],
                            [x1, y2],
                            [x2, y2]
                        ];
                        // 주변 4개 픽셀의 u, v 값 읽기
                        const pixels = corners.map(([cx, cy]) => {
                            // 유효한 인덱스 확인
                            if (cx < 0 || cx >= inputWidth || cy < 0 || cy >= inputHeight) {
                                return [0, 0]; // 잘못된 좌표는 기본값 사용
                            }
                            const inputOffset = (cy * inputWidth + cx) * inputBands;
                            // 원본 PNG 데이터에서 u, v 값 추출 (R 채널=u, G 채널=v)
                            return [inputData[inputOffset], inputData[inputOffset + 1]];
                        });
                        // u, v 값 보간
                        const interpolated = interpolatePixels(xAlong, yAlong, ...pixels);
                        // 실제 u, v 값으로 변환
                        const u = minU + (deltaU * interpolated[0]) / 255;
                        const v = minV + (deltaV * interpolated[1]) / 255;
                        // 바람 속도 계산 (옵션)
                        const speed = Math.sqrt(u * u + v * v);
                        // 타일 데이터에 저장
                        data[offset] = u;
                        data[offset + 1] = v;
                        data[offset + 2] = speed; // 속도 값 저장 (셰이더에서 활용 가능)
                    }
                    catch (pixelError) {
                        // 개별 픽셀 처리 중 오류 발생 시 해당 픽셀만 기본값으로 처리
                        data[offset] = 0; // u
                        data[offset + 1] = 0; // v
                        data[offset + 2] = 0; // magnitude
                    }
                    offset += dataBands;
                }
            }
            return data; // Float32Array 반환
        }
        catch (error) {
            // 타일 로드 실패 로그
            console.warn(`🚨 타일 없음: z=${z}, x=${x}, y=${y}`, error);
            // 오류 발생 시 빈 타일 데이터 반환
            return new Float32Array(dataTileSize * dataTileSize * dataBands);
        }
    }
    /**
     * 레이어 타입 반환 (호환성용)
     */
    getType() {
        return 'wind';
    }
    /**
     * 향상된 색상 매핑 사용 여부 설정
     */
    setUseEnhancedColors(use) {
        this.useEnhancedColors = use;
        this.updateLayer(); // 변경사항 적용을 위해 레이어 업데이트
    }
    /**
     * OpenLayers 레이어 객체 생성 또는 반환
     */
    createLayer() {
        if (this.__disposed) {
            console.warn('폐기된 레이어에서 createLayer가 호출되었습니다');
            return new Layer({}); // 빈 레이어 반환
        }
        if (!this.layer) {
            // 렌더링 파이프라인과의 충돌을 최소화하기 위해 간단한 설정으로 레이어 생성
            this.layer = new Layer({
                source: this.source,
                properties: {
                    useEnhancedColors: this.useEnhancedColors,
                    // 렌더링 시 이 속성을 확인하도록 구현
                    __windLayerDisposed: false
                }
            });
        }
        return this.layer;
    }
    /**
     * 요청 중인 데이터 로드 중단 및 소스 무효화
     * @private
     */
    _clearSourceData() {
        if (!this.source)
            return;
        try {
            // 소스에서 로드된 타일 데이터 초기화
            if (typeof this.source.clear === 'function') {
                this.source.clear(); // 타일 캐시 정리
            }
            // 더 이상 데이터를 제공하지 않도록 소스 무효화
            // 소스의 내부 속성에 접근 (타입 어설션 사용)
            const sourceAny = this.source;
            if (sourceAny.loader) {
                // 로더 함수를 빈 데이터를 반환하는 함수로 재정의
                sourceAny.loader = (_z, _x, _y) => {
                    // 빈 데이터 반환
                    return Promise.resolve(new Float32Array(dataTileSize * dataTileSize * dataBands));
                };
            }
        }
        catch (e) {
            console.warn('소스 정리 중 오류:', e);
        }
    }
    /**
     * 레이어를 맵에 추가
     */
    addToMap(map) {
        if (this.__disposed) {
            console.warn('폐기된 레이어는 맵에 추가할 수 없습니다');
            return;
        }
        if (!this.map) {
            this.map = map;
        }
        if (!this.layer) {
            this.createLayer();
        }
        if (this.layer && this.map) {
            this.map.addLayer(this.layer);
        }
    }
    /**
     * 레이어를 맵에서 제거
     */
    removeFromMap() {
        if (!this.layer || !this.map || this.__disposed) {
            return; // 이미 제거되었거나 맵에 추가되지 않은 경우
        }
        try {
            // OpenLayers 렌더링 문제 방지를 위한 사전 처리
            this.__disposed = true; // 먼저 플래그 설정
            // 1. 레이어가 렌더링 파이프라인에서 무시되도록 설정
            this.layer.setVisible(false); // 시각적 숨김
            this.layer.set('__windLayerDisposed', true); // 내부 플래그 설정
            this.layer.changed(); // 렌더링 파이프라인에 변경 알림
            // 2. 요청 중인 모든 데이터 로드 중단
            this._clearSourceData();
            // 3. 연결된 맵과 소스에서 레이어 분리
            try {
                if (this.map && this.layer) {
                    // 맵에서 레이어 제거
                    this.map.removeLayer(this.layer);
                }
            }
            catch (e) {
                console.error('맵에서 레이어 제거 중 오류:', e);
            }
            // 4. 레이어 참조 정리
            this.map = null;
        }
        catch (e) {
            console.error('레이어 제거 중 오류:', e);
            // 오류 발생 시에도 맵에서 레이어 제거 시도
            try {
                if (this.map && this.layer) {
                    this.map.removeLayer(this.layer);
                }
            }
            catch (e2) {
                // 무시
            }
            this.map = null;
        }
    }
    /**
     * 레이어 관련 리소스 해제
     */
    dispose() {
        if (this.__disposed) {
            return; // 이미 폐기된 경우
        }
        this.__disposed = true; // 폐기 플래그 설정
        // 맵에서 제거 (아직 제거되지 않은 경우)
        if (this.map && this.layer) {
            try {
                // 렌더링 에러 방지를 위해 먼저 레이어 숨김 및 상태 설정
                this.layer.setVisible(false);
                this.layer.set('__windLayerDisposed', true);
                this.layer.changed();
                // 맵에서 레이어 제거
                this.map.removeLayer(this.layer);
            }
            catch (e) {
                console.error('맵에서 레이어 제거 중 오류:', e);
            }
            finally {
                this.map = null; // 맵 참조 해제
            }
        }
        // 나머지 리소스 정리
        try {
            // 레이어 정리
            if (this.layer) {
                try {
                    // 레이어에서 소스 분리
                    if (typeof this.layer.setSource === 'function') {
                        this.layer.setSource(null);
                    }
                    // 레이어 객체 정리
                    if (typeof this.layer.dispose === 'function') {
                        this.layer.dispose();
                    }
                }
                catch (e) {
                    console.warn('레이어 정리 중 오류:', e);
                }
                this.layer = null;
            }
            // 소스 정리
            if (this.source) {
                try {
                    // 소스 정리
                    if (typeof this.source.dispose === 'function') {
                        this.source.dispose();
                    }
                }
                catch (e) {
                    console.warn('소스 정리 중 오류:', e);
                }
                this.source = null;
            }
        }
        catch (e) {
            console.error('리소스 정리 중 오류:', e);
        }
    }
    /**
     * 레이어 업데이트 요청 (데이터 변경 시)
     */
    updateLayer() {
        if (this.source) {
            this.source.changed(); // 타일 다시 로드 트리거
        }
        // 속성 업데이트 (향상된 색상 매핑 옵션)
        if (this.layer) {
            this.layer.set('useEnhancedColors', this.useEnhancedColors);
        }
    }
    /**
     * 데이터 설정 메서드 (호환성용, 실제 데이터는 PNG 이미지에 고정됨)
     */
    setWeatherData(data) {
        // 이 구현에서는 외부 데이터를 직접 설정하지 않음
        // 필요하다면 updateLayer 호출 또는 다른 로직 구현 가능
        console.warn('setWeatherData called on WindDataTileLayer, but data is static.');
        this.updateLayer(); // 데이터가 변경된 것처럼 타일 갱신 시도
    }
}
//# sourceMappingURL=wind-data-tile-layer.js.map