import { useState, useRef, useEffect } from 'react';
import { ComboBox } from '@common/components/ui/ComboBox';
import type { PredictionModel } from './OilSpillView';
import { analyzeImage } from '../services/predictionApi';
import type { ImageAnalyzeResult } from '../services/predictionApi';

interface PredictionInputSectionProps {
  expanded: boolean;
  onToggle: () => void;
  accidentTime: string;
  onAccidentTimeChange: (time: string) => void;
  incidentCoord: { lon: number; lat: number } | null;
  onCoordChange: (coord: { lon: number; lat: number }) => void;
  isSelectingLocation: boolean;
  onMapSelectClick: () => void;
  onRunSimulation: () => void;
  isRunningSimulation: boolean;
  selectedModels: Set<PredictionModel>;
  onModelsChange: (models: Set<PredictionModel>) => void;
  visibleModels?: Set<PredictionModel>;
  onVisibleModelsChange?: (models: Set<PredictionModel>) => void;
  hasResults?: boolean;
  predictionTime: number;
  onPredictionTimeChange: (time: number) => void;
  spillType: string;
  onSpillTypeChange: (type: string) => void;
  oilType: string;
  onOilTypeChange: (type: string) => void;
  spillAmount: number;
  onSpillAmountChange: (amount: number) => void;
  incidentName: string;
  onIncidentNameChange: (name: string) => void;
  spillUnit: string;
  onSpillUnitChange: (unit: string) => void;
  onImageAnalysisResult?: (result: ImageAnalyzeResult) => void;
  validationErrors?: Set<string>;
}

