import React from "react";
import { Maximize2, RotateCcw } from "lucide-react";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import {
  LasPointSample,
  preparePointCloud,
  PreparedPointCloud,
} from "./pointCloudUtils";

type CameraView = "iso" | "top" | "front" | "side";

const DEFAULT_MAX_RENDER = 10_000_000;
const DEFAULT_DENSITY_STEP = 10;
const DEFAULT_POINT_SIZE = 0.42;

function densityStepFromConfig(percent: number | undefined): number {
  if (!Number.isFinite(percent)) return DEFAULT_DENSITY_STEP;
  return Math.max(1, Math.min(10, Math.round(percent! / 10)));
}

function pointSizeFromConfig(size: number | undefined): number {
  if (!Number.isFinite(size)) return DEFAULT_POINT_SIZE;
  return Math.max(0.1, Math.min(2.5, size!));
}

type SceneObjects = {
  renderer: THREE.WebGLRenderer;
  camera: THREE.PerspectiveCamera;
  controls: OrbitControls;
  geometry: THREE.BufferGeometry;
  material: THREE.PointsMaterial;
  pointCloud: THREE.Points;
  axes: THREE.AxesHelper;
};

export function PointCloudViewer({
  url,
  label,
  body,
  bgLayer = "satellite",
  gisLayer = "none",
}: {
  url: string;
  label?: string;
  body?: Record<string, unknown>;
  bgLayer?: string;
  gisLayer?: string;
}) {
  const mountRef = React.useRef<HTMLDivElement | null>(null);
  const sceneRef = React.useRef<SceneObjects | null>(null);
  const preparedRef = React.useRef<PreparedPointCloud | null>(null);
  const initialPointSizeAppliedRef = React.useRef(false);

  const [sample, setSample] = React.useState<LasPointSample | null>(null);
  const [status, setStatus] = React.useState("포인트 데이터 로딩 중...");
  const [pointSize, setPointSize] = React.useState(DEFAULT_POINT_SIZE);
  const [showAxes, setShowAxes] = React.useState(true);
  const [densityStep, setDensityStep] = React.useState(DEFAULT_DENSITY_STEP);
  const [threeScaleMeters, setThreeScaleMeters] = React.useState<number | null>(null);
  const [threeScalePixels, setThreeScalePixels] = React.useState<number | null>(null);

  const bodyKey = body ? JSON.stringify(body) : null;

  // 1. 데이터 로드
  React.useEffect(() => {
    setSample(null);
    setStatus("포인트 데이터 로딩 중...");
    setDensityStep(DEFAULT_DENSITY_STEP);
    let cancelled = false;
    const init: RequestInit = bodyKey
      ? { method: "POST", headers: { "Content-Type": "application/json" }, body: bodyKey }
      : {};
    fetch(url, init)
      .then((r) => {
        if (!r.ok) throw new Error(`데이터 조회 실패: ${r.status}`);
        return r.json();
      })
      .then((data: LasPointSample) => {
        if (cancelled) return;
        setDensityStep(densityStepFromConfig(data.viewer_config?.initial_density_percent));
        if (!initialPointSizeAppliedRef.current) {
          setPointSize(pointSizeFromConfig(data.viewer_config?.initial_point_size));
          initialPointSizeAppliedRef.current = true;
        }
        setSample(data);
      })
      .catch((e) => {
        if (!cancelled) setStatus(e instanceof Error ? e.message : "데이터를 불러오지 못했습니다.");
      });
    return () => { cancelled = true; };
  }, [url, bodyKey]);

  // 2. WebGL 씬 초기화 및 지도/GIS 렌더링
  React.useEffect(() => {
    const mount = mountRef.current;
    if (!mount || !sample || sample.error) return;
    mount.innerHTML = "";

    const width = mount.clientWidth;
    const height = mount.clientHeight;
    const scene = new THREE.Scene();
    scene.background = new THREE.Color(0xf5f7f9);

    const camera = new THREE.PerspectiveCamera(55, width / height, 0.1, 22000);
    const renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
    renderer.setSize(width, height);
    mount.appendChild(renderer.domElement);

    const controls = new OrbitControls(camera, renderer.domElement);
    controls.enableDamping = true;
    controls.dampingFactor = 0.08;
    controls.screenSpacePanning = true;

    const geometry = new THREE.BufferGeometry();
    const material = new THREE.PointsMaterial({ size: pointSize, vertexColors: true, sizeAttenuation: true });
    const pointCloud = new THREE.Points(geometry, material);
    scene.add(pointCloud);

    // 쓰레기 수집 및 자원 관리 목록
    let bgPlane: THREE.Mesh | null = null;
    const gisObjects: THREE.Object3D[] = [];

    const projectId = sample.project_id || "sample";
    const API_BASE = "http://127.0.0.1:8000";

    const [xMin, xMax] = sample.bounds.x;
    const [yMin, yMax] = sample.bounds.y;
    const [zMin, zMax] = sample.bounds.z;
    const xMid = (xMin + xMax) / 2;
    const yMid = (yMin + yMax) / 2;
    const zMid = (zMin + zMax) / 2;
    const xSpan = Math.max(xMax - xMin, 1e-9);
    const ySpan = Math.max(yMax - yMin, 1e-9);
    const zSpan = Math.max(zMax - zMin, 1e-9);
    const scale = 180 / Math.max(xSpan, ySpan, zSpan);

    // ---- A. VWorld 지도 배경(위성/백지도) 렌더링 (gisLayer가 'none'이고 bgLayer가 지정된 경우) ----
    if (gisLayer === "none" && bgLayer !== "none") {
      fetch(`${API_BASE}/api/projects/${projectId}/vworld-meta?layer_name=${bgLayer}`)
        .then(res => {
          if (!res.ok) throw new Error("VWorld 메타데이터 없음");
          return res.json();
        })
        .then(meta => {
          const planeW = meta.width_meters * scale;
          const planeH = meta.height_meters * scale;
          const planeGeo = new THREE.PlaneGeometry(planeW, planeH);
          
          const textureLoader = new THREE.TextureLoader();
          textureLoader.load(
            `${API_BASE}/api/projects/${projectId}/vworld-map?layer_name=${bgLayer}`,
            (texture) => {
              const planeMat = new THREE.MeshBasicMaterial({
                map: texture,
                side: THREE.DoubleSide,
                transparent: true,
                opacity: 0.85
              });
              bgPlane = new THREE.Mesh(planeGeo, planeMat);
              bgPlane.rotation.x = -Math.PI / 2;
              
              const planeCenterX = (meta.center_x - xMid) * scale;
              const planeCenterY = -(meta.center_y - yMid) * scale;
              const planeCenterZ = (zMin - zMid) * scale - 0.5;
              
              bgPlane.position.set(planeCenterX, planeCenterZ, planeCenterY);
              scene.add(bgPlane);
            }
          );
        })
        .catch(e => console.log("VWorld 배경 로드 대기/실패:", e.message));
    }

    // ---- B. 국가 GIS GeoJSON 벡터 데이터 단독 렌더링 ----
    if (gisLayer !== "none") {
      Promise.all([
        fetch(`${API_BASE}/api/projects/${projectId}/geojson?layer=${encodeURIComponent(gisLayer)}`).then(r => {
          if (!r.ok) throw new Error("GIS GeoJSON 로드 실패");
          return r.json();
        }),
        fetch(`${API_BASE}/api/projects/${projectId}/vworld-meta?layer_name=white`).then(r => {
          if (!r.ok) throw new Error("VWorld 메타 로드 실패");
          return r.json();
        }),
      ])
        .then(([geoData, meta]) => {
          // meta.lon_min/max, lat_min/max (WGS84) ↔ meta.x_min/max, y_min/max (로컬 미터)
          // 비율 보간으로 WGS84 → 로컬 미터 변환 (소규모 지역에서 충분히 정확)
          const lonRange = meta.lon_max - meta.lon_min;
          const latRange = meta.lat_max - meta.lat_min;
          const xRange = meta.x_max - meta.x_min;
          const yRange = meta.y_max - meta.y_min;

          const toLocal = (lon: number, lat: number): [number, number] => {
            const localX_m = meta.x_min + ((lon - meta.lon_min) / lonRange) * xRange;
            const localY_m = meta.y_min + ((lat - meta.lat_min) / latRange) * yRange;
            return [
              (localX_m - xMid) * scale,
              -(localY_m - yMid) * scale,
            ];
          };

          const features = geoData.features || [];

          const colors: Record<string, number> = {
            "지적도": 0xff5500,
            "수계망": 0x00aaff,
            "산사태": 0xff0055,
            "행정구역_시군구": 0x333333,
            "행정구역_읍면동": 0x666666,
          };
          const layerColor = colors[gisLayer] || 0x00aa00;
          const groundZ = (zMin - zMid) * scale + 0.1;

          features.forEach((feat: any) => {
            const geom = feat.geometry;
            if (!geom) return;

            const drawRing = (ring: number[][]) => {
              const pts: THREE.Vector3[] = ring.map((pt) => {
                const [lx, ly] = toLocal(pt[0], pt[1]);
                return new THREE.Vector3(lx, groundZ, ly);
              });
              if (pts.length === 0) return;
              const lineGeo = new THREE.BufferGeometry().setFromPoints(pts);
              const lineMat = new THREE.LineBasicMaterial({ color: layerColor, linewidth: 2 });
              const line = new THREE.Line(lineGeo, lineMat);
              scene.add(line);
              gisObjects.push(line);
            };

            const drawCoordinates = (coords: any[], type: string) => {
              if (type === "Polygon") {
                coords.forEach((ring: number[][]) => drawRing(ring));
              } else if (type === "MultiPolygon") {
                coords.forEach((poly: any[]) => drawCoordinates(poly, "Polygon"));
              } else if (type === "LineString") {
                drawRing(coords as number[][]);
              } else if (type === "MultiLineString") {
                (coords as number[][][]).forEach((line) => drawRing(line));
              }
            };

            drawCoordinates(geom.coordinates, geom.type);
          });
          setStatus(`${gisLayer} 벡터 레이어 렌더링 완료 (${features.length}개 객체)`);
        })
        .catch(err => console.error(err));
    }

    const axes = new THREE.AxesHelper(45);
    axes.visible = showAxes;
    scene.add(axes);

    const setCameraView = (view: CameraView) => {
      const positions: Record<CameraView, [number, number, number]> = {
        iso: [120, 95, 135],
        top: [0, 190, 0.001], // top view height
        front: [0, 60, 240],
        side: [240, 60, 0],
      };
      camera.position.set(...positions[view]);
      camera.lookAt(0, 0, 0);
      controls.target.set(0, 0, 0);
      controls.update();
    };
    setCameraView("top");

    sceneRef.current = { renderer, camera, controls, geometry, material, pointCloud, axes };
    (mount as any).__setCameraView = setCameraView;

    let frameId = 0;
    function animate() {
      controls.update();
      renderer.render(scene, camera);
      
      // 실시간 3D 포인트 클라우드 카메라 줌 거리에 따른 척도 계산
      if (sample && sample.bounds) {
        const [xMin, xMax] = sample.bounds.x;
        const [yMin, yMax] = sample.bounds.y;
        const [zMin, zMax] = sample.bounds.z;
        const xSpan = xMax - xMin;
        const ySpan = yMax - yMin;
        const zSpan = zMax - zMin;
        const maxSpan = Math.max(xSpan, ySpan, zSpan);
        const internalScale = 180 / maxSpan; // 1m = internalScale scene units

        const dist = camera.position.distanceTo(controls.target);
        // scene units per pixel
        const clientWidth = mountRef.current?.clientWidth ?? width;
        const sceneUnitsPerPixel = (2 * Math.tan((camera.fov * Math.PI) / 360) * dist) / (clientWidth || 1200);
        // meters per pixel
        const metersPerPixel = sceneUnitsPerPixel / internalScale;

        const roughMeters = 100 * metersPerPixel;
        const prettyMeters = roughMeters < 5 ? 2 : roughMeters < 15 ? 10 : roughMeters < 35 ? 20 : roughMeters < 75 ? 50 : roughMeters < 150 ? 100 : roughMeters < 350 ? 200 : roughMeters < 750 ? 500 : roughMeters < 1500 ? 1000 : roughMeters < 3500 ? 2000 : roughMeters < 7500 ? 5000 : 10000;
        
        setThreeScaleMeters(prettyMeters);
        setThreeScalePixels((prettyMeters * internalScale) / sceneUnitsPerPixel);
      }

      frameId = requestAnimationFrame(animate);
    }
    animate();

    function handleResize() {
      if (!mount) return;
      const w = mount.clientWidth;
      const h = mount.clientHeight;
      camera.aspect = w / h;
      camera.updateProjectionMatrix();
      renderer.setSize(w, h);
    }
    window.addEventListener("resize", handleResize);

    return () => {
      cancelAnimationFrame(frameId);
      window.removeEventListener("resize", handleResize);
      controls.dispose();
      geometry.dispose();
      material.dispose();
      renderer.dispose();
      if (bgPlane) {
        bgPlane.geometry.dispose();
        if (Array.isArray(bgPlane.material)) {
          bgPlane.material.forEach(m => m.dispose());
        } else {
          bgPlane.material.dispose();
        }
      }
      gisObjects.forEach(obj => {
        if (obj instanceof THREE.Line) {
          obj.geometry.dispose();
          if (Array.isArray(obj.material)) {
            obj.material.forEach(m => m.dispose());
          } else {
            obj.material.dispose();
          }
        }
      });
      mount.innerHTML = "";
      sceneRef.current = null;
      preparedRef.current = null;
    };
  }, [sample, bgLayer, gisLayer]);

  React.useEffect(() => {
    const s = sceneRef.current;
    if (!s || !sample || sample.error) return;
    
    // RGB 정보 유무에 따른 색상 모드 설정
    const mode = sample.rgb ? "rgb" : "height";
    
    const configuredMaxRender = sample.viewer_config?.max_render_points;
    const maxRender = Number.isFinite(configuredMaxRender)
      ? Math.max(1, Math.floor(configuredMaxRender!))
      : DEFAULT_MAX_RENDER;
    const prepared = preparePointCloud(sample, maxRender, null, null, densityStep, mode, false);
    preparedRef.current = prepared;
    s.geometry.setAttribute("position", new THREE.BufferAttribute(prepared.positions, 3));
    s.geometry.setAttribute("color", new THREE.BufferAttribute(prepared.colors, 3));
    s.geometry.computeBoundingSphere();
    // 고도 최소/최대 정보를 가장 가까운 10m 단위(정수)로 라운딩하여 포인트 개수와 함께 노출
    let heightRangeStr = "";
    if (sample.bounds && sample.bounds.z) {
      const zMin = sample.bounds.z[0];
      const zMax = sample.bounds.z[1];
      const nearestMin10 = Math.round(zMin / 10) * 10;
      const nearestMax10 = Math.round(zMax / 10) * 10;
      heightRangeStr = ` [높이범위: ~${nearestMin10}m ... ${nearestMax10}m~]`;
    }
    
    setStatus(`${prepared.originalPoints.length.toLocaleString()}개 점 표시 중${heightRangeStr}`);
  }, [sample, densityStep]);

  // 4. 점 크기 업데이트
  React.useEffect(() => {
    const m = sceneRef.current?.material;
    if (m) m.size = pointSize;
  }, [pointSize]);

  // 5. 축 표시 업데이트
  React.useEffect(() => {
    const a = sceneRef.current?.axes;
    if (a) a.visible = showAxes;
  }, [showAxes]);

  function setView(view: CameraView) {
    const fn = (mountRef.current as any)?.__setCameraView as ((v: CameraView) => void) | undefined;
    fn?.(view);
  }

  return (
    <div className="point-viewer">
      {/* 툴바 */}
      <div className="point-toolbar point-toolbar--stacked">
        <div>
          <strong>{label ?? "3D 포인트클라우드 뷰어"}</strong>
          <span>{status}</span>
        </div>
        <div className="viewer-controls">
          <button type="button" onClick={() => setView("iso")} title="사시도"><Maximize2 size={15} />사시도</button>
          <button type="button" onClick={() => setView("top")}>상단</button>
          <button type="button" onClick={() => setView("front")}>정면</button>
          <button type="button" onClick={() => setView("side")}>측면</button>
          <button type="button" onClick={() => setView("iso")} title="뷰 초기화"><RotateCcw size={15} />리셋</button>
          <label className="toggle-label">
            <input type="checkbox" checked={showAxes} onChange={(e) => setShowAxes(e.target.checked)} /> 축
          </label>
        </div>
      </div>

      {/* 옵션: 점 크기 + 밀도 + Z 클리핑 */}
      <div className="viewer-options">
        <label>
          점 크기
          <input type="range" min="0.1" max="2.5" step="0.01" value={pointSize}
            onChange={(e) => setPointSize(Number(e.target.value))} />
        </label>
        <label>
          밀도&nbsp;<span className="viewer-option-val">{densityStep * 10}%</span>
          <input type="range" min="1" max="10" step="1" value={densityStep}
            onChange={(e) => setDensityStep(Number(e.target.value))} />
        </label>
      </div>

      <div className="subtle-notice">
        왼쪽 드래그: 회전 | 휠: 줌 | 오른쪽 드래그: 팬
      </div>

      {sample?.error ? (
        <div className="viewer-error">{sample.error}</div>
      ) : (
        <div style={{ position: "relative", overflow: "hidden" }}>
          <div ref={mountRef} className="three-viewer" />
          
          {/* 3D 포인트 클라우드 전용 척도 (Scale Bar) 표시 */}
          {threeScaleMeters !== null && (
            <div style={{
              position: "absolute",
              bottom: "16px",
              left: "16px",
              background: "rgba(255, 255, 255, 0.9)",
              border: "1.5px solid #1e293b",
              borderTop: "none",
              height: "8px",
              width: `${threeScalePixels}px`,
              zIndex: 10,
              display: "flex",
              flexDirection: "column",
              alignItems: "center",
              justifyContent: "flex-end",
              pointerEvents: "none"
            }}>
              <span style={{ fontSize: "10px", fontWeight: "bold", color: "#1e293b", position: "absolute", bottom: "10px", whiteSpace: "nowrap" }}>
                {threeScaleMeters >= 1000 ? `${(threeScaleMeters / 1000).toFixed(0)} km` : `${threeScaleMeters} m`}
              </span>
            </div>
          )}
        </div>
      )}
    </div>
  );
}
