import { useState, useEffect, useCallback, useRef } from 'react';
import { HNSLeftPanel } from './HNSLeftPanel';
import type { HNSInputParams } from './HNSLeftPanel';
import { HNSRightPanel } from './HNSRightPanel';
import { MapView } from '@common/components/map/MapView';
import { HNSAnalysisListTable } from './HNSAnalysisListTable';
import { HNSTheoryView } from './HNSTheoryView';
import { HNSSubstanceView } from './HNSSubstanceView';
import { HNSScenarioView } from './HNSScenarioView';
import { HNSRecalcModal } from './HNSRecalcModal';
import type { RecalcParams } from './HNSRecalcModal';
import {
  useSubMenu,
  navigateToTab,
  setReportGenCategory,
  setHnsReportPayload,
} from '@common/hooks/useSubMenu';
import { windDirToCompass } from '../hooks/useWeatherFetch';
import { useAuthStore } from '@common/store/authStore';
import { createHnsAnalysis, saveHnsAnalysis, fetchHnsAnalysis } from '../services/hnsApi';
import { computeDispersion } from '../utils/dispersionEngine';
import { getSubstanceToxicity } from '../utils/toxicityData';
import type {
  DispersionPoint,
  DispersionGridResult,
  DispersionModel,
  MeteoParams,
  SourceParams,
  SimParams,
  AlgorithmType,
} from '../utils/dispersionTypes';

/* ─── HNS 매뉴얼 뷰어 컴포넌트 ─── */
function HNSManualViewer() {
  const card = 'rounded-md p-4 mb-3';

  return (
    <div
      className="flex-1 overflow-y-auto bg-bg-base"
      style={{ scrollbarWidth: 'thin', scrollbarColor: 'var(--stroke-light) transparent' }}
    >
      <div className="px-5 py-4 max-w-[1200px] mx-auto">
        {/* 헤더 */}
        <div className="flex items-center justify-between mb-4">
          <div>
            <div className="text-base font-bold">📖 해양 HNS 대응 매뉴얼</div>
            <div className="text-label-2 text-fg-disabled mt-0.5">
              Marine HNS Response Manual — Bonn Agreement / HELCOM / REMPEC (WestMOPoCo 2024
              한국어판)
            </div>
          </div>
        </div>

        {/* 목차 카드 그리드 */}
        <div className="grid mb-5" style={{ gridTemplateColumns: 'repeat(4,1fr)', gap: '10px' }}>
          {[
            {
              icon: '📘',
              title: '1. 서론',
              desc: 'HNS 정의 · OPRC-HNS 의정서 · HNS 협약 범위 및 목적',
              color: 'var(--color-accent)',
            },
            {
              icon: '⚖️',
              title: '2. IMO 협약·의정서·규칙',
              desc: 'SOLAS · MARPOL · IBC Code · IMDG Code · IGC Code',
              color: 'var(--color-accent)',
            },
            {
              icon: '🔬',
              title: '3. HNS 거동 및 유해요소',
              desc: 'SEBC 거동분류 · MSDS · GESAMP · 물리화학적 특성',
              color: 'var(--color-accent)',
            },
            {
              icon: '🛡️',
              title: '4. 대비',
              desc: '위험 평가 · 비상 계획 · 교육훈련 · 장비 비축',
              color: 'var(--color-accent)',
            },
            {
              icon: '🚨',
              title: '5. 대응',
              desc: '최초 조치 · 안전구역 · PPE · 모니터링 · 대응 기술',
              color: 'var(--color-info)',
            },
            {
              icon: '🔄',
              title: '6. 유출 후 관리',
              desc: '비용 문서화 · 환경 회복 · 사고 검토 · 교훈',
              color: 'var(--color-accent)',
            },
            {
              icon: '📋',
              title: '7. 사례연구',
              desc: '실제 HNS 해양사고 사례 분석 및 교훈',
              color: 'var(--color-accent)',
            },
            {
              icon: '📊',
              title: '8. 자료표',
              desc: '물질별 데이터시트 · AEGL · 노출 한계값',
              color: 'var(--color-accent)',
            },
          ].map((ch) => (
            <div
              key={ch.title}
              className={card}
              style={{
                background: 'var(--bg-card)',
                border: '1px solid var(--stroke-default)',
                cursor: 'pointer',
                transition: '.2s',
              }}
            >
              <div className="text-[20px] mb-1.5">{ch.icon}</div>
              <div className="text-label-2 font-bold">{ch.title}</div>
              <div className="text-caption text-fg-disabled mt-1 leading-[1.4]">{ch.desc}</div>
            </div>
          ))}
        </div>

        {/* SEBC 거동 분류 */}
        <div className={`${card} bg-bg-card border border-stroke`}>
          <div className="text-title-4 font-bold mb-2.5">
            SEBC 거동 분류 (Standard European Behaviour Classification)
          </div>
          <div className="text-caption text-fg-disabled mb-2.5 leading-normal">
            물질의 물리적·화학적 특성(용해도, 밀도, 증기압, 점도)에 따라 이론적 거동을 5가지 주요
            범주 + 7가지 하위 범주로 분류
          </div>
          <div className="grid gap-2" style={{ gridTemplateColumns: 'repeat(5,1fr)' }}>
            {[
              {
                icon: '💨',
                label: 'G — 가스',
                desc: '대기 중 확산\n증기압 > 101.3kPa\n예: 암모니아, 염소',
                color: 'rgba(139,92,246',
              },
              {
                icon: '🌫️',
                label: 'E — 증발',
                desc: '수면→대기 증발\n증기압 > 3kPa\n예: 벤젠, 톨루엔',
                color: 'rgba(6,182,212',
              },
              {
                icon: '🟡',
                label: 'F — 부유',
                desc: '해수면에 부유\n밀도 < 1.025\n예: 스티렌, 크실렌',
                color: 'rgba(251,191,36',
              },
              {
                icon: '💧',
                label: 'D — 용해',
                desc: '해수에 용해\n용해도 > 5%\n예: 메탄올, 염산',
                color: 'rgba(6,182,212',
              },
              {
                icon: '⬇️',
                label: 'S — 침강',
                desc: '해저로 침강\n밀도 > 1.025\n예: EDC, 사염화탄소',
                color: 'rgba(139,148,158',
              },
            ].map((s) => (
              <div
                key={s.label}
                className="text-center px-[6px] py-[10px] rounded-sm"
                style={{ background: `${s.color},.08)`, border: `1px solid ${s.color},.2)` }}
              >
                <div className="text-[22px] mb-1">{s.icon}</div>
                <div className="text-label-2 font-bold" style={{ color: `${s.color},1)` }}>
                  {s.label}
                </div>
                <div className="text-fg-disabled whitespace-pre-line text-caption mt-[3px] leading-[1.3]">
                  {s.desc}
                </div>
              </div>
            ))}
          </div>
        </div>

        {/* 출처 */}
        <div className="text-fg-disabled rounded-sm bg-bg-card p-[10px] text-caption leading-[1.5]">
          <b>출처:</b> Marine HNS Response Manual — Bonn Agreement / HELCOM / REMPEC (WestMOPoCo
          Project, 2024 한국어판)
          <br />
          번역: 원해민, 이시연, 양보경, 강성길, 이성엽 — KRISO 선박해양플랜트연구소 / NOWPAP MERRAC
          <br />
          원본: Alcaro L., Brandt J., Giraud W., Mannozzi M., Nicolas-Kopec A. (2021) ISBN:
          978-2-87893-147-1
        </div>
      </div>
    </div>
  );
}