const PredictionInputSection = ({
  expanded,
  onToggle,
  accidentTime,
  onAccidentTimeChange,
  incidentCoord,
  onCoordChange,
  isSelectingLocation,
  onMapSelectClick,
  onRunSimulation,
  isRunningSimulation,
  selectedModels,
  onModelsChange,
  onVisibleModelsChange,
  hasResults,
  predictionTime,
  onPredictionTimeChange,
  spillType,
  onSpillTypeChange,
  oilType,
  onOilTypeChange,
  spillAmount,
  onSpillAmountChange,
  incidentName,
  onIncidentNameChange,
  spillUnit,
  onSpillUnitChange,
  onImageAnalysisResult,
  validationErrors,
}: PredictionInputSectionProps) => {
  const [inputMode, setInputMode] = useState<'direct' | 'upload'>('direct');
  const [uploadedFile, setUploadedFile] = useState<File | null>(null);
  const [isAnalyzing, setIsAnalyzing] = useState(false);
  const [analyzeError, setAnalyzeError] = useState<string | null>(null);
  const [analyzeResult, setAnalyzeResult] = useState<ImageAnalyzeResult | null>(null);
  const fileInputRef = useRef<HTMLInputElement>(null);

  const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0] ?? null;
    setUploadedFile(file);
    setAnalyzeError(null);
    setAnalyzeResult(null);
  };

  const handleRemoveFile = () => {
    setUploadedFile(null);
    setAnalyzeError(null);
    setAnalyzeResult(null);
    if (fileInputRef.current) fileInputRef.current.value = '';
  };

  const handleAnalyze = async () => {
    if (!uploadedFile) return;
    setIsAnalyzing(true);
    setAnalyzeError(null);
    try {
      const result = await analyzeImage(uploadedFile);
      setAnalyzeResult(result);
      onImageAnalysisResult?.(result);
    } catch (err: unknown) {
      if (err && typeof err === 'object' && 'response' in err) {
        const res = (err as { response?: { data?: { error?: string } } }).response;
        if (res?.data?.error === 'GPS_NOT_FOUND') {
          setAnalyzeError('GPS 정보가 없는 이미지입니다');
          return;
        }
        if (res?.data?.error === 'TIMEOUT') {
          setAnalyzeError('분석 서버 응답 없음 (시간 초과)');
          return;
        }
      }
      setAnalyzeError('이미지 분석 중 오류가 발생했습니다');
    } finally {
      setIsAnalyzing(false);
    }
  };

  return (
    <div className="border-b border-stroke">
      <div
        onClick={onToggle}
        className="flex items-center justify-between p-4 cursor-pointer hover:bg-[rgba(255,255,255,0.02)]"
      >
        <h3 className="text-title-4 font-bold text-fg-sub font-korean">예측정보 입력</h3>
        <span className="text-label-2 text-fg-disabled">{expanded ? '▼' : '▶'}</span>
      </div>

      {expanded && (
        <div className="px-4 pb-4 flex flex-col gap-[6px]">
          {/* Input Mode Selection */}
          <div className="flex items-center gap-[10px] text-label-2">
            <label className="flex items-center gap-[3px] cursor-pointer">
              <input
                type="radio"
                name="prdType"
                checked={inputMode === 'direct'}
                onChange={() => setInputMode('direct')}
                className="accent-[var(--color-accent)] m-0 w-[11px] h-[11px]"
              />
              직접 입력
            </label>
            <label className="flex items-center gap-[3px] cursor-pointer">
              <input
                type="radio"
                name="prdType"
                checked={inputMode === 'upload'}
                onChange={() => setInputMode('upload')}
                className="accent-[var(--color-accent)] m-0 w-[11px] h-[11px]"
              />
              이미지 업로드
            </label>
          </div>

          {/* Direct Input Mode */}
          {inputMode === 'direct' && (
            <>
              <input
                className="prd-i"
                placeholder="사고명 직접 입력"
                value={incidentName}
                onChange={(e) => onIncidentNameChange(e.target.value)}
                style={
                  validationErrors?.has('incidentName')
                    ? { borderColor: 'var(--color-danger)' }
                    : undefined
                }
              />
              <input className="prd-i" placeholder="또는 사고 리스트에서 선택" readOnly />
            </>
          )}

          {/* Image Upload Mode */}
          {inputMode === 'upload' && (
            <>
              {/* 파일 선택 영역 */}
              {!uploadedFile ? (
                <label
                  className="flex items-center justify-center text-label-2 text-fg-disabled cursor-pointer"
                  style={{
                    padding: '20px',
                    background: 'var(--bg-base)',
                    border: '2px dashed var(--stroke-default)',
                    borderRadius: 'var(--radius-sm)',
                    transition: '0.15s',
                  }}
                  onMouseEnter={(e) => {
                    e.currentTarget.style.borderColor = 'var(--color-accent)';
                    e.currentTarget.style.background = 'rgba(6,182,212,0.05)';
                  }}
                  onMouseLeave={(e) => {
                    e.currentTarget.style.borderColor = 'var(--stroke-default)';
                    e.currentTarget.style.background = 'var(--bg-base)';
                  }}
                >
                  이미지 파일을 선택하세요
                  <input
                    ref={fileInputRef}
                    type="file"
                    accept="image/*"
                    onChange={handleFileSelect}
                    className="hidden"
                  />
                </label>
              ) : (
                <div
                  className="flex items-center justify-between font-mono text-label-2 bg-bg-base border border-stroke"
                  style={{ padding: '8px 10px', borderRadius: 'var(--radius-sm)' }}
                >
                  <span className="text-fg-sub">📄 {uploadedFile.name}</span>
                  <button
                    onClick={handleRemoveFile}
                    className="text-label-2 text-fg-disabled bg-transparent border-none cursor-pointer"
                    style={{ padding: '2px 6px', transition: '0.15s' }}
                    onMouseEnter={(e) => {
                      e.currentTarget.style.color = 'var(--color-danger)';
                    }}
                    onMouseLeave={(e) => {
                      e.currentTarget.style.color = 'var(--fg-disabled)';
                    }}
                  >
                    ✕
                  </button>
                </div>
              )}

              {/* 분석 실행 버튼 */}
              <button
                className="prd-btn pri"
                style={{ padding: '7px', fontSize: 'var(--font-size-label-2)' }}
                onClick={handleAnalyze}
                disabled={!uploadedFile || isAnalyzing}
              >
                {isAnalyzing ? '⏳ 분석 중...' : '이미지 분석 실행'}
              </button>

              {/* 에러 메시지 */}
              {analyzeError && (
                <div
                  className="text-label-2 font-medium"
                  style={{
                    padding: '6px 8px',
                    background: 'rgba(239,68,68,0.1)',
                    border: '1px solid rgba(239,68,68,0.3)',
                    borderRadius: 'var(--radius-sm)',
                    color: 'var(--color-danger)',
                  }}
                >
                  ⚠ {analyzeError}
                </div>
              )}

              {/* 분석 완료 메시지 */}
              {analyzeResult && (
                <div
                  className="text-label-2 font-medium"
                  style={{
                    padding: '6px 8px',
                    background: 'rgba(34,197,94,0.1)',
                    border: '1px solid rgba(34,197,94,0.3)',
                    borderRadius: 'var(--radius-sm)',
                    color: '#22c55e',
                    lineHeight: 1.6,
                  }}
                >
                  ✓ 분석 완료
                  <br />
                  <span className="font-normal text-fg-disabled">
                    위도 {analyzeResult.lat.toFixed(4)} / 경도 {analyzeResult.lon.toFixed(4)}
                    <br />
                    유종: {analyzeResult.oilType} / 면적: {analyzeResult.area.toFixed(1)} m²
                  </span>
                </div>
              )}
            </>
          )}

          {/* 사고 발생 시각 */}
          <div className="flex flex-col gap-0.5">
            <label className="text-label-2 text-fg-disabled font-korean">
              사고 발생 시각 (KST)
            </label>
            <DateTimeInput
              value={accidentTime}
              onChange={onAccidentTimeChange}
              error={validationErrors?.has('accidentTime')}
            />
          </div>

          {/* Coordinates (DMS) + Map Button */}
          <div className="flex flex-col gap-1">
            <div className="grid grid-cols-[1fr_auto] gap-x-1 gap-y-1">
              <DmsCoordInput
                label="위도"
                isLatitude={true}
                decimal={incidentCoord?.lat ?? 0}
                onChange={(val) => onCoordChange({ lon: incidentCoord?.lon ?? 0, lat: val })}
                error={validationErrors?.has('coord')}
              />
              <button
                className={`prd-map-btn${isSelectingLocation ? ' active' : ''}`}
                onClick={onMapSelectClick}
                style={{
                  gridRow: '1 / 3',
                  gridColumn: 2,
                  whiteSpace: 'nowrap',
                  alignSelf: 'stretch',
                  minWidth: 48,
                  padding: '0 10px',
                }}
              >
                위경도
                <br />
                선택
              </button>
              <DmsCoordInput
                label="경도"
                isLatitude={false}
                decimal={incidentCoord?.lon ?? 0}
                onChange={(val) => onCoordChange({ lat: incidentCoord?.lat ?? 0, lon: val })}
                error={validationErrors?.has('coord')}
              />
            </div>
          </div>

          {/* Oil Type + Oil Kind */}
          <div className="grid grid-cols-2 gap-1">
            <ComboBox
              className="prd-i"
              value={spillType}
              onChange={onSpillTypeChange}
              options={[
                { value: '연속', label: '연속' },
                { value: '비연속', label: '비연속' },
                { value: '순간 유출', label: '순간 유출' },
              ]}
            />
            <ComboBox
              className="prd-i"
              value={oilType}
              onChange={onOilTypeChange}
              options={[
                { value: '벙커C유', label: '벙커C유' },
                { value: '경유', label: '경유' },
                { value: '원유', label: '원유' },
                { value: '중유', label: '중유' },
                { value: '등유', label: '등유' },
                { value: '휘발유', label: '휘발유' },
              ]}
            />
          </div>

          {/* Volume + Unit + Duration */}
          <div className="grid items-center gap-1" style={{ gridTemplateColumns: '1fr 65px 1fr' }}>
            <input
              className="prd-i"
              placeholder="유출량"
              type="number"
              min="1"
              step="1"
              value={spillAmount}
              onChange={(e) => onSpillAmountChange(parseInt(e.target.value) || 0)}
            />
            <ComboBox
              className="prd-i"
              value={spillUnit}
              onChange={onSpillUnitChange}
              options={[
                { value: 'kL', label: 'kL' },
                { value: 'ton', label: 'Ton' },
                { value: 'barrel', label: '배럴' },
              ]}
            />
            <ComboBox
              className="prd-i"
              value={predictionTime}
              onChange={(v) => onPredictionTimeChange(parseInt(v))}
              options={[
                { value: '6', label: '6시간' },
                { value: '12', label: '12시간' },
                { value: '24', label: '24시간' },
                { value: '48', label: '48시간' },
                { value: '72', label: '72시간' },
              ]}
            />
          </div>

          {/* Divider */}
          <div className="h-px bg-border my-0.5" />

          {/* Model Selection (다중 선택) */}
          {/* POSEIDON: 엔진 연동 완료. KOSPS: 준비 중 (ready: false) */}
          <div className="grid grid-cols-3 gap-[3px]">
            {(
              [
                { id: 'KOSPS' as PredictionModel, color: 'var(--color-accent)', ready: false },
                { id: 'POSEIDON' as PredictionModel, color: 'var(--color-danger)', ready: true },
                { id: 'OpenDrift' as PredictionModel, color: 'var(--color-info)', ready: true },
              ] as const
            ).map((m) => (
              <div
                key={m.id}
                className={`prd-mc ${selectedModels.has(m.id) ? 'on' : ''} cursor-pointer text-center`}
                onClick={() => {
                  if (!m.ready) {
                    alert(`${m.id} 모델은 현재 준비중입니다.`);
                    return;
                  }
                  const next = new Set(selectedModels);
                  if (next.has(m.id)) {
                    next.delete(m.id);
                  } else {
                    next.add(m.id);
                  }
                  onModelsChange(next);
                  if (hasResults && onVisibleModelsChange) {
                    onVisibleModelsChange(new Set(next));
                  }
                }}
              >
                {/* <span className="prd-md" style={{ background: m.color }} /> */}
                {m.id}
              </div>
            ))}
            {/* 임시 비활성화 — OpenDrift만 구동 가능 (앙상블은 모든 모델 연동 후 활성화 예정)
            <div
              className={`prd-mc ${selectedModels.size === ALL_MODELS.length ? 'on' : ''} cursor-pointer`}
              onClick={() => {
                alert('앙상블 모델은 현재 준비중입니다.')
              }}
            >
              <span className="prd-md" style={{ background: 'var(--color-tertiary)' }} />
              앙상블
            </div>
            */}
          </div>

          {/* 모델 미선택 경고 (실행 시 검증) */}
          {validationErrors?.has('models') && (
            <p className="text-label-2 text-color-danger font-korean">
              ⚠ 예측 모델을 하나 이상 선택하세요.
            </p>
          )}

          {/* Run Button */}
          <button
            className="prd-btn pri mt-0.5"
            style={{ padding: '7px', fontSize: 'var(--font-size-label-2)' }}
            onClick={onRunSimulation}
            disabled={isRunningSimulation}
          >
            {isRunningSimulation ? '⏳ 실행 중...' : '확산예측 실행'}
          </button>
        </div>
      )}
    </div>
  );
};

