import { useCallback, useEffect, useMemo, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useQuery } from '@tanstack/react-query';
import { layoutApi } from '../../api/endpoints';
import { hallAssignApi, type Hall } from '../hallassign/hallAssignApi';
import { ApiRequestError } from '../../api/client';
import { Button } from '../../components/ui/Button';
import { Modal } from '../../components/ui/Modal';
import { GridPdfButton } from '../../components/GridPdfButton';
import { EmptyState, Skeleton } from '../../components/ui/States';
import '../../components/data-table.css';
import { IconCheck, IconClock, IconClose, IconExpand, IconImage } from '../../components/ui/icons';
import { FloorplanCanvas, type CanvasLayer } from './FloorplanCanvas';
import { AutoLayoutDialog } from './AutoLayoutDialog';
import { ValidationPanel } from './ValidationPanel';
import { sampleLayout, emptyLayout } from './sampleLayout';
import { hallFloorplanUrl, hallFloorRegion, hallDwgUrl, hallDwgRegion } from './hallFloorplan';
import type {
  AutoLayoutOption,
  BoothDto,
  BoothType,
  ComplianceReport,
  HallObstacleDto,
  HallPillarDto,
  HallTrenchDto,
  LayoutDto,
  LayoutVersionDto,
} from '../../api/types';
import './editor.css';

type RightTab = 'props' | 'validation';

/** 표준 부스 규격 팔레트(브러시) — 선택 후 캔버스 클릭/드래그로 배치. */
interface BoothBrush {
  key: string;
  label: string;
  wM: number;
  dM: number;
  type: BoothType;
  premium: boolean;
}
const PALETTE: BoothBrush[] = [
  { key: 'basic', label: '기본 3×3m', wM: 3, dM: 3, type: 'basic', premium: false },
  { key: 'premium', label: '프리미엄 6×3m', wM: 6, dM: 3, type: 'premium', premium: true },
  { key: 'island', label: '아일랜드 6×6m', wM: 6, dM: 6, type: 'island', premium: true },
];

function clamp(v: number, min: number, max: number): number {
  return Math.min(max, Math.max(min, v));
}