/* ─── 시간 슬라이더 컴포넌트 ─── */
function DispersionTimeSlider({
  currentFrame,
  totalFrames,
  isPlaying,
  onFrameChange,
  onPlayPause,
  dt,
}: {
  currentFrame: number;
  totalFrames: number;
  isPlaying: boolean;
  onFrameChange: (frame: number) => void;
  onPlayPause: () => void;
  dt: number;
}) {
  const currentTime = (currentFrame + 1) * dt;
  const endTime = totalFrames * dt;

  return (
    <div
      className="absolute bottom-4 left-1/2 -translate-x-1/2 z-10 flex items-center gap-3 px-4 py-2.5 rounded-lg"
      style={{
        background: 'rgba(10,22,40,0.92)',
        border: '1px solid rgba(6,182,212,0.25)',
        backdropFilter: 'blur(8px)',
        minWidth: '360px',
      }}
    >
      <button
        onClick={onPlayPause}
        className="flex items-center justify-center w-7 h-7 rounded-full text-title-3"
        style={{
          background: isPlaying ? 'rgba(59,130,246,0.2)' : 'rgba(6,182,212,0.2)',
          border: `1px solid ${isPlaying ? 'rgba(59,130,246,0.4)' : 'rgba(6,182,212,0.4)'}`,
          cursor: 'pointer',
        }}
      >
        {isPlaying ? '⏸' : '▶'}
      </button>

      <div className="flex-1 flex flex-col gap-1">
        <div className="flex items-center justify-between text-caption">
          <span className="text-color-accent font-mono font-bold">t = {currentTime}s</span>
          <span className="text-fg-disabled font-mono">{endTime}s</span>
        </div>
        <input
          type="range"
          min={0}
          max={totalFrames - 1}
          value={currentFrame}
          onChange={(e) => onFrameChange(parseInt(e.target.value))}
          className="w-full h-1 accent-[var(--color-accent)]"
          style={{ cursor: 'pointer' }}
        />
      </div>

      <div className="text-caption text-fg-disabled font-mono whitespace-nowrap">
        {currentFrame + 1}/{totalFrames}
      </div>
    </div>
  );
}