// ── 커스텀 날짜/시간 선택 컴포넌트 ─────────────────────
function DateTimeInput({
  value,
  onChange,
  error,
}: {
  value: string;
  onChange: (v: string) => void;
  error?: boolean;
}) {
  const [showCal, setShowCal] = useState(false);
  const ref = useRef<HTMLDivElement>(null);

  const datePart = value ? value.split('T')[0] : '';
  const timePart = value && value.includes('T') ? value.split('T')[1] : '00:00';
  const timeParts = timePart.split(':').map(Number);
  const hh = isNaN(timeParts[0]) ? 0 : timeParts[0];
  const mm = timeParts[1] === undefined || isNaN(timeParts[1]) ? 0 : timeParts[1];

  const parsed = datePart ? new Date(datePart + 'T00:00:00') : new Date();
  const [viewYear, setViewYear] = useState(parsed.getFullYear());
  const [viewMonth, setViewMonth] = useState(parsed.getMonth());

  const selY = datePart ? parsed.getFullYear() : -1;
  const selM = datePart ? parsed.getMonth() : -1;
  const selD = datePart ? parsed.getDate() : -1;

  useEffect(() => {
    const handler = (e: MouseEvent) => {
      if (ref.current && !ref.current.contains(e.target as Node)) setShowCal(false);
    };
    document.addEventListener('mousedown', handler);
    return () => document.removeEventListener('mousedown', handler);
  }, []);

  const daysInMonth = new Date(viewYear, viewMonth + 1, 0).getDate();
  const firstDay = new Date(viewYear, viewMonth, 1).getDay();
  const days: (number | null)[] = [];
  for (let i = 0; i < firstDay; i++) days.push(null);
  for (let i = 1; i <= daysInMonth; i++) days.push(i);

  const pickDate = (day: number) => {
    const m = String(viewMonth + 1).padStart(2, '0');
    const d = String(day).padStart(2, '0');
    onChange(`${viewYear}-${m}-${d}T${timePart}`);
    setShowCal(false);
  };

  const updateTime = (newHH: number, newMM: number) => {
    const date = datePart || new Date().toISOString().split('T')[0];
    onChange(`${date}T${String(newHH).padStart(2, '0')}:${String(newMM).padStart(2, '0')}`);
  };

  const prevMonth = () => {
    if (viewMonth === 0) {
      setViewYear(viewYear - 1);
      setViewMonth(11);
    } else setViewMonth(viewMonth - 1);
  };
  const nextMonth = () => {
    if (viewMonth === 11) {
      setViewYear(viewYear + 1);
      setViewMonth(0);
    } else setViewMonth(viewMonth + 1);
  };

  const displayDate = datePart
    ? `${selY}.${String(selM + 1).padStart(2, '0')}.${String(selD).padStart(2, '0')}`
    : '날짜 선택';

  const today = new Date();
  const todayY = today.getFullYear();
  const todayM = today.getMonth();
  const todayD = today.getDate();

  return (
    <div
      ref={ref}
      className="flex items-center gap-1 relative"
      style={
        error ? { border: '1px solid var(--color-danger)', borderRadius: 6, padding: 2 } : undefined
      }
    >
      {/* 날짜 버튼 */}
      <button
        type="button"
        onClick={() => setShowCal(!showCal)}
        className="prd-i flex-1 flex items-center justify-between cursor-pointer"
        style={{ padding: '5px 8px', fontSize: 'var(--font-size-label-2)' }}
      >
        <span
          className="font-mono"
          style={{ color: datePart ? 'var(--fg-default)' : 'var(--fg-disabled)' }}
        >
          {displayDate}
        </span>
        <span className="text-label-2 opacity-60">📅</span>
      </button>

      {/* 시 */}
      <TimeDropdown value={hh} max={24} onChange={(v) => updateTime(v, mm)} />
      <span className="text-label-2 text-fg-disabled font-bold">:</span>
      {/* 분 */}
      <TimeDropdown value={mm} max={60} onChange={(v) => updateTime(hh, v)} />

      {/* 캘린더 팝업 */}
      {showCal && (
        <div
          className="absolute z-[9999] rounded-md overflow-hidden"
          style={{
            top: '100%',
            left: 0,
            marginTop: 4,
            width: 200,
            background: 'var(--bg-card)',
            border: '1px solid var(--stroke-default)',
            boxShadow: '0 8px 24px rgba(0,0,0,0.5)',
          }}
        >
          {/* 헤더 */}
          <div
            className="flex items-center justify-between"
            style={{ padding: '6px 8px', borderBottom: '1px solid var(--stroke-default)' }}
          >
            <button
              type="button"
              onClick={prevMonth}
              className="text-label-2 text-fg-disabled cursor-pointer px-1 hover:text-fg"
            >
              ◀
            </button>
            <span className="text-label-2 font-bold text-fg font-korean">
              {viewYear}년 {viewMonth + 1}월
            </span>
            <button
              type="button"
              onClick={nextMonth}
              className="text-label-2 text-fg-disabled cursor-pointer px-1 hover:text-fg"
            >
              ▶
            </button>
          </div>
          {/* 요일 */}
          <div className="grid grid-cols-7 text-center" style={{ padding: '3px 4px 0' }}>
            {['일', '월', '화', '수', '목', '금', '토'].map((d) => (
              <span
                key={d}
                className="text-label-2 text-fg-disabled font-korean"
                style={{ padding: '2px 0' }}
              >
                {d}
              </span>
            ))}
          </div>
          {/* 날짜 */}
          <div className="grid grid-cols-7 text-center" style={{ padding: '2px 4px 6px' }}>
            {days.map((day, i) => {
              if (day === null) return <span key={`e-${i}`} />;
              const isSelected = viewYear === selY && viewMonth === selM && day === selD;
              const isToday = viewYear === todayY && viewMonth === todayM && day === todayD;
              return (
                <button
                  key={day}
                  type="button"
                  onClick={() => pickDate(day)}
                  className="cursor-pointer rounded-sm"
                  style={{
                    padding: '3px 0',
                    fontSize: 'var(--font-size-label-2)',
                    fontFamily: 'var(--font-mono)',
                    fontWeight: isSelected ? 700 : 400,
                    color: isSelected ? '#fff' : isToday ? 'var(--color-accent)' : 'var(--fg-sub)',
                    background: isSelected ? 'var(--color-accent)' : 'transparent',
                    border: 'none',
                  }}
                >
                  {day}
                </button>
              );
            })}
          </div>
          {/* 오늘 버튼 */}
          <div style={{ padding: '0 8px 6px' }}>
            <button
              type="button"
              onClick={() => {
                const now = new Date();
                setViewYear(now.getFullYear());
                setViewMonth(now.getMonth());
                const m = String(now.getMonth() + 1).padStart(2, '0');
                const d = String(now.getDate()).padStart(2, '0');
                const hh = String(now.getHours()).padStart(2, '0');
                const mm = String(now.getMinutes()).padStart(2, '0');
                onChange(`${now.getFullYear()}-${m}-${d}T${hh}:${mm}`);
                setShowCal(false);
              }}
              className="w-full text-label-2 font-korean font-medium cursor-pointer rounded-sm"
              style={{
                padding: '3px 0',
                background: 'rgba(6,182,212,0.08)',
                border: '1px solid rgba(6,182,212,0.2)',
                color: 'var(--color-accent)',
              }}
            >
              오늘
            </button>
          </div>
        </div>
      )}
    </div>
  );
}

