<!-- src/routes/(protected)/bigdata-generate/+page.svelte  -->

<script context="module" lang="ts">
  // Declare global cv for TypeScript
  declare let cv: any;
</script>

<script lang="ts">
  type PageData = any;
  import { tick, onMount } from 'svelte';
  import Icon from '@iconify/svelte';

  // --- 상태 변수 ---
  export let data: PageData;
  let dataNumbers: DataNumberInfo[] = [];
  let rectangles: Rectangle[] = [];
  let selectedMovie: number | null = null;
  let selectedDataNumberInfo: DataNumberInfo | null = null;
  let isLoadingDataNumbers = false;
  let isLoadingImage = false;
  let imageHasError = false;

  // --- Undo/Redo 및 OF 결과 저장을 위한 상태 변수 ---
  let originalRectsFromDB: Rectangle[] = [];
  let stabilizedRectsResult: {
    stabilizedRects: Rectangle[],
    flowLines: any[]
  } | null = null;

  // --- UI 제어 상태 변수 ---
  let isCvReady = false;
  let opticalFlowCanvas: HTMLCanvasElement;
  let showOpticalFlowVectors = true;
  let showPrevRects = true;
  let showCurrentRects = true;
  let prevRectsForDisplay: Rectangle[] = [];

  // --- 상호작용 상태 변수 ---
  let selectedRectIds: number[] = []; 
  let imageElement: HTMLImageElement;
  let svgElement: SVGSVGElement;
  let viewBox = '0 0 100 100';
  let activeInteraction: {
      type: 'move' | 'resize';
      handle?: string;
      startX: number;
      startY: number;
      initialRects?: Rectangle[]; 
  } | null = null;
  
  let dataNumberPanel: HTMLDivElement;
  let clipboard: Omit<Rectangle, 'id'>[] | null = null;

  let movieFilter: 'my' | 'all' = 'my'; // 기본값 '나'
  // 필터에 따라 보여줄 영화 목록을 동적으로 결정
  $: displayedMovies = movieFilter === 'my' ? data.myMovies || [] : data.allMovies || [];

  // 필터 변경 시, 현재 선택된 영화가 새 목록에 없으면 선택 해제
  $: if (displayedMovies) {
    if (selectedMovie && !displayedMovies.includes(selectedMovie)) {
      selectedMovie = null;
      selectedDataNumberInfo = null;
      rectangles = [];
      dataNumbers = [];
      isLoadingDataNumbers = false;
    }
  }

  // --- 반응형 로직 ---
  let selectedRectangles: Rectangle[] = [];
  $: selectedRectangles = rectangles.filter(r => selectedRectIds.includes(r.id));

  $: if (imageElement) {
    const updateViewBox = () => {
      if (imageElement.naturalWidth > 0 && imageElement.naturalHeight > 0) {
        viewBox = `0 0 ${imageElement.naturalWidth} ${imageElement.naturalHeight}`;
      }
    };
    if (imageElement.complete) {
      updateViewBox();
    } else {
      imageElement.onload = updateViewBox;
    }
  }

  $: if (selectedDataNumberInfo) {
    imageHasError = false;
    selectedRectIds = [];
    if (dataNumberPanel) {
        const selectedElement = dataNumberPanel.querySelector(`[data-index="${selectedDataNumberInfo.Index}"]`);
        if (selectedElement) {
            const containerRect = dataNumberPanel.getBoundingClientRect();
            const elementRect = selectedElement.getBoundingClientRect();
            if (elementRect.top < containerRect.top || elementRect.bottom > containerRect.bottom) {
                //selectedElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
                selectedElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
            }
        }
    }
  }

  $: if (opticalFlowCanvas) {
    showOpticalFlowVectors, stabilizedRectsResult, redrawCanvas();
  }
  
  $: crudActions = [
    { label: 'Add', icon: 'mdi:plus-box-outline', onClick: addNewRectangle, disabled: !selectedDataNumberInfo, color: 'bg-blue-600 hover:bg-blue-700' },
    { label: 'Copy', icon: 'mdi:content-copy', onClick: copySelectedRect, disabled: selectedRectangles.length === 0, color: 'bg-indigo-600 hover:bg-indigo-700' },
    { label: 'Paste', icon: 'mdi:content-paste', onClick: pasteRect, disabled: !clipboard || clipboard.length === 0, color: 'bg-purple-600 hover:bg-purple-700' },
    { label: 'Delete', icon: 'mdi:delete-outline', onClick: deleteSelectedRect, disabled: selectedRectangles.length === 0, color: 'bg-red-600 hover:bg-red-700' },
    { label: 'Save', icon: 'mdi:content-save-outline', onClick: () => saveRectangles(rectangles), disabled: rectangles.length === 0, color: 'bg-teal-600 hover:bg-teal-700' }
  ];

  $: flowActions = [
    { label: 'Apply Flow', icon: 'mdi:arrow-right-bold-box-outline', onClick: applyOpticalFlowResult, disabled: !stabilizedRectsResult, color: 'bg-green-600 hover:bg-green-700' },
    { label: 'Undo', icon: 'mdi:undo', onClick: undoOpticalFlowResult, disabled: rectangles === originalRectsFromDB, color: 'bg-gray-500 hover:bg-gray-600' }
  ];

  onMount(() => {
    const initialize = () => {
      if (cv && cv.Mat) { isCvReady = true; return; }
      if (cv) { cv.onRuntimeInitialized = () => { isCvReady = true; }; }
    };
    const intervalId = setInterval(() => {
      if (typeof cv !== 'undefined') { clearInterval(intervalId); initialize(); }
    }, 50);
  });

  // (이하 모든 script 로직은 이전과 동일)
  // ... calculateOpticalFlow, redrawCanvas, selectMovie, selectDataNumber 등 모든 함수 ...
  // --- 계산만 담당하는 함수 ---
  async function calculateOpticalFlow(
    prevImgSrc: string,
    currentImgSrc: string,
    prevRects: Rectangle[]
  ): Promise<{ stabilizedRects: Rectangle[], flowLines: any[] }> {
    const emptyResult = { stabilizedRects: prevRects, flowLines: [] };
    if (!isCvReady) return emptyResult;

    const loadImage = (src: string): Promise<HTMLImageElement> => new Promise((resolve, reject) => {
      const img = new Image();
      img.crossOrigin = "Anonymous";
      img.onload = () => resolve(img);
      img.onerror = (err) => reject(new Error(`Failed to load image: ${src}, error: ${err}`));
      img.src = src;
    });

    try {
      const [prevImg, currentImg] = await Promise.all([
        loadImage(prevImgSrc),
        loadImage(currentImgSrc)
      ]);

      const stabilizedRects: Rectangle[] = [];
      let allGoodFlowLines: any[] = [];

      // OpenCV 이미지 변환
      let src1 = cv.imread(prevImg);
      let src2 = cv.imread(currentImg);
      let gray1 = new cv.Mat();
      let gray2 = new cv.Mat();
      cv.cvtColor(src1, gray1, cv.COLOR_RGBA2GRAY);
      cv.cvtColor(src2, gray2, cv.COLOR_RGBA2GRAY);

      const imageWidth = gray1.cols;
      const imageHeight = gray1.rows;

      for (const prevRect of prevRects) {
        let allFlowLines: any[] = [];
        let points1 = new cv.Mat();
        let points2 = new cv.Mat();
        let status = new cv.Mat();
        let err = new cv.Mat();

        const roiRect = new cv.Rect(prevRect.x, prevRect.y, prevRect.width, prevRect.height);
        let roiGray1 = gray1.roi(roiRect);

        // 특징점 검출
        cv.goodFeaturesToTrack(roiGray1, points1, 200, 0.01, 5);
        if (points1.rows === 0) {
          stabilizedRects.push({ ...prevRect });
          roiGray1.delete(); points1.delete(); points2.delete(); status.delete(); err.delete();
          continue;
        }

        // ROI 좌표 보정
        for (let i = 0; i < points1.rows; i++) {
          points1.data32F[i * 2] += prevRect.x;
          points1.data32F[i * 2 + 1] += prevRect.y;
        }

        // Optical Flow 계산
        cv.calcOpticalFlowPyrLK(gray1, gray2, points1, points2, status, err);

        let initial_avg_dx = 0, initial_avg_dy = 0, tracked_count = 0;
        for (let i = 0; i < status.rows; ++i) {
          if (status.data[i] === 1) {
            const p1 = new cv.Point(points1.data32F[i * 2], points1.data32F[i * 2 + 1]);
            const p2 = new cv.Point(points2.data32F[i * 2], points2.data32F[i * 2 + 1]);
            const dx = p2.x - p1.x;
            const dy = p2.y - p1.y;
            allFlowLines.push({ p1, p2, dx, dy, length: Math.sqrt(dx * dx + dy * dy) });
            initial_avg_dx += dx;
            initial_avg_dy += dy;
            tracked_count++;
          }
        }

        if (tracked_count > 0) {
          initial_avg_dx /= tracked_count;
          initial_avg_dy /= tracked_count;
        }

        const maxLength = 40;
        const goodFlowLines = allFlowLines.filter(line => {
          if (line.length > maxLength) return false;
          const dotProduct = (line.dx * initial_avg_dx) + (line.dy * initial_avg_dy);
          if (dotProduct < 0) return false;
          return true;
        });

        allGoodFlowLines.push(...goodFlowLines);

        let final_avg_dx = 0, final_avg_dy = 0;
        if (goodFlowLines.length > 0) {
          goodFlowLines.forEach(line => {
            final_avg_dx += line.dx;
            final_avg_dy += line.dy;
          });
          final_avg_dx /= goodFlowLines.length;
          final_avg_dy /= goodFlowLines.length;
        }

        // 이동된 좌표
        let newX = prevRect.x + final_avg_dx;
        let newY = prevRect.y + final_avg_dy;
        let newWidth = prevRect.width;
        let newHeight = prevRect.height;

        // === 경계 보정 ===
        if (newX < 1) newX = 1;
        if (newY < 1) newY = 1;

        if (newX + newWidth > imageWidth - 1)
          newWidth = imageWidth - newX - 1;
        if (newY + newHeight > imageHeight - 1)
          newHeight = imageHeight - newY - 1;

        stabilizedRects.push({
          ...prevRect,
          x: Math.round(newX),
          y: Math.round(newY),
          width: Math.max(1, Math.round(newWidth)),
          height: Math.max(1, Math.round(newHeight))
        });

        // 메모리 해제
        points1.delete(); points2.delete(); status.delete(); err.delete(); roiGray1.delete();
      }

      src1.delete(); src2.delete(); gray1.delete(); gray2.delete();

      return { stabilizedRects, flowLines: allGoodFlowLines };

    } catch (error) {
      console.error("Optical Flow 처리 중 에러:", error);
      return emptyResult;
    }
  }

  // 그리기를 전담하는 함수
  function redrawCanvas() {
    const ctx = opticalFlowCanvas.getContext('2d');
    if (!ctx || !imageElement) return;

    const canvasWidth = opticalFlowCanvas.clientWidth;
    const canvasHeight = opticalFlowCanvas.clientHeight;
    opticalFlowCanvas.width = canvasWidth;
    opticalFlowCanvas.height = canvasHeight;
    ctx.clearRect(0, 0, canvasWidth, canvasHeight);

    if (showOpticalFlowVectors && stabilizedRectsResult) {
        // --- 1. 모바일 환경을 감지하는 로직 추가 ---
        const isMobile = canvasWidth < 768; // 화면 너비가 768px 미만이면 모바일로 간주

        // --- 2. 모바일/데스크톱에 따라 화살표 스타일 동적 변경 ---
        const lineWidth = isMobile ? 1 : 2; // 모바일이면 선 굵기 1px
        const headlen = isMobile ? 3 : 5;   // 모바일이면 화살표 머리 크기 3
        const arrowColor = 'rgba(0, 255, 255, 0.8)'; // 눈에 잘 띄는 청록색(Cyan)으로 변경
        const maxFlowLines = isMobile ? 75 : 500; // 모바일이면 최대 75개 화살표만 표시

        const { stabilizedRects, flowLines } = stabilizedRectsResult;
        
        // --- 3. 표시할 화살표 개수를 제한하는 로직 추가 ---
        let linesToDraw = flowLines;
        if (flowLines.length > maxFlowLines) {
          // 화살표가 너무 많으면, 무작위로 샘플링하여 개수를 줄임
          linesToDraw = flowLines.sort(() => 0.5 - Math.random()).slice(0, maxFlowLines);
        }

        const imgNaturalWidth = imageElement.naturalWidth;
        const imgNaturalHeight = imageElement.naturalHeight;
        const scale = Math.min(canvasWidth / imgNaturalWidth, canvasHeight / imgNaturalHeight);
        const renderedImgWidth = imgNaturalWidth * scale;
        const renderedImgHeight = imgNaturalHeight * scale;
        const offsetX = (canvasWidth - renderedImgWidth) / 2;
        const offsetY = (canvasHeight - renderedImgHeight) / 2;

        // 화살표 그리기 (수정된 스타일 변수 사용)
        ctx.lineWidth = lineWidth;
        ctx.strokeStyle = arrowColor;
        ctx.fillStyle = arrowColor;
        
        linesToDraw.forEach(line => { // <-- 전체 flowLines 대신 개수가 조절된 linesToDraw 사용
            const startX = (line.p1.x * scale) + offsetX;
            const startY = (line.p1.y * scale) + offsetY;
            const endX = (line.p2.x * scale) + offsetX;
            const endY = (line.p2.y * scale) + offsetY;
            if (Math.abs(startX - endX) < 0.5 && Math.abs(startY - endY) < 0.5) return;
            const angle = Math.atan2(endY - startY, endX - startX);
            ctx.beginPath();
            ctx.moveTo(startX, startY);
            ctx.lineTo(endX, endY);
            ctx.stroke();
            ctx.beginPath();
            ctx.moveTo(endX, endY);
            ctx.lineTo(endX - headlen * Math.cos(angle - Math.PI / 6), endY - headlen * Math.sin(angle - Math.PI / 6));
            ctx.lineTo(endX - headlen * Math.cos(angle + Math.PI / 6), endY - headlen * Math.sin(angle + Math.PI / 6));
            ctx.lineTo(endX, endY);
            ctx.fill();
        });

        // 예측 사각형 그리기 (이 부분은 변경 없음)
        stabilizedRects.forEach(rect => {
            const rectX = (rect.x * scale) + offsetX;
            const rectY = (rect.y * scale) + offsetY;
            const rectWidth = rect.width * scale;
            const rectHeight = rect.height * scale;
            ctx.strokeStyle = 'rgba(0, 255, 0, 0.9)';
            ctx.lineWidth = 2; // 예측 사각형은 원래 굵기 유지
            ctx.strokeRect(rectX, rectY, rectWidth, rectHeight);
        });
    }
  }
  
  async function selectMovie(movieIndex: number) {
    if (selectedMovie === movieIndex) return;
    selectedMovie = movieIndex;
    selectedDataNumberInfo = null;
    rectangles = [];
    dataNumbers = [];
    isLoadingDataNumbers = true;
    try {
      const response = await fetch(`/api/datanumbers/${movieIndex}`);
      if (!response.ok) throw new Error('Failed to fetch data numbers');
      dataNumbers = await response.json();
    } catch (error) {
      console.error(error);
      alert((error as Error).message);
    } finally {
      isLoadingDataNumbers = false;
    }
   }

  async function selectDataNumber(dataInfo: DataNumberInfo) {
    if (selectedDataNumberInfo?.Index === dataInfo.Index || isLoadingImage) return;
    const prevRects = JSON.parse(JSON.stringify(rectangles));
    const prevImgSrc = selectedDataNumberInfo?.LocationFile ? `/api/images/${selectedDataNumberInfo.LocationFile}` : null;
    prevRectsForDisplay = prevRects;
    isLoadingImage = true;
    selectedRectIds = [];
    stabilizedRectsResult = null;
    try {
        const newImgSrc = `/api/images/${dataInfo.LocationFile}`;
        const preloadImagePromise = new Promise((resolve, reject) => {
            const img = new Image();
            img.onload = () => resolve(true);
            img.onerror = () => reject(new Error('Image preloading failed'));
            img.src = newImgSrc;
        });
        const fetchRectsPromise = fetch(`/api/rectangles/by-ref/${dataInfo.Index}`);
        const [_, response] = await Promise.all([preloadImagePromise, fetchRectsPromise]);
        if (!response.ok) throw new Error('Failed to fetch rectangles');
        const newRectsFromAPI = await response.json();
        
        originalRectsFromDB = newRectsFromAPI;
        selectedDataNumberInfo = dataInfo;
        await tick();
        
        if (prevImgSrc && prevRects.length > 0 && isCvReady) {
            stabilizedRectsResult = await calculateOpticalFlow(prevImgSrc, newImgSrc, prevRects);
        }
        rectangles = newRectsFromAPI;
        const newButton = dataNumberPanel?.querySelector(`li[data-index="${dataInfo.Index}"] button`) as HTMLElement;
        newButton?.focus();
    } catch (error) {
        console.error(error);
        alert((error as Error).message);
    } finally {
        isLoadingImage = false;
    }
  }
  
  function applyOpticalFlowResult() {
    if (!stabilizedRectsResult || !originalRectsFromDB) return;
    const stabilizedRects = stabilizedRectsResult.stabilizedRects;
    if (stabilizedRects.length !== originalRectsFromDB.length) {
        alert("The number of rectangles does not match. Cannot apply flow.");
        return;
    }
    const updatedRects = originalRectsFromDB.map((originalRect, index) => {
        const stabilizedRect = stabilizedRects[index];
        return {
            id: originalRect.id,
            x: stabilizedRect.x,
            y: stabilizedRect.y,
            width: stabilizedRect.width,
            height: stabilizedRect.height,
        };
    });
    rectangles = updatedRects;
    saveRectangles(rectangles);
  }
  
  function undoOpticalFlowResult() {
    rectangles = JSON.parse(JSON.stringify(originalRectsFromDB));
    saveRectangles(rectangles);
  }

  async function addNewRectangle() {
    if (!selectedDataNumberInfo) return;
    const defaultRect = { x: 10, y: 10, width: 100, height: 100 };
    try {
        const url = `/api/rectangles/by-ref/${selectedDataNumberInfo.Index}`;
        const response = await fetch(url, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(defaultRect),
        });
        if (!response.ok) throw new Error(await response.text());
        const newRectFromServer = await response.json();
        const finalNewRect = { ...defaultRect, id: newRectFromServer.id };
        rectangles = [...rectangles, finalNewRect];
        originalRectsFromDB = [...originalRectsFromDB, finalNewRect];
        selectedRectIds = [finalNewRect.id];
    } catch (error) {
        console.error(error);
        alert((error as Error).message);
    }
  }

  async function saveRectangles(rects: Rectangle[]) {
    if (!rects || rects.length === 0) return;
    const url = `/api/rectangles/bulk-update`; 
    try {
      const response = await fetch(url, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(rects),
      });
      if (!response.ok) throw new Error('Failed to save rectangles');
    } catch (error) {
      console.error(error);
      alert((error as Error).message);
    }
  }

  async function deleteSelectedRect() {
    if (selectedRectIds.length === 0) return;
    const idsToDelete = [...selectedRectIds];
    rectangles = rectangles.filter(r => !idsToDelete.includes(r.id));
    selectedRectIds = [];
    try {
      await Promise.all(
        idsToDelete.map(id =>
          fetch(`/api/rectangles/by-id/${id}`, { method: 'DELETE' }).then(res => {
            if (!res.ok) throw new Error(`Failed to delete rectangle ${id}`);
          })
        )
      );
    } catch (error) {
      console.error(error);
    }
  }

  function copySelectedRect() {
    if (selectedRectangles.length === 0) {
      clipboard = null;
      return;
    }
    clipboard = selectedRectangles.map(({ id, ...rest }) => rest);
  }

  async function pasteRect() {
    if (!clipboard || clipboard.length === 0 || !selectedDataNumberInfo) return;
    const newRectsData = clipboard.map(rect => ({
      x: rect.x + 10,
      y: rect.y + 10,
      width: rect.width,
      height: rect.height,
    }));
    try {
      const url = `/api/rectangles/by-ref/${selectedDataNumberInfo.Index}`;
      const createdRectsFromServer = await Promise.all(
        newRectsData.map(rectData =>
          fetch(url, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(rectData),
          }).then(res => res.ok ? res.json() : Promise.reject('Failed to create'))
        )
      );
      const finalNewRects = newRectsData.map((rectData, index) => ({
        ...rectData,
        id: createdRectsFromServer[index].id,
      }));
      rectangles = [...rectangles, ...finalNewRects];
      selectedRectIds = finalNewRects.map(r => r.id);
    } catch (error) {
      console.error(error);
      alert((error as Error).message);
    }
  }

  function getMousePosition(event: MouseEvent) {
    if (!imageElement || !svgElement) return { x: 0, y: 0 };
    const svgRect = svgElement.getBoundingClientRect();
    const { naturalWidth, naturalHeight, clientWidth, clientHeight } = imageElement;
    const scale = Math.min(clientWidth / naturalWidth, clientHeight / naturalHeight);
    const renderedWidth = naturalWidth * scale;
    const renderedHeight = naturalHeight * scale;
    const offsetX = (clientWidth - renderedWidth) / 2;
    const offsetY = (clientHeight - renderedHeight) / 2;
    const mouseX = (event.clientX - svgRect.left - offsetX) / scale;
    const mouseY = (event.clientY - svgRect.top - offsetY) / scale;
    return { x: mouseX, y: mouseY };
  }

  function handleInteractionStart(event: MouseEvent, rect: Rectangle, type: 'move' | 'resize', handle: string) {
    event.stopPropagation();
    const alreadySelected = selectedRectIds.includes(rect.id);
    if (event.shiftKey) {
        selectedRectIds = alreadySelected ? selectedRectIds.filter(id => id !== rect.id) : [...selectedRectIds, rect.id];
    } else if (!alreadySelected) {
        selectedRectIds = [rect.id];
    }
    if (type === 'resize' && selectedRectIds.length > 1) return;
    const pos = getMousePosition(event);
    activeInteraction = {
      type,
      handle,
      startX: pos.x,
      startY: pos.y,
      initialRects: rectangles
        .filter(r => selectedRectIds.includes(r.id))
        .map(r => ({ ...r })),
    };
    window.addEventListener('mousemove', handleMouseMove);
    window.addEventListener('mouseup', handleMouseUp);
  }

  function handleMouseMove(event: MouseEvent) {
    if (!activeInteraction?.initialRects?.length) return;
    const pos = getMousePosition(event);
    const dx = pos.x - activeInteraction.startX;
    const dy = pos.y - activeInteraction.startY;
    if (activeInteraction.type === 'resize') {
      const initialRect = activeInteraction.initialRects[0];
      const i = rectangles.findIndex(r => r.id === initialRect.id);
      if (i === -1) return;
      const { handle } = activeInteraction;
      let { x, y, width, height } = initialRect;
      if (handle!.includes('e')) width += dx;
      if (handle!.includes('w')) { width -= dx; x += dx; }
      if (handle!.includes('s')) height += dy;
      if (handle!.includes('n')) { height -= dy; y += dy; }
      rectangles[i] = { ...rectangles[i], x, y, width: Math.max(10, width), height: Math.max(10, height) };
    } else if (activeInteraction.type === 'move') {
      activeInteraction.initialRects.forEach(initialRect => {
        const i = rectangles.findIndex(r => r.id === initialRect.id);
        if (i !== -1) {
            rectangles[i] = { ...rectangles[i], x: initialRect.x + dx, y: initialRect.y + dy };
        }
      });
    }
  }

  function handleMouseUp() {
    if (activeInteraction) saveRectangles(rectangles);
    activeInteraction = null;
    window.removeEventListener('mousemove', handleMouseMove);
    window.removeEventListener('mouseup', handleMouseUp);
  }

  function deselectRect(event: Event) {
    if (event.target === svgElement) selectedRectIds = [];
  }

  function handleKeyDown(event: KeyboardEvent) {
    if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement) return;
    if (event.ctrlKey || event.metaKey) {
        if (event.key.toLowerCase() === 'c') { event.preventDefault(); copySelectedRect(); return; }
        if (event.key.toLowerCase() === 'v') { event.preventDefault(); pasteRect(); return; }
    }
    if (!selectedDataNumberInfo || dataNumbers.length <= 1) return;
    const currentIndex = dataNumbers.findIndex(d => d.Index === selectedDataNumberInfo!.Index);
    if (currentIndex === -1) return;
    let nextIndex = -1;
    const keyMap = { ArrowDown: 1, ArrowRight: 1, PageDown: 1, ArrowUp: -1, ArrowLeft: -1, PageUp: -1 };
    const step = keyMap[event.key as keyof typeof keyMap];
    if (step) {
        event.preventDefault();
        const newIndex = currentIndex + step;
        if (newIndex >= 0 && newIndex < dataNumbers.length) {
            selectDataNumber(dataNumbers[newIndex]);
        }
    }
  }

  const handles = ['n', 's', 'e', 'w', 'nw', 'ne', 'sw', 'se'];


  // --- Auto Flow 버튼용 함수 (완전 통합 버전) ---
  async function autoApplyFlowAndSave() {
      if (!selectedDataNumberInfo) {
          alert("현재 선택된 데이터 번호가 없습니다.");
          return;
      }

      if (!prevRectsForDisplay || prevRectsForDisplay.length === 0) {
          alert("이전 프레임 사각형이 없습니다.");
          return;
      }

      try {
          // 1️⃣ 이전 프레임 사각형만큼 Add
          const newRectsData = prevRectsForDisplay.map(rect => ({
              x: rect.x,
              y: rect.y,
              width: rect.width,
              height: rect.height
          }));

          const url = `/api/rectangles/by-ref/${selectedDataNumberInfo.Index}`;
          const createdRectsFromServer = await Promise.all(
              newRectsData.map(rectData =>
                  fetch(url, {
                      method: 'POST',
                      headers: { 'Content-Type': 'application/json' },
                      body: JSON.stringify(rectData),
                  }).then(res => res.ok ? res.json() : Promise.reject('Failed to create'))
              )
          );

          const finalNewRects = newRectsData.map((rectData, index) => ({
              ...rectData,
              id: createdRectsFromServer[index].id,
          }));

          rectangles = [...rectangles, ...finalNewRects];
          originalRectsFromDB = [...originalRectsFromDB, ...finalNewRects];
          selectedRectIds = finalNewRects.map(r => r.id);

          // 2️⃣ Optical Flow 자동 계산
          if (prevRectsForDisplay.length > 0 && isCvReady) {
              const prevImgSrc = selectedDataNumberInfo?.LocationFile ? `/api/images/${selectedDataNumberInfo.LocationFile}` : null;
              const currentImgSrc = `/api/images/${selectedDataNumberInfo.LocationFile}`;
              if (prevImgSrc) {
                  stabilizedRectsResult = await calculateOpticalFlow(prevImgSrc, currentImgSrc, prevRectsForDisplay);
                  applyOpticalFlowResult();
              }
          }

          // 3️⃣ DB 저장
          await saveRectangles(rectangles);

          //alert("Auto Flow 적용 완료 ✅");
      } catch (error) {
          console.error(error);
          alert(`Auto Flow 중 오류 발생 ❌: ${(error as Error).message}`);
      }
  }

  async function autoApplyFlowLikeButton() {
    if (!selectedDataNumberInfo) {
      alert("현재 선택된 데이터 번호가 없습니다.");
      return;
    }

    if (!prevRectsForDisplay || prevRectsForDisplay.length === 0) {
      alert("이전 프레임 사각형이 없습니다.");
      return;
    }

    try {
      const url = `/api/rectangles/by-ref/${selectedDataNumberInfo.Index}`;
      const finalNewRects = [];

      // 1️⃣ 이전 프레임 사각형을 순차적으로 DB에 저장
      for (const rect of prevRectsForDisplay) {
        const rectData = {
          x: rect.x,
          y: rect.y,
          width: rect.width,
          height: rect.height,
        };

        const res = await fetch(url, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(rectData),
        });

        if (!res.ok) throw new Error("사각형 생성 실패");
        const created = await res.json();
        finalNewRects.push({ ...rectData, id: created.id });
      }

      // 2️⃣ UI 배열 갱신
      rectangles = [...rectangles, ...finalNewRects];
      originalRectsFromDB = [...originalRectsFromDB, ...finalNewRects];
      selectedRectIds = finalNewRects.map((r) => r.id);

      // 3️⃣ tick()을 이용해 Svelte DOM 업데이트 완료 대기
      await tick();

      const applyFlowButton =
        document.querySelector('button[title="Apply Flow"]') || document.getElementById('apply-flow-btn');

      // 4️⃣ 실제 Apply Flow 버튼 클릭 이벤트 트리거
      //const applyFlowButton = document.getElementById('apply-flow-btn');
      if (applyFlowButton) {
        console.log('✅ Apply Flow 버튼 클릭 시도 중...');
        applyFlowButton.click();
        console.log('✅ Apply Flow 버튼을 자동으로 클릭했습니다.');
      } else {
        console.warn('⚠️ Apply Flow 버튼을 찾을 수 없습니다.');
      }

      // 5️⃣ 전체 DB 저장
      await saveRectangles(rectangles);

      //alert("✅ Auto Flow 적용 완료 (실제 버튼 클릭과 동일)");
    } catch (error) {
      console.error(error);
      alert(`❌ Auto Flow 중 오류 발생: ${(error as Error).message}`);
    }
  }