/* ─── 메인 HNSView ─── */
export function HNSView() {
  const { activeSubTab, setActiveSubTab } = useSubMenu('hns');
  const { user } = useAuthStore();
  const [leftCollapsed, setLeftCollapsed] = useState(false);
  const [rightCollapsed, setRightCollapsed] = useState(false);
  const [incidentCoord, setIncidentCoord] = useState<{ lon: number; lat: number } | null>(null);
  const [flyToCoord, setFlyToCoord] = useState<{ lon: number; lat: number } | undefined>(undefined);
  const [isSelectingLocation, setIsSelectingLocation] = useState(false);
  const [isRunningPrediction, setIsRunningPrediction] = useState(false);
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const [dispersionResult, setDispersionResult] = useState<any>(null);
  const [recalcModalOpen, setRecalcModalOpen] = useState(false);

  // 확산 엔진 결과
  const [heatmapData, setHeatmapData] = useState<DispersionPoint[]>([]);
  const [computedResult, setComputedResult] = useState<DispersionGridResult | null>(null);
  const [allTimeFrames, setAllTimeFrames] = useState<DispersionGridResult[]>([]);
  const [currentFrame, setCurrentFrame] = useState(0);
  const [isPuffPlaying, setIsPuffPlaying] = useState(false);

  // 좌측 패널 입력 파라미터 (state로 관리하여 변경 시 재계산 트리거)
  const [inputParams, setInputParams] = useState<HNSInputParams | null>(null);
  const [loadedParams, setLoadedParams] = useState<Partial<HNSInputParams> | null>(null);
  const hasRunOnce = useRef(false); // 최초 실행 여부
  const mapCaptureRef = useRef<(() => string | null) | null>(null);

  const handleReset = useCallback(() => {
    setDispersionResult(null);
    setHeatmapData([]);
    setComputedResult(null);
    setAllTimeFrames([]);
    setCurrentFrame(0);
    setIsPuffPlaying(false);
    setInputParams(null);
    setIncidentCoord(null);
    hasRunOnce.current = false;
  }, []);

  const handleParamsChange = useCallback((params: HNSInputParams) => {
    setInputParams(params);
  }, []);

  const handleMapClick = (lon: number, lat: number) => {
    if (isSelectingLocation) {
      setIncidentCoord({ lon, lat });
      setIsSelectingLocation(false);
    }
  };

  // 시간 애니메이션 (puff/dense_gas)
  useEffect(() => {
    if (!isPuffPlaying || allTimeFrames.length === 0) return;

    const interval = setInterval(() => {
      setCurrentFrame((prev) => {
        const next = prev + 1;
        if (next >= allTimeFrames.length) {
          setIsPuffPlaying(false);
          return prev;
        }
        setHeatmapData(allTimeFrames[next].points);
        setComputedResult(allTimeFrames[next]);
        return next;
      });
    }, 300);

    return () => clearInterval(interval);
  }, [isPuffPlaying, allTimeFrames]);

  const handleFrameChange = (frame: number) => {
    setCurrentFrame(frame);
    if (allTimeFrames[frame]) {
      setHeatmapData(allTimeFrames[frame].points);
      setComputedResult(allTimeFrames[frame]);
    }
  };

  /** 확산 계산 핵심 로직 (버튼 클릭 + 파라미터 변경 시 공유) */
  const runComputation = useCallback(
    (params: HNSInputParams | null, coord: { lon: number; lat: number }) => {
      const substanceName = params?.substance || '톨루엔 (Toluene)';
      const tox = getSubstanceToxicity(substanceName);

      const weather = params?.weather;
      const meteo: MeteoParams = {
        windSpeed: weather?.windSpeed ?? 5.0,
        windDirDeg: weather?.windDirection ?? 270,
        stability: weather?.stability ?? 'D',
        temperature: (weather?.temperature ?? 15) + 273.15,
        pressure: 101325,
        mixingHeight: 800,
      };

      const releaseType = params?.releaseType || '연속 유출';
      const source: SourceParams = {
        Q: params?.emissionRate ?? tox.Q,
        QTotal: params?.totalRelease ?? tox.QTotal,
        x0: 0,
        y0: 0,
        z0: params?.releaseHeight ?? 0.5,
        releaseDuration: releaseType === '연속 유출' ? (params?.releaseDuration ?? 300) : 0,
        molecularWeight: tox.mw,
        vaporPressure: tox.vaporPressure,
        densityGas: tox.densityGas,
        poolRadius: params?.poolRadius ?? tox.poolRadius,
      };

      const sim: SimParams = {
        xRange: [-100, 10000],
        yRange: [-2000, 2000],
        nx: 300,
        ny: 200,
        zRef: 1.5,
        tStart: 0,
        tEnd: 600,
        dt: 30,
      };

      const modelType: DispersionModel =
        releaseType === '연속 유출' ? 'plume' : releaseType === '순간 유출' ? 'puff' : 'dense_gas';

      const algo = (params?.algorithm || 'ALOHA (EPA)') as AlgorithmType;

      if (modelType === 'plume') {
        const result = computeDispersion({
          meteo,
          source,
          sim,
          modelType,
          originLon: coord.lon,
          originLat: coord.lat,
          substanceName,
          t: sim.dt,
          algorithm: algo,
        });
        console.log(
          '[HNS] plume 계산 완료:',
          result.points.length,
          '포인트, maxConc:',
          result.maxConcentration.toFixed(2),
          'ppm',
        );
        setComputedResult(result);
        setHeatmapData(result.points);
        setAllTimeFrames([result]);
        setCurrentFrame(0);
        setIsPuffPlaying(false);
      } else {
        const times: number[] = [];
        for (let t = sim.dt; t <= sim.tEnd; t += sim.dt) times.push(t);
        const frames = times.map((t) =>
          computeDispersion({
            meteo,
            source,
            sim,
            modelType,
            originLon: coord.lon,
            originLat: coord.lat,
            substanceName,
            t,
            algorithm: algo,
          }),
        );
        console.log(
          '[HNS]',
          modelType,
          '계산 완료:',
          frames.length,
          '프레임, 첫 프레임:',
          frames[0].points.length,
          '포인트',
        );
        setAllTimeFrames(frames);
        setComputedResult(frames[0]);
        setHeatmapData(frames[0].points);
        setCurrentFrame(0);
        setIsPuffPlaying(true);
      }

      // AEGL 구역 (MapView 레거시 호환)
      const resultForZones =
        modelType === 'plume'
          ? computeDispersion({
              meteo,
              source,
              sim,
              modelType,
              originLon: coord.lon,
              originLat: coord.lat,
              substanceName,
              t: sim.dt,
              algorithm: algo,
            })
          : computeDispersion({
              meteo,
              source,
              sim,
              modelType,
              originLon: coord.lon,
              originLat: coord.lat,
              substanceName,
              t: sim.dt * 5,
              algorithm: algo,
            });

      return { tox, meteo, resultForZones, substanceName };
    },
    [],
  );

  const handleRunPrediction = async (paramsOverride?: HNSInputParams | null) => {
    setIsRunningPrediction(true);

    try {
      const params = paramsOverride ?? inputParams;
      if (!incidentCoord) {
        alert('사고 지점을 먼저 지도에서 선택하세요.');
        setIsRunningPrediction(false);
        return;
      }

      // 1. 계산 먼저 실행 (동기, 히트맵 즉시 표시)
      const { tox, meteo, resultForZones, substanceName } = runComputation(params, incidentCoord);
      hasRunOnce.current = true;

      setDispersionResult({
        hnsAnlysSn: 0,
        zones: [
          {
            level: 'AEGL-3',
            color: '#ef4444',
            radius: resultForZones.aeglDistances.aegl3,
            angle: meteo.windDirDeg,
          },
          {
            level: 'AEGL-2',
            color: '#f97316',
            radius: resultForZones.aeglDistances.aegl2,
            angle: meteo.windDirDeg,
          },
          {
            level: 'AEGL-1',
            color: '#eab308',
            radius: resultForZones.aeglDistances.aegl1,
            angle: meteo.windDirDeg,
          },
        ].filter((z) => z.radius > 0),
        timestamp: new Date().toISOString(),
        windDirection: meteo.windDirDeg,
        substance: substanceName,
        concentration: {
          'AEGL-3': `${tox.aegl3} ppm`,
          'AEGL-2': `${tox.aegl2} ppm`,
          'AEGL-1': `${tox.aegl1} ppm`,
        },
        maxConcentration: resultForZones.maxConcentration,
      });

      // 2. 분석 레코드 DB 저장 (비동기, 실패해도 무시)
      try {
        const acdntDtm =
          params?.accidentDate && params?.accidentTime
            ? `${params.accidentDate}T${params.accidentTime}:00`
            : params?.accidentDate || undefined;
        const result = await createHnsAnalysis({
          anlysNm: params?.accidentName || `HNS 분석 ${new Date().toLocaleDateString('ko-KR')}`,
          acdntDtm,
          lon: incidentCoord.lon,
          lat: incidentCoord.lat,
          locNm: `${incidentCoord.lat.toFixed(4)} / ${incidentCoord.lon.toFixed(4)}`,
          sbstNm: params?.substance,
          windSpd: params?.weather?.windSpeed,
          windDir:
            params?.weather?.windDirection != null
              ? String(params.weather.windDirection)
              : undefined,
          temp: params?.weather?.temperature,
          humid: params?.weather?.humidity,
          atmStblCd: params?.weather?.stability,
          analystNm: user?.name || undefined,
        });
        // DB 저장 성공 시 SN 업데이트
        setDispersionResult((prev: Record<string, unknown> | null) =>
          prev ? { ...prev, hnsAnlysSn: result.hnsAnlysSn } : prev,
        );
      } catch {
        // API 실패 시 무시 (히트맵은 이미 표시됨)
      }
    } catch (error) {
      console.error('대기확산 예측 오류:', error);
      alert('대기확산 예측 중 오류가 발생했습니다.');
    } finally {
      setIsRunningPrediction(false);
    }
  };

  /** 분석 결과 저장 */
  const handleSave = async () => {
    if (!dispersionResult || !inputParams || !computedResult) {
      alert('저장할 분석 결과가 없습니다. 먼저 예측을 실행해주세요.');
      return;
    }

    const rsltData: Record<string, unknown> = {
      inputParams: {
        substance: inputParams.substance,
        releaseType: inputParams.releaseType,
        emissionRate: inputParams.emissionRate,
        totalRelease: inputParams.totalRelease,
        releaseHeight: inputParams.releaseHeight,
        releaseDuration: inputParams.releaseDuration,
        poolRadius: inputParams.poolRadius,
        algorithm: inputParams.algorithm,
        criteriaModel: inputParams.criteriaModel,
        accidentDate: inputParams.accidentDate,
        accidentTime: inputParams.accidentTime,
        predictionTime: inputParams.predictionTime,
        accidentName: inputParams.accidentName,
      },
      coord: { lon: incidentCoord.lon, lat: incidentCoord.lat },
      zones: dispersionResult.zones,
      aeglDistances: computedResult.aeglDistances,
      aeglAreas: computedResult.aeglAreas,
      maxConcentration: computedResult.maxConcentration,
      modelType: computedResult.modelType,
      weather: {
        windSpeed: inputParams.weather.windSpeed,
        windDirection: inputParams.weather.windDirection,
        temperature: inputParams.weather.temperature,
        humidity: inputParams.weather.humidity,
        stability: inputParams.weather.stability,
      },
      aegl3: (computedResult.aeglDistances?.aegl3 ?? 0) > 0,
      aegl2: (computedResult.aeglDistances?.aegl2 ?? 0) > 0,
      aegl1: (computedResult.aeglDistances?.aegl1 ?? 0) > 0,
      damageRadius: `${((computedResult.aeglDistances?.aegl1 ?? 0) / 1000).toFixed(1)} km`,
    };

    // 위험등급 자동 판정
    let riskCd = 'LOW';
    if ((computedResult.aeglDistances?.aegl3 ?? 0) > 0) riskCd = 'CRITICAL';
    else if ((computedResult.aeglDistances?.aegl2 ?? 0) > 0) riskCd = 'HIGH';
    else if ((computedResult.aeglDistances?.aegl1 ?? 0) > 0) riskCd = 'MEDIUM';

    // DB 저장 시도
    let savedToDb = false;
    try {
      let sn = dispersionResult.hnsAnlysSn as number;
      if (!sn) {
        const fcstHrNum = parseInt(inputParams.predictionTime) || 24;
        const spilQtyVal =
          inputParams.releaseType === '순간 유출'
            ? inputParams.totalRelease
            : inputParams.emissionRate;
        const anlysNm =
          inputParams.accidentName || `HNS 분석 ${new Date().toLocaleDateString('ko-KR')}`;
        const saveAcdntDtm =
          inputParams.accidentDate && inputParams.accidentTime
            ? `${inputParams.accidentDate}T${inputParams.accidentTime}:00`
            : inputParams.accidentDate || undefined;
        const created = await createHnsAnalysis({
          anlysNm,
          acdntDtm: saveAcdntDtm,
          lon: incidentCoord.lon,
          lat: incidentCoord.lat,
          locNm: `${incidentCoord.lat.toFixed(4)} / ${incidentCoord.lon.toFixed(4)}`,
          sbstNm: inputParams.substance,
          spilQty: spilQtyVal,
          spilUnitCd: inputParams.releaseType === '순간 유출' ? 'g' : 'g/s',
          fcstHr: fcstHrNum,
          windSpd: inputParams.weather.windSpeed,
          windDir: String(inputParams.weather.windDirection),
          algoCd: inputParams.algorithm,
          critMdlCd: inputParams.criteriaModel,
          analystNm: user?.name || undefined,
        });
        sn = created.hnsAnlysSn;
        setDispersionResult((prev: Record<string, unknown> | null) =>
          prev ? { ...prev, hnsAnlysSn: sn } : prev,
        );
      }
      await saveHnsAnalysis(sn, { rsltData, execSttsCd: 'COMPLETED', riskCd });
      savedToDb = true;
    } catch {
      // DB 저장 실패 — localStorage fallback
    }

    // localStorage에도 저장 (DB 실패 시 fallback, 성공 시 백업)
    try {
      const localKey = 'hns_saved_analyses';
      const existing: Record<string, unknown>[] = JSON.parse(
        localStorage.getItem(localKey) || '[]',
      );
      const fcstHrLocal = parseInt(inputParams.predictionTime) || 24;
      const spilQtyLocal =
        inputParams.releaseType === '순간 유출'
          ? inputParams.totalRelease
          : inputParams.emissionRate;
      const entry = {
        id: Date.now(),
        anlysNm: inputParams.accidentName || `HNS 분석 ${new Date().toLocaleDateString('ko-KR')}`,
        acdntDtm:
          inputParams.accidentDate && inputParams.accidentTime
            ? `${inputParams.accidentDate}T${inputParams.accidentTime}:00`
            : inputParams.accidentDate || null,
        sbstNm: inputParams.substance,
        analystNm: user?.name || null,
        spilQty: spilQtyLocal,
        spilUnitCd: inputParams.releaseType === '순간 유출' ? 'g' : 'g/s',
        fcstHr: fcstHrLocal,
        regDtm: new Date().toISOString(),
        riskCd,
        rsltData,
      };
      existing.unshift(entry);
      // 최대 50건 유지
      if (existing.length > 50) existing.length = 50;
      localStorage.setItem(localKey, JSON.stringify(existing));
    } catch {
      // localStorage 저장 실패 무시
    }

    alert(
      savedToDb
        ? '분석 결과가 저장되었습니다.'
        : '분석 결과가 로컬에 저장되었습니다. (서버 연결 실패)',
    );
  };

  /** 분석 목록에서 불러오기 (DB 또는 localStorage) */
  const handleLoadAnalysis = async (sn: number, localRsltData?: Record<string, unknown>) => {
    try {
      let rslt: Record<string, unknown>;

      if (localRsltData) {
        // localStorage에서 직접 전달된 데이터
        rslt = localRsltData;
      } else {
        // DB에서 조회
        const analysis = await fetchHnsAnalysis(sn);
        if (!analysis.rsltData) {
          alert('저장된 분석 결과가 없습니다.');
          return;
        }
        rslt = analysis.rsltData as Record<string, unknown>;
      }
      const savedParams = rslt.inputParams as Record<string, unknown> | undefined;
      const savedCoord = rslt.coord as { lon: number; lat: number } | undefined;

      // 좌표 복원
      if (savedCoord) {
        setIncidentCoord(savedCoord);
      }

      // 입력 파라미터 복원 → HNSLeftPanel에 전달
      if (savedParams) {
        setLoadedParams({
          substance: savedParams.substance as string,
          releaseType: savedParams.releaseType as HNSInputParams['releaseType'],
          emissionRate: savedParams.emissionRate as number,
          totalRelease: savedParams.totalRelease as number,
          releaseHeight: savedParams.releaseHeight as number,
          releaseDuration: savedParams.releaseDuration as number,
          poolRadius: savedParams.poolRadius as number,
          algorithm: savedParams.algorithm as string,
          criteriaModel: savedParams.criteriaModel as string,
          accidentDate: savedParams.accidentDate as string,
          accidentTime: savedParams.accidentTime as string,
          predictionTime: savedParams.predictionTime as string,
          accidentName: savedParams.accidentName as string,
        });
      }

      // 탭 전환 → analysis
      setActiveSubTab('analysis');

      // 약간의 딜레이 후 계산 실행 (loadedParams → inputParams 동기화 대기)
      setTimeout(() => {
        if (savedParams && savedCoord) {
          const savedWeather = rslt.weather as Record<string, unknown> | undefined;
          const params: HNSInputParams = {
            substance: (savedParams.substance as string) || '톨루엔 (Toluene)',
            releaseType: (savedParams.releaseType as HNSInputParams['releaseType']) || '연속 유출',
            emissionRate: (savedParams.emissionRate as number) || 10,
            totalRelease: (savedParams.totalRelease as number) || 5000,
            releaseHeight: (savedParams.releaseHeight as number) || 0.5,
            releaseDuration: (savedParams.releaseDuration as number) || 300,
            poolRadius: (savedParams.poolRadius as number) || 5,
            algorithm: (savedParams.algorithm as string) || 'ALOHA (EPA)',
            criteriaModel: (savedParams.criteriaModel as string) || 'AEGL',
            accidentDate: (savedParams.accidentDate as string) || '',
            accidentTime: (savedParams.accidentTime as string) || '',
            predictionTime: (savedParams.predictionTime as string) || '24시간',
            accidentName: (savedParams.accidentName as string) || '',
            weather: {
              windSpeed: (savedWeather?.windSpeed as number) ?? 5.0,
              windDirection: (savedWeather?.windDirection as number) ?? 270,
              temperature: (savedWeather?.temperature as number) ?? 15,
              humidity: (savedWeather?.humidity as number) ?? 60,
              stability: ((savedWeather?.stability as string) ??
                'D') as HNSInputParams['weather']['stability'],
              isLoading: false,
              error: null,
              lastUpdate: null,
            },
          };

          setInputParams(params);

          try {
            const { tox, meteo, resultForZones, substanceName } = runComputation(
              params,
              savedCoord,
            );
            hasRunOnce.current = true;

            setDispersionResult({
              hnsAnlysSn: sn,
              zones: [
                {
                  level: 'AEGL-3',
                  color: 'rgba(59,130,246,0.4)',
                  radius: resultForZones.aeglDistances.aegl3,
                  angle: meteo.windDirDeg,
                },
                {
                  level: 'AEGL-2',
                  color: 'rgba(6,182,212,0.3)',
                  radius: resultForZones.aeglDistances.aegl2,
                  angle: meteo.windDirDeg,
                },
                {
                  level: 'AEGL-1',
                  color: 'rgba(6,182,212,0.25)',
                  radius: resultForZones.aeglDistances.aegl1,
                  angle: meteo.windDirDeg,
                },
              ].filter((z: { radius: number }) => z.radius > 0),
              timestamp: new Date().toISOString(),
              windDirection: meteo.windDirDeg,
              substance: substanceName,
              concentration: {
                'AEGL-3': `${tox.aegl3} ppm`,
                'AEGL-2': `${tox.aegl2} ppm`,
                'AEGL-1': `${tox.aegl1} ppm`,
              },
              maxConcentration: resultForZones.maxConcentration,
            });
          } catch {
            // 재계산 실패 시 무시
          }
        }
      }, 200);
    } catch {
      alert('분석 불러오기에 실패했습니다.');
    }
  };

  /** 보고서 생성 — 실 데이터 수집 + 지도 캡처 후 탭 이동 */
  const handleOpenReport = () => {
    try {
      let mapImage: string | null = null;
      try {
        mapImage = mapCaptureRef.current?.() ?? null;
      } catch {
        /* canvas capture 실패 무시 */
      }

      const tox = getSubstanceToxicity(inputParams?.substance || '');
      const modelType = computedResult?.modelType;
      const modelLabel =
        modelType === 'plume'
          ? 'Gaussian Plume'
          : modelType === 'puff'
            ? 'Gaussian Puff'
            : modelType === 'dense_gas'
              ? 'Dense Gas (B-M)'
              : 'ALOHA';

      setHnsReportPayload({
        mapImageDataUrl: mapImage,
        substance: {
          name: inputParams?.substance || '—',
          toxicity: `AEGL-2: ${tox.aegl2} ppm / AEGL-3: ${tox.aegl3} ppm`,
        },
        hazard: {
          aegl3: `${((computedResult?.aeglDistances.aegl3 ?? 0) / 1000).toFixed(1)} km`,
          aegl2: `${((computedResult?.aeglDistances.aegl2 ?? 0) / 1000).toFixed(1)} km`,
          aegl1: `${((computedResult?.aeglDistances.aegl1 ?? 0) / 1000).toFixed(1)} km`,
        },
        atm: {
          model: modelLabel,
          maxDistance: `${((computedResult?.aeglDistances.aegl1 ?? 0) / 1000).toFixed(1)} km`,
        },
        weather: {
          windDir: `${windDirToCompass(inputParams?.weather?.windDirection ?? 0)} ${inputParams?.weather?.windDirection ?? 0}°`,
          windSpeed: `${inputParams?.weather?.windSpeed ?? 0} m/s`,
          stability: `${inputParams?.weather?.stability ?? 'D'}`,
          temperature: `${inputParams?.weather?.temperature ?? 0}°C`,
        },
        maxConcentration: `${(computedResult?.maxConcentration ?? 0).toFixed(1)} ppm`,
        aeglAreas: {
          aegl1: `${(computedResult?.aeglAreas.aegl1 ?? 0).toFixed(2)} km²`,
          aegl2: `${(computedResult?.aeglAreas.aegl2 ?? 0).toFixed(2)} km²`,
          aegl3: `${(computedResult?.aeglAreas.aegl3 ?? 0).toFixed(2)} km²`,
        },
      });
    } catch (err) {
      console.error('[HNS] 보고서 데이터 수집 오류:', err);
    }
    setReportGenCategory(1);
    navigateToTab('reports', 'generate');
  };

  if (activeSubTab === 'scenario') {
    return <HNSScenarioView />;
  }

  if (activeSubTab === 'manual') {
    return <HNSManualViewer />;
  }

  if (activeSubTab === 'theory') {
    return <HNSTheoryView />;
  }

  if (activeSubTab === 'substance') {
    return <HNSSubstanceView />;
  }

  return (
    <div className="flex flex-1 overflow-hidden">
      {/* Left Panel - 분석 목록일 때는 숨김 */}
      {activeSubTab === 'analysis' && (
        <div className="shrink-0 overflow-hidden" style={{ width: leftCollapsed ? 0 : 320 }}>
          <HNSLeftPanel
            activeSubTab={activeSubTab}
            onSubTabChange={setActiveSubTab}
            incidentCoord={incidentCoord}
            onCoordChange={setIncidentCoord}
            onMapSelectClick={() => setIsSelectingLocation(true)}
            onRunPrediction={handleRunPrediction}
            isRunningPrediction={isRunningPrediction}
            onParamsChange={handleParamsChange}
            onReset={handleReset}
            loadedParams={loadedParams}
            onFlyToCoord={(c) => setFlyToCoord({ lat: c.lat, lon: c.lon })}
          />
        </div>
      )}

      {/* Center - Map/Content Area */}
      <div className="flex-1 relative overflow-hidden">
        {/* Left panel toggle button */}
        {activeSubTab === 'analysis' && (
          <button
            onClick={() => setLeftCollapsed((v) => !v)}
            className="absolute z-[500] top-1/2 -translate-y-1/2 flex items-center justify-center text-[10px]"
            style={{
              left: 0,
              width: 18,
              height: 40,
              background: 'var(--bg-elevated)',
              border: '1px solid var(--stroke-default)',
              borderLeft: 'none',
              borderRadius: '0 6px 6px 0',
              color: 'var(--fg-sub)',
              cursor: 'pointer',
            }}
          >
            {leftCollapsed ? '▶' : '◀'}
          </button>
        )}

        {/* Right panel toggle button */}
        {activeSubTab === 'analysis' && (
          <button
            onClick={() => setRightCollapsed((v) => !v)}
            className="absolute z-[500] top-1/2 -translate-y-1/2 flex items-center justify-center text-[10px]"
            style={{
              right: 0,
              width: 18,
              height: 40,
              background: 'var(--bg-elevated)',
              border: '1px solid var(--stroke-default)',
              borderRight: 'none',
              borderRadius: '6px 0 0 6px',
              color: 'var(--fg-sub)',
              cursor: 'pointer',
            }}
          >
            {rightCollapsed ? '◀' : '▶'}
          </button>
        )}

        {activeSubTab === 'list' ? (
          <HNSAnalysisListTable
            onTabChange={(v) =>
              setActiveSubTab(typeof v === 'function' ? v(activeSubTab as 'analysis' | 'list') : v)
            }
            onSelectAnalysis={handleLoadAnalysis}
          />
        ) : (
          <>
            <MapView
              incidentCoord={incidentCoord ?? undefined}
              flyToIncident={flyToCoord}
              onIncidentFlyEnd={() => setFlyToCoord(undefined)}
              isSelectingLocation={isSelectingLocation}
              onMapClick={handleMapClick}
              oilTrajectory={[]}
              enabledLayers={new Set()}
              dispersionResult={dispersionResult}
              dispersionHeatmap={heatmapData}
              mapCaptureRef={mapCaptureRef}
            />
            {/* 시간 슬라이더 (puff/dense_gas 모델용) */}
            {allTimeFrames.length > 1 && (
              <DispersionTimeSlider
                currentFrame={currentFrame}
                totalFrames={allTimeFrames.length}
                isPlaying={isPuffPlaying}
                onFrameChange={handleFrameChange}
                onPlayPause={() => setIsPuffPlaying(!isPuffPlaying)}
                dt={30}
              />
            )}
          </>
        )}
      </div>

      {/* Right Panel - 분석 목록일 때는 숨김 */}
      {activeSubTab === 'analysis' && (
        <div className="shrink-0 overflow-hidden" style={{ width: rightCollapsed ? 0 : 300 }}>
          <HNSRightPanel
            dispersionResult={dispersionResult}
            computedResult={computedResult}
            weatherData={inputParams?.weather ?? null}
            onOpenRecalc={() => setRecalcModalOpen(true)}
            onOpenReport={handleOpenReport}
            onSave={handleSave}
          />
        </div>
      )}

      {/* HNS 재계산 모달 */}
      <HNSRecalcModal
        isOpen={recalcModalOpen}
        onClose={() => setRecalcModalOpen(false)}
        currentParams={
          inputParams
            ? {
                substance: inputParams.substance,
                releaseType: inputParams.releaseType,
                emissionRate: inputParams.emissionRate,
                totalRelease: inputParams.totalRelease,
                algorithm: inputParams.algorithm,
                predictionTime: inputParams.predictionTime,
              }
            : null
        }
        onSubmit={(recalcP: RecalcParams) => {
          // 재계산 파라미터를 현재 inputParams에 병합하여 즉시 실행
          const merged: HNSInputParams = {
            ...(inputParams as HNSInputParams),
            substance: recalcP.substance,
            releaseType: recalcP.releaseType,
            emissionRate: recalcP.emissionRate,
            totalRelease: recalcP.totalRelease,
            algorithm: recalcP.algorithm,
            predictionTime: recalcP.predictionTime,
          };
          // 좌측 패널 UI도 동기화
          setLoadedParams(merged);
          setInputParams(merged);
          // 병합된 파라미터로 즉시 예측 실행
          handleRunPrediction(merged);
        }}
      />
    </div>
  );
}