// ── 커스텀 시간 드롭다운 (다크 테마) ───────────────────
function TimeDropdown({
  value,
  max,
  onChange,
}: {
  value: number;
  max: number;
  onChange: (v: number) => void;
}) {
  const [open, setOpen] = useState(false);
  const dropRef = useRef<HTMLDivElement>(null);
  const listRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const handler = (e: MouseEvent) => {
      if (dropRef.current && !dropRef.current.contains(e.target as Node)) setOpen(false);
    };
    document.addEventListener('mousedown', handler);
    return () => document.removeEventListener('mousedown', handler);
  }, []);

  useEffect(() => {
    if (open && listRef.current) {
      const activeEl = listRef.current.querySelector('[data-active="true"]');
      if (activeEl) activeEl.scrollIntoView({ block: 'center' });
    }
  }, [open]);

  return (
    <div ref={dropRef} className="relative">
      <button
        type="button"
        onClick={() => setOpen(!open)}
        className="prd-i text-center font-mono cursor-pointer"
        style={{ width: 38, padding: '5px 2px', fontSize: 'var(--font-size-caption)' }}
      >
        {String(value).padStart(2, '0')}
      </button>
      {open && (
        <div
          ref={listRef}
          className="absolute z-[9999] overflow-y-auto rounded-md"
          style={{
            top: '100%',
            left: '50%',
            transform: 'translateX(-50%)',
            marginTop: 2,
            width: 42,
            maxHeight: 160,
            background: 'var(--bg-card)',
            border: '1px solid var(--stroke-default)',
            boxShadow: '0 8px 24px rgba(0,0,0,0.5)',
            scrollbarWidth: 'thin',
            scrollbarColor: 'var(--stroke-default) transparent',
          }}
        >
          {Array.from({ length: max }, (_, i) => (
            <button
              key={i}
              type="button"
              data-active={i === value}
              onClick={() => {
                onChange(i);
                setOpen(false);
              }}
              className="w-full text-center font-mono cursor-pointer"
              style={{
                padding: '4px 0',
                fontSize: 'var(--font-size-caption)',
                color: i === value ? 'var(--color-accent)' : 'var(--fg-sub)',
                background: i === value ? 'rgba(6,182,212,0.15)' : 'transparent',
                fontWeight: i === value ? 700 : 400,
                border: 'none',
              }}
            >
              {String(i).padStart(2, '0')}
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

// ── 도분초 좌표 입력 컴포넌트 ──────────────────────────
function DmsCoordInput({
  label,
  isLatitude,
  decimal,
  onChange,
  error,
}: {
  label: string;
  isLatitude: boolean;
  decimal: number;
  onChange: (val: number) => void;
  error?: boolean;
}) {
  const abs = Math.abs(decimal);
  const d = Math.floor(abs);
  const mDec = (abs - d) * 60;
  const m = Math.floor(mDec);
  const s = parseFloat(((mDec - m) * 60).toFixed(2));
  const dir = isLatitude ? (decimal >= 0 ? 'N' : 'S') : decimal >= 0 ? 'E' : 'W';

  const update = (deg: number, min: number, sec: number, direction: string) => {
    let val = deg + min / 60 + sec / 3600;
    if (direction === 'S' || direction === 'W') val = -val;
    onChange(val);
  };

  const fieldStyle = { padding: '5px 2px', fontSize: 'var(--font-size-label-2)', minWidth: 0 };

  return (
    <div className="flex flex-col gap-0.5">
      <span className="text-caption text-fg-disabled font-korean">{label}</span>
      <div
        className="flex items-center gap-0.5"
        style={
          error
            ? { border: '1px solid var(--color-danger)', borderRadius: 6, padding: 2 }
            : undefined
        }
      >
        <select
          className="prd-i text-center"
          value={dir}
          onChange={(e) => update(d, m, s, e.target.value)}
          style={{
            width: 32,
            padding: '5px 1px',
            fontSize: 'var(--font-size-label-2)',
            appearance: 'none',
            WebkitAppearance: 'none',
            backgroundImage: 'none',
          }}
        >
          {isLatitude ? (
            <>
              <option value="N">N</option>
              <option value="S">S</option>
            </>
          ) : (
            <>
              <option value="E">E</option>
              <option value="W">W</option>
            </>
          )}
        </select>
        <input
          className="prd-i text-center flex-1"
          type="number"
          min={0}
          max={isLatitude ? 90 : 180}
          value={d}
          onChange={(e) => update(parseInt(e.target.value) || 0, m, s, dir)}
          style={fieldStyle}
        />
        <span className="text-caption text-fg-disabled">°</span>
        <input
          className="prd-i text-center flex-1"
          type="number"
          min={0}
          max={59}
          value={m}
          onChange={(e) => update(d, parseInt(e.target.value) || 0, s, dir)}
          style={fieldStyle}
        />
        <span className="text-caption text-fg-disabled">'</span>
        <input
          className="prd-i text-center flex-1"
          type="number"
          min={0}
          max={59.99}
          step={0.01}
          value={s}
          onChange={(e) => update(d, m, parseFloat(e.target.value) || 0, dir)}
          style={fieldStyle}
        />
        <span className="text-caption text-fg-disabled">"</span>
      </div>
    </div>
  );
}

export default PredictionInputSection;