</script>

<svelte:window on:keydown={handleKeyDown} />

<div class="flex flex-col landscape:flex-row w-full h-full overflow-hidden bg-gray-100">
  
  <div class="w-full flex-shrink-0 flex flex-row landscape:flex-col lg:flex-col gap-2 p-2 
              h-[25vh] landscape:h-full 
              landscape:w-[30%] landscape:max-w-xs
              lg:w-[15%] lg:min-w-[200px] lg:max-w-xs">

    <div class="w-1/2 landscape:w-full lg:w-full h-full landscape:h-1/2 lg:h-1/2 flex flex-col gap-1">
      <h2 class="flex-shrink-0 text-sm font-bold text-gray-600 uppercase tracking-wider px-1">Movie Index</h2>
      <div class="flex-shrink-0 px-1 py-1 bg-gray-100 rounded-md flex justify-around gap-1">
        <label class="flex-1 text-center text-sm px-2 py-1 rounded-md cursor-pointer transition-colors {movieFilter === 'my' ? 'bg-blue-600 text-white shadow' : 'text-gray-600 hover:bg-gray-200'}">
          <input
            type="radio"
            bind:group={movieFilter}
            value="my"
            class="sr-only"
          />
          나 (My)
        </label>
        <label class="flex-1 text-center text-sm px-2 py-1 rounded-md cursor-pointer transition-colors {movieFilter === 'all' ? 'bg-blue-600 text-white shadow' : 'text-gray-600 hover:bg-gray-200'}">
          <input
            type="radio"
            bind:group={movieFilter}
            value="all"
            class="sr-only"
          />
          전체 (All)
        </label>
      </div>

      <div class="nav-section border bg-white border-gray-200 rounded-lg p-2 flex-grow overflow-y-auto min-h-0">
        <ul class="space-y-1">
          {#if displayedMovies && displayedMovies.length > 0}
            {#each displayedMovies as movie}
              <li>
                <button
                  on:click={() => selectMovie(movie)}
                  class="w-full text-left px-3 py-2 rounded-md text-gray-700 transition-colors duration-150 text-sm"
                  class:bg-blue-500={selectedMovie === movie}
                  class:text-white={selectedMovie === movie}
                  class:hover:bg-gray-100={selectedMovie !== movie}
                >
                  {movie}
                </button>
              </li>
            {/each}
          {:else if data.error}
            <p class="text-sm text-red-500 px-3">{data.error}</p>
          {:else if movieFilter === 'my'}
             <p class="text-sm text-gray-500 px-3">내 작업 목록이 없습니다.</p>
          {:else}
            <p class="text-sm text-gray-500 px-3">전체 영화 목록이 없습니다.</p>
          {/if}
        </ul>
      </div>
    </div>

    <div class="w-1/2 landscape:w-full lg:w-full h-full landscape:h-1/2 lg:h-1/2 flex flex-col gap-1">
      <h2 class="flex-shrink-0 text-sm font-bold text-gray-600 uppercase tracking-wider px-1">Data Number</h2>
      <div bind:this={dataNumberPanel} class="nav-section border bg-gray-50 border-gray-200 rounded-lg p-2 flex-grow overflow-y-auto min-h-0">
        {#if selectedMovie}
          {#if isLoadingDataNumbers}
            <p class="text-sm text-gray-500 px-3">Loading...</p>
          {:else if dataNumbers.length > 0}
            <ul class="space-y-1">
              {#each dataNumbers as dataInfo (dataInfo.Index)}
                <li data-index={dataInfo.Index}>
                  <button
                    on:click={() => selectDataNumber(dataInfo)}
                    class="w-full text-left px-3 py-2 rounded-md text-gray-700 transition-colors duration-150 text-sm"
                    class:bg-green-500={selectedDataNumberInfo?.Index === dataInfo.Index}
                    class:text-white={selectedDataNumberInfo?.Index === dataInfo.Index}
                    class:hover:bg-gray-100={selectedDataNumberInfo?.Index !== dataInfo.Index}
                  >
                    {dataInfo.DataNumber}
                  </button>
                </li>
              {/each}
            </ul>
          {:else}
            <p class="text-sm text-gray-500 px-3">No data found.</p>
          {/if}
        {:else}
          <div class="text-center text-gray-400 text-sm mt-4">
            <p>Select a Movie Index.</p>
          </div>
        {/if}
      </div>
    </div>
  </div>


  <div class="w-full flex flex-col bg-gray-200 flex-grow min-h-0">
    <div class="flex-shrink-0 p-2 bg-white border-b border-gray-300 shadow-sm flex flex-col md:flex-row md:items-center md:justify-between gap-2">
        <div class="flex items-center gap-1.5 flex-wrap justify-center md:justify-start">
            {#each crudActions as action}
              <button 
                on:click={action.onClick} 
                disabled={action.disabled}
                title={action.label}
                class="p-2 text-white rounded-md disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors {action.color}"
              >
                <Icon icon={action.icon} width="20" height="20" />
              </button>
            {/each}
            <div class="flex items-center gap-1.5 border-l-2 border-gray-200 ml-2 pl-2">
              {#each flowActions as action}
                <button 
                  on:click={action.onClick} 
                  disabled={action.disabled}
                  title={action.label}
                  class="p-2 text-white rounded-md disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors {action.color}"
                >
                  <Icon icon={action.icon} width="20" height="20" />
                </button>
              {/each}

              <!-- 구분선 -->
              <div class="border-l border-gray-300 h-6 mx-2"></div>

              <!-- Auto Flow 버튼 -->
              <button
                  on:click={autoApplyFlowLikeButton}
                  title="Auto Flow"
                  class="p-2 bg-pink-600 hover:bg-pink-700 text-white rounded-md transition-colors"
              >
                  Auto Flow
              </button>
            </div>
        </div>
        <div class="flex items-center justify-center md:justify-end gap-4 text-sm text-gray-600 pr-2">
          <label class="flex items-center gap-1.5 cursor-pointer">
            <input type="checkbox" bind:checked={showOpticalFlowVectors} class="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500" />
            <span>Flow</span>
          </label>
          <label class="flex items-center gap-1.5 cursor-pointer">
            <input type="checkbox" bind:checked={showPrevRects} class="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" />
            <span>Prev</span>
          </label>
          <label class="flex items-center gap-1.5 cursor-pointer">
            <input type="checkbox" bind:checked={showCurrentRects} class="h-4 w-4 rounded border-gray-300 text-yellow-500 focus:ring-yellow-400" />
            <span>Current</span>
          </label>
        </div>
    </div>
    <div class="flex-grow p-4 lg:p-6 overflow-auto">
      {#if !selectedDataNumberInfo}
        <div class="h-full flex justify-center items-center text-gray-500"><p>Select a data number to view the image.</p></div>
      {:else}
        <div class="relative w-full h-full shadow-lg bg-white image-viewer-container">
          {#if !imageHasError}
              <img 
                bind:this={imageElement} 
                src={`/api/images/${selectedDataNumberInfo.LocationFile}`} 
                alt={`Data ${selectedDataNumberInfo.DataNumber}`} 
                class="block w-full h-full object-contain" 
                on:error={() => imageHasError = true} 
              />
          {/if}
          <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
          <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
          <svg 
            bind:this={svgElement} 
            viewBox={viewBox}  
            class="absolute top-0 left-0 w-full h-full" 
            on:mousedown|self={deselectRect} 
            tabindex="0"
            on:keydown={(e) => { if (e.key === 'Escape') deselectRect(e); }}
            role="region" 
            aria-label="Image annotation area">
            
            {#if !imageHasError}
              {#if showPrevRects}
                {#each prevRectsForDisplay as rect (rect.id)}
                  <rect
                    x={rect.x} y={rect.y} width={rect.width} height={rect.height}
                    class="fill-transparent stroke-blue-500 stroke-2"
                    style="stroke-dasharray: 6 3;"
                  />
                {/each}
              {/if}
              {#if showCurrentRects}
                {#each rectangles as rect (rect.id)}
                  {@const isSelected = selectedRectIds.includes(rect.id)}
                  <rect
                    x={rect.x} y={rect.y} width={rect.width} height={rect.height}
                    role="button"
                    tabindex="0"
                    aria-label={`Annotation ${rect.id}`}
                    class="fill-yellow-400/20 stroke-yellow-400 stroke-2"
                    class:selected={isSelected} class:cursor-move={isSelected}
                    on:mousedown|stopPropagation={(e) => handleInteractionStart(e, rect, 'move', 'body')}
                  />
                  {#if selectedRectangles.length === 1 && selectedRectangles[0].id === rect.id}
                    {#each handles as handle}
                      {@const handleX = handle.includes('w') ? rect.x : (handle.includes('e') ? rect.x + rect.width : rect.x + rect.width / 2)}
                      {@const handleY = handle.includes('n') ? rect.y : (handle.includes('s') ? rect.y + rect.height : rect.y + rect.height / 2)}
                      <rect
                        x={handleX - 4} y={handleY - 4} width="8" height="8"
                        role="button"
                        tabindex="0"
                        aria-label={`Resize handle ${handle}`}
                        class="fill-white stroke-blue-600 stroke-2 cursor-{handle}-resize"
                        on:mousedown|stopPropagation={(e) => handleInteractionStart(e, rect, 'resize', handle)}
                      />
                    {/each}
                  {/if}
                {/each}
              {/if}
            {/if}
          </svg>
          <canvas bind:this={opticalFlowCanvas} class="absolute top-0 left-0 w-full h-full pointer-events-none"></canvas>
        </div>
      {/if}
    </div>
  </div>
</div>