import { Fragment, useState } from 'react';
import type { MouseEvent } from 'react';
import { RouteSegment } from '../RouteSegment/RouteSegment';
import type { StructureType, StructureSegment } from '../../types/timeline';
import {
  TRACK_RENDER_END_PX,
  TRACK_RENDER_START_PX,
  TRACK_START_PX,
  TRACK_WIDTH_PX,
  TRACK_WRAPPER_TRANSFORM,
} from '../../mocks/route';
import type { KmLabel, StructMark } from '../../StationBar';
import { cssVars, px } from '../../utils/cssVars';
import { MileageMarker } from '../MileageMarker/MileageMarker';
import styles from './Timeline.module.scss';

/** 미터값 → "158k160" (10m 단위). 음수면 빈 문자열. */
function fmtKm(m: number): string {
  if (m < 0) return '';
  const r = Math.round(m / 10) * 10;
  return `${Math.floor(r / 1000)}k${String(r % 1000).padStart(3, '0')}`;
}

/**
 * 구조물명 라벨 분할 — 한 줄에 안 들어가면 위/아래 '글자 수'가 최대한 같게 2줄로 나눈다.
 * (균등 분할 → 가장 넓은 줄 폭 최소화 → 옆 라벨과 간격 최대 확보)
 * 한글/CJK 1.0, 그 외(영문·괄호·숫자) 0.55폭으로 한 줄 용량(≈140px/16.5px) 추정.
 * 분할점은 글자수 절반. 단, 절반 근처(±2)에 여는 괄호가 있으면 괄호를 통째로 아랫줄로.
 */
function splitStructLabel(text: string): string[] {
  const chars = [...text];
  const isCJK = (c: string): boolean => /[　-鿿가-힣]/.test(c);
  const width = chars.reduce((a, c) => a + (isCJK(c) ? 1 : 0.55), 0);
  if (width <= 8.2) return [text]; // 한 줄에 들어감 → 그대로
  const len = chars.length;
  // 기본 분할점 = 글자수 절반(위/아래 글자수 균등).
  let cut = Math.round(len / 2);
  // 분할점이 괄호 그룹 "(...)" 내부를 가르면 → 괄호 시작 앞으로 옮겨 통째로 아랫줄.
  let open = -1;
  for (let i = 0; i < len; i++) {
    const c = chars[i];
    if (c === '(' || c === '（') open = i;
    else if (c === ')' || c === '）') {
      if (open >= 0 && open < cut && i >= cut) { cut = open; break; }
      open = -1;
    }
  }
  return [chars.slice(0, cut).join(''), chars.slice(cut).join('')];
}

interface TimelineProps {
  posPx: number;
  onSeekDown: (e: MouseEvent<HTMLDivElement>) => void;
  /** 통과 구간 트랙 gradient (정방향=주황/역방향=청록). */
  trackGradient: string;
  /** 미통과 구간 트랙 gradient (정방향=회색/역방향=청회색). */
  trackGradientIdle: string;
  /** 색(방향)이 바뀌는 전환점 px(스테이지 좌표) — 구분선 위치. */
  dividers: number[];
  /** 데이터 기반 측점값 라벨 (방향 전환점·시종점). */
  kmLabels: KmLabel[];
  /** 데이터 기반 구조물 (교량/터널/역사). */
  structures: StructMark[];
  /** 시점 역명 (route.json routeInfo 우선, 없으면 첫 측점 title). */
  startStationName?: string;
  /** 종점 역명 (route.json routeInfo 우선, 없으면 끝 측점 title). */
  endStationName?: string;
  /** 종점역 미도착 폭(px) — 트랙 우측 이 구간은 '재생 불가'(영상이 도달 못 함)로 별색 표시. */
  endGapPx?: number;
  /** 영상 진행 방향('상'|'하'). 평행 상/하행 구조물 겹침 시 해당 방향을 우선 표출. */
  routeDir?: '상' | '하' | null;
  /** 겹침제외(POI 옵션). true=겹친 구조물 1개만, false=모두 표시. */
  overlapExclude?: boolean;
}