export function BoothLayoutEditorPage() {
  const { eventId = '', hallId = '' } = useParams();
  const navigate = useNavigate();

  // 전시장(홀) 선택 실 운영(소유자 지시 2026-07-14) — 홀 마스터 전체를 드롭다운으로 제공.
  const hallsQuery = useQuery<Hall[]>({
    queryKey: ['halls'],
    queryFn: hallAssignApi.halls,
    staleTime: 10 * 60 * 1000,
  });
  const hallGroups = useMemo(() => {
    const groups = new Map<number, Hall[]>();
    for (const h of hallsQuery.data ?? []) {
      const list = groups.get(h.exhibitionCenter) ?? [];
      list.push(h);
      groups.set(h.exhibitionCenter, list);
    }
    return [...groups.entries()].sort(([a], [b]) => a - b);
  }, [hallsQuery.data]);

  const [layers, setLayers] = useState<CanvasLayer>({
    trench: true,
    structure: true,
    violations: true,
  });
  const [selectedBoothId, setSelectedBoothId] = useState<string | null>(null);
  const [zoom, setZoom] = useState(1);
  // 평면도 언더레이 소스 — DWG(CAD 렌더) 기본, JPG(크롤 도면) 대체(소유자 지시 2026-07-23: 디폴트 DWG).
  const [drawingSrc, setDrawingSrc] = useState<'jpg' | 'dwg'>('dwg');
  const dwgAvailable = hallDwgUrl(hallId) != null;
  const drawingTab = drawingSrc === 'dwg' && dwgAvailable ? 'dwg' : 'jpg';
  const [rightTab, setRightTab] = useState<RightTab>('validation');
  const [autoOpen, setAutoOpen] = useState(false);
  const [report, setReport] = useState<ComplianceReport | null>(null);
  const [validating, setValidating] = useState(false);
  // 부스 팔레트: 선택된 브러시 + 로컬 배치(저장 전) + 저장/확대 상태.
  const [brushKey, setBrushKey] = useState<string | null>(null);
  const [localBooths, setLocalBooths] = useState<BoothDto[]>([]);
  const [saving, setSaving] = useState(false);
  const [bigOpen, setBigOpen] = useState(false);
  // 배치안 이력(버전) 조회 + 선택 버전 확대 열람 + proposal 적용.
  const [historyOpen, setHistoryOpen] = useState(false);
  const [versionView, setVersionView] = useState<{
    version: number;
    name: string;
    status: string;
    booths: BoothDto[];
    dims: [number, number];
  } | null>(null);
  const [versionLoading, setVersionLoading] = useState(false);
  const [applyingVersion, setApplyingVersion] = useState<number | null>(null);
  const brush = PALETTE.find((b) => b.key === brushKey) ?? null;

  // 배치 조회 (M2 GET layout) — 501이면 degraded 샘플로 캔버스 시연.
  const layoutQuery = useQuery<{ layout: LayoutDto; degraded: boolean }>({
    queryKey: ['layout', eventId, hallId],
    queryFn: async () => {
      try {
        const layout = await layoutApi.get(eventId, hallId);
        return { layout, degraded: false };
      } catch (err) {
        if (err instanceof ApiRequestError && err.code === 'NOT_IMPLEMENTED') {
          return { layout: sampleLayout(eventId, hallId), degraded: true };
        }
        // 아직 저장된 배치안이 없는 홀(404) — 에러가 아니라 "AI 자동배치로 시작" 빈 캔버스 상태.
        if (err instanceof ApiRequestError && err.code === 'NOT_FOUND') {
          return { layout: emptyLayout(eventId, hallId), degraded: false };
        }
        throw err;
      }
    },
  });

  const layout = layoutQuery.data?.layout;
  const degraded = layoutQuery.data?.degraded ?? false;

  // 홀 치수(미터) — 배치 좌표 클램프 근거. 없으면 캔버스 기본값과 정렬.
  const hallDims = useMemo<[number, number]>(() => {
    const d = layout?.hall?.dimsM;
    return d && d.length >= 2 ? [d[0], d[1]] : [126, 90];
  }, [layout]);

  // 저장된 배치 + 로컬(미저장) 배치 병합 — 캔버스/검증 표시 대상.
  const mergedBooths = useMemo<BoothDto[]>(
    () => [...(layout?.booths ?? []), ...localBooths],
    [layout, localBooths],
  );
  const selectedBooth = useMemo(
    () => mergedBooths.find((b) => b.boothId === selectedBoothId) ?? null,
    [mergedBooths, selectedBoothId],
  );

  // 홀/행사 전환 시 로컬 배치·브러시 초기화(다른 홀로 새어나가지 않게).
  useEffect(() => {
    setLocalBooths([]);
    setBrushKey(null);
    setSelectedBoothId(null);
  }, [eventId, hallId]);

  // Esc 로 배치 브러시 취소.
  useEffect(() => {
    if (!brushKey) return;
    const onKey = (e: KeyboardEvent) => {
      if (e.key === 'Escape') setBrushKey(null);
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [brushKey]);

  // 캔버스 클릭/드롭 지점(홀 로컬 미터)에 선택된 규격의 부스를 배치.
  const placeBooth = useCallback(
    (xM: number, yM: number) => {
      if (!brush) return;
      const [hw, hh] = hallDims;
      const x0 = clamp(xM - brush.wM / 2, 0, Math.max(0, hw - brush.wM));
      const y0 = clamp(yM - brush.dM / 2, 0, Math.max(0, hh - brush.dM));
      const stamp = Date.now();
      setLocalBooths((prev) => {
        const n = prev.length + 1;
        const booth: BoothDto = {
          boothId: `local-${stamp}-${n}`,
          boothNo: `N${n}`,
          type: brush.type,
          polygon: [
            [x0, y0],
            [x0 + brush.wM, y0],
            [x0 + brush.wM, y0 + brush.dM],
            [x0, y0 + brush.dM],
            [x0, y0],
          ],
          sizeM: [brush.wM, brush.dM],
          heightM: 3,
          floorLoadTPerM2: 0.5,
          assignedCompanyName: null,
          premium: brush.premium,
        };
        return [...prev, booth];
      });
    },
    [brush, hallDims],
  );

  // 로컬 배치를 서버에 저장(applyOption) → 새 버전으로 반영 후 로컬 비움.
  const saveLocal = useCallback(async () => {
    if (localBooths.length === 0 || saving) return;
    setSaving(true);
    try {
      await layoutApi.applyOption(eventId, hallId, {
        name: `${layout?.name ?? '배치안'}(부스 추가)`,
        booths: mergedBooths,
      });
      setLocalBooths([]);
      setBrushKey(null);
      await layoutQuery.refetch();
    } catch {
      /* 저장 실패 시 로컬 유지 — 재시도 가능 */
    } finally {
      setSaving(false);
    }
  }, [localBooths.length, saving, eventId, hallId, layout?.name, mergedBooths, layoutQuery]);

  // 배치안 버전 이력(자동생성 proposal·draft·applied) — 이력 패널 열 때만 조회.
  const versionsQuery = useQuery<LayoutVersionDto[]>({
    queryKey: ['layout-versions', eventId, hallId],
    queryFn: () => layoutApi.versions(eventId, hallId),
    enabled: historyOpen && !!eventId && !!hallId,
  });

  // 이력에서 특정 버전 열람 — 해당 버전 배치안을 조회해 확대 팝업에 표시.
  const openVersion = useCallback(
    async (v: LayoutVersionDto) => {
      setVersionLoading(true);
      try {
        const dto = await layoutApi.get(eventId, hallId, v.version);
        const d = dto.hall?.dimsM;
        setVersionView({
          version: v.version,
          name: v.name,
          status: v.status,
          booths: dto.booths,
          dims: d && d.length >= 2 ? [d[0], d[1]] : [126, 90],
        });
      } catch {
        /* 조회 실패 무시 — 사용자 재시도 가능 */
      } finally {
        setVersionLoading(false);
      }
    },
    [eventId, hallId],
  );

  // proposal 안을 현재 작업본으로 적용(applyOption → status='applied' 새 버전).
  const applyVersion = useCallback(
    async (v: LayoutVersionDto, booths: BoothDto[]) => {
      if (applyingVersion != null) return;
      setApplyingVersion(v.version);
      try {
        await layoutApi.applyOption(eventId, hallId, { name: `${v.name}(적용)`, booths });
        setVersionView(null);
        setHistoryOpen(false);
        await Promise.all([layoutQuery.refetch(), versionsQuery.refetch()]);
      } catch {
        /* 실패 무시 — 재시도 가능 */
      } finally {
        setApplyingVersion(null);
      }
    },
    [applyingVersion, eventId, hallId, layoutQuery, versionsQuery],
  );

  const runValidate = useCallback(async () => {
    setValidating(true);
    try {
      const r = await layoutApi.validate(eventId, hallId, layout?.version);
      setReport(r);
      setRightTab('validation');
    } catch (err) {
      if (err instanceof ApiRequestError && err.code === 'NOT_IMPLEMENTED') {
        // degraded: 요약 카운트만이라도 노출
        setReport(null);
      }
    } finally {
      setValidating(false);
    }
  }, [eventId, hallId, layout?.version]);

  function applyAutoOption(_option: AutoLayoutOption, appliedHallId: string) {
    // 다이얼로그가 apply-option으로 서버 저장을 마친 뒤 호출된다(2026-07-14 실배선).
    setAutoOpen(false);
    if (appliedHallId && appliedHallId !== hallId) {
      navigate(`/events/${eventId}/halls/${appliedHallId}/layout`);
    } else {
      void layoutQuery.refetch();
    }
  }

  const summary = layout?.summary;

  const VERSION_STATUS_LABEL: Record<string, string> = {
    applied: '적용됨',
    draft: '작업중',
    proposal: '자동생성안',
  };
  const historyPdfColumns = [
    { key: 'version', label: '버전' },
    { key: 'name', label: '이름' },
    { key: 'status', label: '상태' },
    { key: 'booths', label: '부스', align: 'right' as const },
    { key: 'updated', label: '갱신' },
  ];
  const historyPdfRows = () =>
    (versionsQuery.data ?? []).map((v) => ({
      version: `v${v.version}`,
      name: v.name,
      status: VERSION_STATUS_LABEL[v.status] ?? v.status,
      booths: v.boothCount.toLocaleString(),
      updated: formatDateTime(v.updatedAt),
    }));

  return (
    <div className="kx-editor">
      {/* 상단 툴바 */}
      <header className="kx-editor__toolbar">
        <div className="kx-editor__toolbar-left">
          <select
            className="kx-select kx-editor__hall-select"
            aria-label="전시장 홀 선택"
            value={hallId}
            onChange={(e) => navigate(`/events/${eventId}/halls/${e.target.value}/layout`)}
          >
            {!hallsQuery.data?.some((h) => h.id === hallId) && (
              <option value={hallId}>{hallId || '홀 선택'}</option>
            )}
            {hallGroups.map(([center, halls]) => (
              <optgroup key={center} label={`제${center}전시장`}>
                {halls.map((h) => (
                  <option key={h.id} value={h.id}>
                    {h.label}
                  </option>
                ))}
              </optgroup>
            ))}
          </select>
          <h2 className="kx-editor__hall">{layout?.name ?? '배치안'}</h2>
          <span className="kx-editor__version">
            v{layout?.version ?? '—'}
            <span className="kx-editor__saved">저장됨</span>
          </span>
          {degraded && (
            <span className="kx-editor__degraded" title="백엔드 매퍼 준비 중 — 샘플 데이터 표시">
              데이터 준비 중 (샘플)
            </span>
          )}
        </div>
        <div className="kx-editor__toolbar-right">
          <Button variant="secondary" leadingIcon={<IconExpand size={16} />} onClick={() => setBigOpen(true)}>
            평면도 크게
          </Button>
          <Button variant="secondary" leadingIcon={<IconClock size={16} />} onClick={() => setHistoryOpen(true)}>
            배치 이력
          </Button>
          <Button variant="secondary" leadingIcon={<IconImage size={16} />} onClick={() => setAutoOpen(true)}>
            홀 전경 생성
          </Button>
          <Button variant="secondary" leadingIcon={<IconCheck size={16} />} onClick={runValidate} disabled={validating}>
            {validating ? '검증 중…' : '규정 검증 실행'}
          </Button>
          <Button variant="ai" leadingIcon="✦" onClick={() => setAutoOpen(true)}>
            AI 자동배치
          </Button>
        </div>
      </header>

      <div className="kx-editor__workspace">
        {/* 좌측 도구 패널 */}
        <aside className="kx-editor__left" aria-label="도구">
          <section className="kx-tool-group">
            <h3>부스 팔레트</h3>
            <div className="kx-palette">
              {PALETTE.map((b) => (
                <button
                  key={b.key}
                  type="button"
                  className={`kx-palette__item ${brushKey === b.key ? 'is-active' : ''}`}
                  aria-pressed={brushKey === b.key}
                  draggable
                  onDragStart={() => setBrushKey(b.key)}
                  onClick={() => setBrushKey((prev) => (prev === b.key ? null : b.key))}
                  title="선택 후 도면을 클릭하거나 끌어다 놓아 배치"
                >
                  <span
                    className="kx-palette__swatch"
                    style={{ width: Math.min(48, b.wM * 7), height: Math.min(48, b.dM * 7) }}
                  />
                  <span className="kx-palette__label">{b.label}</span>
                </button>
              ))}
            </div>
            <p className="kx-palette__hint">
              규격을 선택한 뒤 도면을 <strong>클릭</strong>하거나 <strong>끌어다 놓으면</strong> 배치됩니다.
            </p>
          </section>
          <section className="kx-tool-group">
            <h3>부대 시설</h3>
            <div className="kx-facility">
              <button>무대</button>
              <button>라운지</button>
              <button>주출입구</button>
            </div>
          </section>
          <section className="kx-tool-group">
            <h3>도면</h3>
            <div className="kx-drawing-tabs" role="tablist" aria-label="평면도 종류">
              <button
                role="tab"
                aria-selected={drawingTab === 'jpg'}
                className={drawingTab === 'jpg' ? 'is-active' : ''}
                onClick={() => setDrawingSrc('jpg')}
              >
                JPG
              </button>
              <button
                role="tab"
                aria-selected={drawingTab === 'dwg'}
                className={drawingTab === 'dwg' ? 'is-active' : ''}
                disabled={!dwgAvailable}
                title={dwgAvailable ? 'CAD(DWG) 실측 렌더 도면' : '이 홀은 CAD 렌더 준비 중'}
                onClick={() => setDrawingSrc('dwg')}
              >
                DWG
              </button>
            </div>
          </section>
          <section className="kx-tool-group">
            <h3>레이어</h3>
            <LayerToggle
              label="트렌치 그리드"
              on={layers.trench}
              onToggle={() => setLayers((l) => ({ ...l, trench: !l.trench }))}
            />
            <LayerToggle
              label="기둥·비상구"
              on={layers.structure}
              onToggle={() => setLayers((l) => ({ ...l, structure: !l.structure }))}
            />
            <LayerToggle
              label="위반 오버레이"
              on={layers.violations}
              onToggle={() => setLayers((l) => ({ ...l, violations: !l.violations }))}
            />
          </section>
        </aside>

        {/* 중앙 캔버스 */}
        <section className="kx-editor__canvas-wrap">
          {brush && (
            <div className="kx-editor__place-hint" role="status">
              <span>
                <strong>‘{brush.label}’</strong> 배치 중 — 도면을 클릭하거나 끌어다 놓으세요.
              </span>
              <button type="button" className="kx-editor__place-stop" onClick={() => setBrushKey(null)}>
                <IconClose size={14} /> 배치 종료 (Esc)
              </button>
            </div>
          )}
          <div className="kx-editor__canvas-stage">
            {layoutQuery.isLoading ? (
              <div className="kx-editor__canvas-loading">
                <Skeleton height={480} radius={8} />
              </div>
            ) : layoutQuery.isError ? (
              <div className="kx-editor__canvas-error" role="alert">
                <p>도면을 불러오지 못했습니다.</p>
                <Button variant="secondary" onClick={() => layoutQuery.refetch()}>
                  다시 시도
                </Button>
              </div>
            ) : layout && mergedBooths.length === 0 && !brush ? (
              <div className="kx-editor__canvas-empty">
                <p>AI 자동배치로 시작하거나, 왼쪽 부스 팔레트에서 규격을 선택해 직접 배치하세요.</p>
                <Button variant="ai" leadingIcon="✦" onClick={() => setAutoOpen(true)}>
                  AI 자동배치
                </Button>
              </div>
            ) : (
              layout && (
                <FloorplanCanvas
                  hallDims={hallDims}
                  booths={mergedBooths}
                  layers={layers}
                  selectedBoothId={selectedBoothId}
                  onSelectBooth={setSelectedBoothId}
                  zoom={zoom}
                  onZoomChange={setZoom}
                  floorplanUrl={drawingTab === 'dwg' ? hallDwgUrl(hallId) : hallFloorplanUrl(hallId)}
                  floorRegion={drawingTab === 'dwg' ? hallDwgRegion(hallId) : hallFloorRegion(hallId)}
                  pillars={layout.hall?.pillars}
                  obstacles={layout.hall?.obstacles}
                  trenches={layout.hall?.trenches}
                  placementActive={!!brush}
                  onPlaceAt={placeBooth}
                />
              )
            )}
          </div>

          {/* 하단 상태 바 */}
          <div className="kx-editor__statusbar">
            <div className="kx-editor__stats tnum">
              {summary && (
                <>
                  <span>부스 {summary.boothCount} / {summary.targetBoothCount}</span>
                  <span className="kx-edot" />
                  <span>판매면적 {summary.salesAreaM2.toLocaleString()}㎡</span>
                </>
              )}
              {localBooths.length > 0 && (
                <>
                  <span className="kx-edot" />
                  <span className="kx-editor__local-count">신규 배치 {localBooths.length}개(미저장)</span>
                  <Button size="sm" onClick={saveLocal} disabled={saving}>
                    {saving ? '저장 중…' : '배치 저장'}
                  </Button>
                  <Button size="sm" variant="ghost" onClick={() => setLocalBooths([])} disabled={saving}>
                    초기화
                  </Button>
                </>
              )}
            </div>
            <div className="kx-editor__zoom">
              <button aria-label="축소" onClick={() => setZoom((z) => Math.max(0.5, z - 0.1))}>
                −
              </button>
              <span className="tnum">{Math.round(zoom * 100)}%</span>
              <button aria-label="확대" onClick={() => setZoom((z) => Math.min(2, z + 0.1))}>
                +
              </button>
            </div>
          </div>
        </section>

        {/* 우측 속성/검증 패널 */}
        <aside className="kx-editor__right" aria-label="속성 및 검증">
          <div className="kx-editor__tabs" role="tablist">
            <button
              role="tab"
              aria-selected={rightTab === 'props'}
              className={rightTab === 'props' ? 'is-active' : ''}
              onClick={() => setRightTab('props')}
            >
              속성
            </button>
            <button
              role="tab"
              aria-selected={rightTab === 'validation'}
              className={rightTab === 'validation' ? 'is-active' : ''}
              onClick={() => setRightTab('validation')}
            >
              검증{summary ? ` (${summary.violationBlock + summary.violationWarn})` : ''}
            </button>
          </div>

          {rightTab === 'props' ? (
            <div className="kx-props">
              {selectedBooth ? (
                <dl className="kx-props__list">
                  <PropRow k="부스번호" v={selectedBooth.boothNo} />
                  <PropRow k="타입" v={selectedBooth.type} />
                  <PropRow k="크기" v={`${selectedBooth.sizeM[0]}×${selectedBooth.sizeM[1]}m`} />
                  <PropRow k="높이" v={`${selectedBooth.heightM}m`} />
                  <PropRow k="바닥하중" v={`${selectedBooth.floorLoadTPerM2} t/㎡`} />
                  <PropRow k="배정 업체" v={selectedBooth.assignedCompanyName ?? '미배정'} />
                </dl>
              ) : (
                <p className="kx-props__empty">부스를 선택하면 속성이 표시됩니다.</p>
              )}
            </div>
          ) : (
            <ValidationPanel
              report={report}
              summary={summary}
              degraded={degraded}
            />
          )}
        </aside>
      </div>

      {autoOpen && (
        <AutoLayoutDialog
          eventId={eventId}
          hallId={hallId}
          onClose={() => setAutoOpen(false)}
          onApply={applyAutoOption}
        />
      )}

      {/* 평면도 크게 보기 — 전체화면급 팝업(읽기 전용 대형 뷰어) */}
      <Modal
        open={bigOpen}
        onClose={() => setBigOpen(false)}
        title={`${layout?.name ?? '배치안'} — 평면도 크게 보기`}
        size="xl"
        flushBody
      >
        {layout && (
          <div className="kx-editor__bigview">
            <BigFloorplanView
              hallDims={hallDims}
              booths={mergedBooths}
              layers={layers}
              floorplanUrl={drawingTab === 'dwg' ? hallDwgUrl(hallId) : hallFloorplanUrl(hallId)}
              floorRegion={drawingTab === 'dwg' ? hallDwgRegion(hallId) : hallFloorRegion(hallId)}
              pillars={layout.hall?.pillars}
              obstacles={layout.hall?.obstacles}
              trenches={layout.hall?.trenches}
            />
          </div>
        )}
      </Modal>

      {/* 배치안 이력(버전) — 목록 그리드(Nifty .kx-table) */}
      <Modal
        open={historyOpen}
        onClose={() => setHistoryOpen(false)}
        title="배치안 이력"
        size="lg"
        footer={<Button variant="secondary" onClick={() => setHistoryOpen(false)}>닫기</Button>}
      >
        {versionsQuery.isLoading ? (
          <Skeleton height={200} radius={8} />
        ) : versionsQuery.isError ? (
          <EmptyState title="이력을 불러오지 못했습니다" description="잠시 후 다시 시도해 주세요." />
        ) : (versionsQuery.data?.length ?? 0) === 0 ? (
          <EmptyState
            title="저장된 배치안이 없습니다"
            description="AI 자동배치를 생성하면 후보안이 이력에 기록되고, 저장·적용 시 버전으로 남습니다."
          />
        ) : (
          <>
          <div className="kx-dt__toolbar">
            <div className="kx-dt__toolbar-end">
              <GridPdfButton title="배치안 이력" columns={historyPdfColumns} rows={historyPdfRows} disabled={(versionsQuery.data?.length ?? 0) === 0} />
            </div>
          </div>
          <div className="kx-table-scroll">
          <table className="kx-table kx-editor__hist-table">
            <thead>
              <tr>
                <th>버전</th>
                <th>이름</th>
                <th>상태</th>
                <th className="kx-num">부스</th>
                <th>갱신</th>
                <th aria-label="동작" />
              </tr>
            </thead>
            <tbody>
              {versionsQuery.data!.map((v) => (
                <tr key={v.version}>
                  <td className="tnum">v{v.version}</td>
                  <td>{v.name}</td>
                  <td><VersionStatusBadge status={v.status} /></td>
                  <td className="kx-num tnum">{v.boothCount.toLocaleString()}</td>
                  <td className="tnum">{formatDateTime(v.updatedAt)}</td>
                  <td className="kx-editor__hist-actions">
                    <Button size="sm" variant="secondary" onClick={() => openVersion(v)} disabled={versionLoading}>
                      보기
                    </Button>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
          </div>
          </>
        )}
      </Modal>

      {/* 선택 버전 확대 열람 — 평면도 팝업 재사용 + proposal 적용 */}
      <Modal
        open={!!versionView}
        onClose={() => setVersionView(null)}
        title={versionView ? `${versionView.name} (v${versionView.version}) 미리보기` : '배치안'}
        size="xl"
        flushBody
        footer={
          versionView ? (
            <>
              <Button variant="ghost" onClick={() => setVersionView(null)}>닫기</Button>
              {versionView.status === 'proposal' && (
                <Button
                  onClick={() => applyVersion(
                    { version: versionView.version, name: versionView.name, status: versionView.status, boothCount: versionView.booths.length, updatedAt: '' },
                    versionView.booths,
                  )}
                  disabled={applyingVersion != null}
                >
                  {applyingVersion === versionView.version ? '적용 중…' : '이 안 적용'}
                </Button>
              )}
            </>
          ) : undefined
        }
      >
        {versionView && (
          <div className="kx-editor__bigview">
            <BigFloorplanView
              hallDims={versionView.dims}
              booths={versionView.booths}
              layers={layers}
              floorplanUrl={drawingTab === 'dwg' ? hallDwgUrl(hallId) : hallFloorplanUrl(hallId)}
              floorRegion={drawingTab === 'dwg' ? hallDwgRegion(hallId) : hallFloorRegion(hallId)}
              pillars={layout?.hall?.pillars}
              obstacles={layout?.hall?.obstacles}
              trenches={layout?.hall?.trenches}
            />
          </div>
        )}
      </Modal>
    </div>
  );
}

/** 배치안 상태 배지 — draft/applied/proposal(Nifty 뱃지·kx 상태색). */
function VersionStatusBadge({ status }: { status: string }) {
  const map: Record<string, { label: string; cls: string }> = {
    applied: { label: '적용됨', cls: 'is-applied' },
    draft: { label: '작업중', cls: 'is-draft' },
    proposal: { label: '자동생성안', cls: 'is-proposal' },
  };
  const m = map[status] ?? { label: status, cls: 'is-draft' };
  return <span className={`kx-editor__hist-badge ${m.cls}`}>{m.label}</span>;
}

/** ISO 8601 → 'YYYY-MM-DD HH:MM'(로컬). */
function formatDateTime(iso: string): string {
  if (!iso) return '-';
  const d = new Date(iso);
  if (Number.isNaN(d.getTime())) return iso;
  const p = (n: number) => String(n).padStart(2, '0');
  return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;
}

/** 대형 평면도 뷰어(읽기 전용) — 자체 zoom 상태(휠 확대/축소). */
function BigFloorplanView(props: {
  hallDims: [number, number];
  booths: BoothDto[];
  layers: CanvasLayer;
  floorplanUrl: string | null;
  floorRegion: [number, number, number, number] | null;
  pillars?: HallPillarDto[] | null;
  obstacles?: HallObstacleDto[] | null;
  trenches?: HallTrenchDto[] | null;
}) {
  const [zoom, setZoom] = useState(1);
  return (
    <FloorplanCanvas
      hallDims={props.hallDims}
      booths={props.booths}
      layers={props.layers}
      selectedBoothId={null}
      onSelectBooth={() => {}}
      zoom={zoom}
      onZoomChange={setZoom}
      floorplanUrl={props.floorplanUrl}
      floorRegion={props.floorRegion}
      pillars={props.pillars}
      obstacles={props.obstacles}
      trenches={props.trenches}
    />
  );
}

function LayerToggle({ label, on, onToggle }: { label: string; on: boolean; onToggle: () => void }) {
  return (
    <label className="kx-layer">
      <span>{label}</span>
      <button
        type="button"
        role="switch"
        aria-checked={on}
        aria-label={label}
        className={`kx-eswitch ${on ? 'is-on' : ''}`}
        onClick={onToggle}
      >
        <span className="kx-eswitch__knob" />
      </button>
    </label>
  );
}

function PropRow({ k, v }: { k: string; v: string }) {
  return (
    <div className="kx-props__row">
      <dt>{k}</dt>
      <dd className="tnum">{v}</dd>
    </div>
  );
}