export function Timeline({
  posPx,
  onSeekDown,
  trackGradient,
  trackGradientIdle,
  dividers,
  kmLabels,
  structures,
  startStationName,
  endStationName,
  endGapPx = 0,
  routeDir = null,
  overlapExclude = true,
}: TimelineProps) {
  // 하단 스테이션바는 겹쳐도 무방 → 역사(역, KAKAO_RAIL)는 dedup 대상에서 제외하고 항상 표출.
  // (종점 '대전조차장'이 ~70px 앞 '법동가도교'에 밀려 사라지던 문제 방지.)
  // 교량/터널만 라벨 텍스트가 길어 130px 겹침 방지 적용. 위치는 실제 통과 px 그대로.
  // 평행 상/하행 구조물(예: 회덕터널 상/하)은 ~6px 차이라 한쪽만 남는데, 영상 방향(routeDir)에
  //   해당하는 쪽(하행=‘(하)’, 상행=‘(상)’)을 우선 남긴다. → 드론이 실제 지나는 선로와 일치.
  const isStation = (s: StructMark): boolean => s.category === '역사' || s.category === '역';
  // '(상' / '(하' 로 매칭 — 이름이 "법동가도교(상,인상,고속)" 처럼 괄호 안 다중 토큰이어도 인식.
  //   (이전 '(상)' 완전일치는 다중토큰 이름에서 매칭 실패해 잘못된 변형이 선택되던 버그)
  const dirTag = routeDir === '상' ? '(상' : routeDir === '하' ? '(하' : '';
  const baseStruct = (t: string): string => t.replace(/\s*[(（].*$/, '').trim();
  const others = structures.filter((s) => !isStation(s));
  const ordered = dirTag
    ? [...others].sort((a, b) =>
        (a.title.includes(dirTag) ? 0 : 1) - (b.title.includes(dirTag) ? 0 : 1) || a.px - b.px)
    : [...others].sort((a, b) => a.px - b.px);
  // 같은 구조물의 상/하/인상 변형(같은 base = 같은 좌표)은 '겹침제외 옵션과 무관하게' 진행방향 1개만.
  //   (dir-우선 정렬돼 있어 방향 변형이 먼저 = 채택. 예: 법동가도교(하)/(인상) → (하) 1개)
  const byBase = new Map<string, StructMark>();
  for (const it of ordered) {
    const b = baseStruct(it.title);
    if (!byBase.has(b)) byBase.set(b, it);
  }
  const keptOthers = [...byBase.values()];
  // 라벨은 긴 이름 2줄(아래 splitStructLabel)로 폭을 줄여 표시. 그래도 가까워 겹치면 그대로 둠(엇갈림 없음).
  void overlapExclude;
  const structs = [...structures.filter(isStation), ...keptOthers].sort((a, b) => a.px - b.px);

  // 측점값 라벨 겹침 숨김: px 오름차순으로 보며, 직전에 '표시한' 라벨과 시각적으로 겹치면
  //   (중앙 간격 < 두 라벨 평균 폭) 뒤엣것을 숨긴다 → 먼저 나온(좌측) 것만 1개 표시.
  //   같은/근접 측점을 여러 번 통과해 측점값이 겹쳐 보이던 문제 방지. (아이콘·점선은 그대로 둠)
  const labelW = (v: string): number => v.length * 7.5 + 4; // 13px bold + 2px stroke 대략 폭(px)
  const showMileage: boolean[] = new Array(structs.length).fill(false);
  {
    let lastPx = -Infinity;
    let lastW = 0;
    for (let i = 0; i < structs.length; i++) {
      const s = structs[i];
      // 측점값 없음(s.km<0) 또는 '그 위치에 실제 측점값이 존재하지 않음'(kmExists=false,
      //   좌표 반경만으로 잡힌 통과)은 측점값 라벨을 그리지 않는다. (마커 동그라미는 별도로 유지)
      if (s.km < 0 || s.kmExists === false) continue;
      const w = labelW(fmtKm(s.km));
      if (s.px - lastPx >= (lastW + w) / 2) {
        showMileage[i] = true;
        lastPx = s.px;
        lastW = w;
      }
    }
  }

  // 라벨 그룹핑: 같은 항목(동일 title)을 드론이 여러 번 지나면 통과 마커가 흩어져 이름이 반복된다.
  //  → 동명 통과를 거리와 무관하게 하나로 묶어, 각 통과에 점선 드롭 + 양끝을 잇는 하단 수평 점선(브래킷)을
  //    그리고 묶음 중앙에 라벨 1개만 표시한다. (단일 통과는 브래킷 없이 자기 위치 라벨 1개)
  //    아이콘·측점값은 통과별로 그대로 표출.
  const labelGroups: {
    title: string;
    km: number;
    category: string;
    unreached?: boolean;
    centerPx: number;      // 라벨 위치 및 passed 판정 기준 (묶음 중앙)
    bracketPxs: number[];  // 길이 ≥2면 브래킷(드롭+수평선) 표시 대상 통과 px (정렬)
  }[] = [];
  {
    const byTitle = new Map<string, StructMark[]>();
    for (const s of structs) {
      const a = byTitle.get(s.title);
      if (a) a.push(s);
      else byTitle.set(s.title, [s]);
    }
    for (const arr of byTitle.values()) {
      const sorted = [...arr].sort((a, b) => a.px - b.px);
      // 동명 통과(역사 포함)는 하나로 묶어 브래킷(드롭+수평선) + 묶음 라벨 1개. 단일 통과는 자기 위치 라벨.
      const lo = sorted[0].px;
      const hi = sorted[sorted.length - 1].px;
      labelGroups.push({
        title: sorted[0].title,
        km: sorted[0].km,
        category: sorted[0].category,
        unreached: sorted.some((m) => m.unreached),
        centerPx: (lo + hi) / 2,
        bracketPxs: sorted.length > 1 ? sorted.map((m) => m.px) : [],
      });
    }
  }

  // 구조물 마커 클릭 시 속성 팝업(클릭 좌표 기준 fixed — 트랙 래퍼 transform 영향 회피).
  const [sel, setSel] = useState<{ s: StructMark; x: number; y: number } | null>(null);

  return (
    <>
      {/* 압축 밖 레이어: 상단 그라데이션과 역명 리더선 */}
      <div className={styles.trackFade} />

      {/* 역명 레이어: 트랙 시작/끝(단일 소스) 기준 시·종점 역명 배치 (route.json/측점 유래) */}
      <div
        className={styles.stationLayer}
        style={cssVars({
          '--track-start': px(TRACK_RENDER_START_PX),
          '--track-end': px(TRACK_RENDER_END_PX),
        })}
      >
        {/* 좌측(시점)=역방향 하늘색, 우측(종점)=정방향 주황 */}
        <div className={styles.stationStart} style={{ color: '#06a4c8' }}>{startStationName ?? ''}</div>
        <div className={styles.dotStart} style={{ background: '#06a4c8' }} />
        <div className={styles.leaderStart} style={cssVars({ '--color-station-leader': '#06a4c8' })} />
        <div className={styles.leaderEnd} style={cssVars({ '--color-station-leader': '#ff8a25' })} />
        <div className={styles.dotEnd} style={{ background: '#ff8a25' }} />
        <div className={styles.stationEnd} style={{ color: '#ff8a25' }}>{endStationName ?? ''}</div>
      </div>

      {/* 트랙 본체 (가로 압축 래퍼) */}
      <div
        className={styles.trackWrapper}
        style={{
          ...cssVars({
            '--track-x': px(TRACK_START_PX),
            '--track-w': px(TRACK_WIDTH_PX),
          }),
          transform: TRACK_WRAPPER_TRANSFORM,
        }}
      >
        <div className={styles.trackFrame} />
        <div className={styles.trackFrameStripesH} />
        <div className={styles.trackFrameStripesV} />
        <div className={styles.trackBase} />

        {/* 데이터 기반 색 트랙(그라데이션): 전진=주황 / 후진=하늘색.
            전체는 저톤(드론 순/역방향 미리보기), 재생되어 커서가 지나간 구간은 원래 색으로 복원. */}
        <div className={styles.track}>
          {/* 미재생(미통과): 방향별 저톤 미리보기 (전체 폭) */}
          <div
            style={{
              position: 'absolute',
              left: 0,
              top: 0,
              height: '100%',
              width: px(TRACK_WIDTH_PX),
              background: trackGradientIdle,
              borderRadius: 'inherit',
            }}
          />
          {/* 재생 불가 구간(종점역 미도착): 트랙 우측 endGapPx 전체. */}
          {endGapPx > 0 && (
            <div
              title="재생 불가 구간 (종점역 미도착)"
              style={{
                position: 'absolute',
                left: px(TRACK_WIDTH_PX - endGapPx),
                top: 0,
                height: '100%',
                width: px(endGapPx),
                background: '#54545c',
                borderRadius: 'inherit',
                zIndex: 2,
              }}
            />
          )}
          {/* 재생된 구간: 원래 색 복원 (커서까지 clip, --pos-px CSS변수로 매 프레임 갱신). */}
          <div
            style={{
              position: 'absolute',
              left: 0,
              top: 0,
              height: '100%',
              width: `max(0px, min(${TRACK_WIDTH_PX}px, calc(var(--pos-px, ${TRACK_START_PX}px) - ${TRACK_START_PX}px)))`,
              overflow: 'hidden',
            }}
          >
            <div
              style={{
                position: 'absolute',
                left: 0,
                top: 0,
                height: '100%',
                width: px(TRACK_WIDTH_PX),
                background: trackGradient,
                borderRadius: 'inherit',
              }}
            />
          </div>
          {/* 색(방향) 전환점 구분선 — videoplayer leg 솔기와 동일(검정+흰색 점선) */}
          {dividers.map((dpx, i) => (
            <div
              key={`div-${i}`}
              style={{
                position: 'absolute',
                left: dpx - TRACK_START_PX,
                top: 0,
                width: 0,
                height: '100%',
                transform: 'translateX(-1px)',
                borderLeft: '1px dashed rgba(0, 0, 0, 0.7)',
                borderRight: '1px dashed rgba(255, 255, 255, 0.3)',
                zIndex: 3,
                pointerEvents: 'none',
              }}
            />
          ))}
        </div>

        {/* 측점값 라벨 — 각 시설물 측점값(아이콘 위) */}
        <div className={styles.mileageRow}>
          {structs.map((s, i) =>
            s.km >= 0 && showMileage[i] ? (
              <MileageMarker
                key={`stkm-${i}`}
                marker={{ id: `stkm-${i}`, value: fmtKm(s.km), left: s.px, mileage: 0 }}
              />
            ) : null,
          )}
        </div>

        {/* 구조물 아이콘 — 재생바 위에 겹쳐 표시 (videoplayer 3-slice) */}
        <div style={{ position: 'absolute', inset: 0, zIndex: 4, pointerEvents: 'none' }}>
          {structs.map((s, i) => {
            // 두 상태(무채색 upcoming / 유채색 passed) 아이콘을 겹쳐두고 opacity 로 전환 →
            // 배경이미지 교체가 없어 전환 순간 깜빡임(사라짐)이 없다.
            // 역사/터미널 → 원형 아이콘
            if (s.category === '역사' || s.category === '역') {
              const D = 18;
              // 라벨과 동일 트리거(라벨 W=22 → s.px-11). 아이콘·라벨 동시 전환.
              // 미도착(종점역): 영상이 도달 못 함 → 항상 '예정(무채색)' 상태로 고정.
              // (불투명 아이콘이 뒤의 재생불가 빗금을 덮도록 opacity 1 유지.)
              const passed = s.unreached ? false : posPx >= s.px - 11;
              const base = import.meta.env.BASE_URL;
              const common = {
                position: 'absolute' as const,
                left: s.px - D / 2,
                top: 40.5 - D / 2,
                width: D,
                height: D,
                transition: 'opacity 0.12s',
              };
              return (
                <Fragment key={i}>
                  <img src={`${base}assets/route-segment/terminal/circle-upcoming.png`} alt="" style={{ ...common, opacity: passed ? 0 : 1 }} />
                  <img src={`${base}assets/route-segment/terminal/circle-passed.png`} alt="" style={{ ...common, opacity: passed ? 1 : 0 }} />
                </Fragment>
              );
            }
            const segType: StructureType | null =
              s.category === '터널' ? 'tunnel' : s.category === '교량' ? 'bridge' : null;
            if (!segType) return null;
            // 원본(videoplayer) 치수: 교량 22×16/cap6/top32.5, 터널 24×14/cap10/top33.75
            const isTunnel = segType === 'tunnel';
            const W = isTunnel ? 24 : 22;
            const H = isTunnel ? 14 : 16;
            const CAP = isTunnel ? 10 : 6;
            const TOP = isTunnel ? 33.75 : 32.5;
            const passed = posPx >= s.px - W / 2;
            const seg: StructureSegment = {
              id: `st-${i}`,
              type: segType,
              label: s.title,
              startMileage: 0,
              endMileage: 0,
              left: s.px - W / 2,
              top: TOP,
              width: W,
              height: H,
              capWidth: CAP,
              centerHeight: H,
              passedAtPx: 0,
            };
            const layer = (op: number) =>
              ({ position: 'absolute' as const, inset: 0, opacity: op, transition: 'opacity 0.12s' });
            return (
              <Fragment key={i}>
                <div style={layer(passed ? 0 : 1)}><RouteSegment segment={seg} state="upcoming" /></div>
                <div style={layer(passed ? 1 : 0)}><RouteSegment segment={seg} state="passed" /></div>
              </Fragment>
            );
          })}
        </div>

        {/* 구조물 클릭 핫스팟 제거 — 스테이션바 클릭은 속성 팝업을 띄우지 않고 seek 으로만 동작.
            (POI 정보는 화면 오버레이 라벨 클릭에서 확인) */}

        {/* 동일 항목 묶음 — 각 통과 위치를 수직 점선으로 내려, 라벨 '문자 세로 중앙'을 지나는
            수평 점선으로 잇는다. 수평선은 라벨 뒤로 지나가 글자 양옆으로만 보인다(두번째 참고 이미지).
            z-index 미지정(auto) → DOM 상 뒤의 labelRow 가 위에 그려져 글자가 선을 덮는다. */}
        <div style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>
          {labelGroups
            .filter((g) => g.bracketPxs.length > 1)
            .map((g, gi) => {
              const lo = g.bracketPxs[0];
              const hi = g.bracketPxs[g.bracketPxs.length - 1];
              // 라벨: 절대 top 53, 줄높이 18px(structName). 문자 세로 중앙 y = 53 + 줄수*9.
              const lineCount = splitStructLabel(g.title).length;
              const lineY = 53 + lineCount * 9;
              const dash = '1.5px dashed rgba(232, 232, 232, 0.6)';
              return (
                <Fragment key={`grp-${gi}`}>
                  {/* 문자 세로 중앙을 지나는 수평 점선 (라벨 뒤) */}
                  <div
                    style={{
                      position: 'absolute',
                      left: lo,
                      top: lineY,
                      width: hi - lo,
                      height: 0,
                      borderTop: dash,
                    }}
                  />
                  {/* 각 통과 수직 점선 (트랙 하단 → 수평선) */}
                  {g.bracketPxs.map((bp, j) => (
                    <div
                      key={j}
                      style={{
                        position: 'absolute',
                        left: bp,
                        top: 46,
                        width: 0,
                        height: lineY - 46,
                        borderLeft: dash,
                      }}
                    />
                  ))}
                </Fragment>
              );
            })}
        </div>

        {/* 구조물명 라벨 (바 아래) — 통과 시 아이콘과 함께 색 변경. 긴 명칭은 2줄.
            같은 항목 다중통과는 labelGroups(클러스터)로 묶여 클러스터당 라벨 1개만 표출. */}
        <div className={styles.labelRow}>
          {labelGroups.map((g, i) => {
            const W = g.category === '터널' ? 24 : 22;
            // 미도착 종점역은 항상 미통과(neutral) 색 유지.
            const passed = g.unreached ? false : posPx >= g.centerPx - W / 2;
            // 폭 처리: 긴 이름은 2줄(균등 분할)로 폭을 줄여 표시. 그래도 가까워 겹치면 그대로 둠.
            const lines = splitStructLabel(g.title);
            return (
              <div
                key={i}
                className={`${styles.segmentLabel} ${styles.structName} ${passed ? styles.accent : styles.neutral}`}
                style={cssVars({ '--x': px(g.centerPx) })}
                title={`${g.category} · ${g.title} (${fmtKm(g.km)})`}
              >
                {lines.length === 1 ? lines[0] : <>{lines[0]}<br />{lines[1]}</>}
              </div>
            );
          })}
        </div>

        <div className={styles.seekArea} onMouseDown={onSeekDown} />
      </div>

      {/* 구조물 속성 팝업 — 클릭 좌표 기준 fixed(트랙 래퍼 밖). 배경 클릭 시 닫힘. */}
      {sel && (
        <>
          <div
            onMouseDown={() => setSel(null)}
            style={{ position: 'fixed', inset: 0, zIndex: 999 }}
          />
          <div
            onMouseDown={(e) => e.stopPropagation()}
            style={{
              position: 'fixed',
              left: Math.min(sel.x + 4, window.innerWidth - 264),
              top: Math.max(8, Math.min(sel.y - 8, window.innerHeight - 220)),
              zIndex: 1000,
              maxWidth: 260,
              maxHeight: '45vh',
              overflowY: 'auto',
              background: 'rgba(0,0,0,0.92)',
              border: '1px solid rgba(16,185,129,0.7)',
              borderRadius: 6,
              padding: 8,
              color: '#fff',
              fontSize: 11,
              boxShadow: '0 6px 24px rgba(0,0,0,0.5)',
            }}
          >
            <div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, marginBottom: 4 }}>
              <b style={{ color: '#6ee7b7', wordBreak: 'break-all' }}>📍 {sel.s.title}</b>
              <span onMouseDown={() => setSel(null)} style={{ cursor: 'pointer', color: '#9ca3af' }}>✕</span>
            </div>
            <div style={{ display: 'grid', gridTemplateColumns: 'auto 1fr', columnGap: 8, rowGap: 2, fontFamily: 'monospace', fontSize: 10, color: '#d1d5db' }}>
              {(sel.s.props ?? []).map((p, i) => (
                <Fragment key={i}>
                  <span style={{ color: '#6b7280', whiteSpace: 'nowrap' }}>{p.k}</span>
                  <span style={{ wordBreak: 'break-all' }}>{p.v}</span>
                </Fragment>
              ))}
            </div>
          </div>
        </>
      )}
    </>
  );
}
