<script lang="ts">
  import type { PageData } from './$types';
  import { tick, onMount } from '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;

  let rectanglesOpticalFlow: Rectangle[] = [];

  // --- OpenCV 상태 변수 ---
  let isCvReady = false;
  let opticalFlowCanvas: HTMLCanvasElement;
  let showOpticalFlow = true;

  // --- 상호작용 상태 변수 ---
  let selectedRectIds: number[] = []; 
  let imageElement: HTMLImageElement;
  let svgElement: SVGSVGElement;
  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 viewBox = '0 0 100 100'; // 기본 viewBox 값

  let selectedRectangles: Rectangle[] = [];

  
  $: if (imageElement) {
    // 이미지가 로드되기를 기다립니다.
    const updateViewBox = () => {
      if (imageElement.naturalWidth > 0 && imageElement.naturalHeight > 0) {
        viewBox = `0 0 ${imageElement.naturalWidth} ${imageElement.naturalHeight}`;
        console.log('SVG viewBox updated:', viewBox);
      }
    };

    // 이미지가 이미 로드된 경우 즉시 업데이트, 아니면 onload 이벤트를 기다립니다.
    if (imageElement.complete) {
      updateViewBox();
    } else {
      imageElement.onload = updateViewBox;
    }
  }

  
  
  $: selectedRectangles = rectangles.filter(r => selectedRectIds.includes(r.id));


  $: 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' });
            }
        }
    }
  }



  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);
  });

  async function calculateAndApplyOpticalFlow(prevImgSrc: string, currentImgSrc: string, prevRects: Rectangle[]): Promise<Rectangle[]> {
    if (!isCvReady) return rectangles;

    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[] = [];
        const flowLines: { p1: any; p2: any }[] = [];
        
        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);

        for (const prevRect of prevRects) {
          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, 100, 0.01, 5);
          if (points1.rows === 0) {
            stabilizedRects.push({ ...prevRect });
            roiGray1.delete(); points1.delete(); points2.delete(); status.delete(); err.delete();
            continue; 
          }
          for(let i = 0; i < points1.rows; i++) {
              points1.data32F[i * 2] += prevRect.x;
              points1.data32F[i * 2 + 1] += prevRect.y;
          }
          cv.calcOpticalFlowPyrLK(gray1, gray2, points1, points2, status, err);
          let avg_dx = 0, 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]);
                  flowLines.push({ p1, p2 });
                  avg_dx += (p2.x - p1.x);
                  avg_dy += (p2.y - p1.y);
                  tracked_count++;
              }
          }
          if (tracked_count > 0) { avg_dx /= tracked_count; avg_dy /= tracked_count; }
          stabilizedRects.push({ ...prevRect, x: prevRect.x + avg_dx, y: prevRect.y + avg_dy });
          points1.delete(); points2.delete(); status.delete(); err.delete(); roiGray1.delete();
        }
        
        const ctx = opticalFlowCanvas.getContext('2d');
        if (ctx) {
            const canvasWidth = opticalFlowCanvas.clientWidth;
            const canvasHeight = opticalFlowCanvas.clientHeight;
            opticalFlowCanvas.width = canvasWidth;
            opticalFlowCanvas.height = canvasHeight;
            ctx.clearRect(0, 0, canvasWidth, canvasHeight);

            // 스케일 및 여백 계산 (기존과 동일)
            const imgNaturalWidth = currentImg.naturalWidth;
            const imgNaturalHeight = currentImg.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;

            // 1. 옵티컬 플로우 화살표 그리기 (기존과 동일)
            ctx.lineWidth = 2;
            ctx.strokeStyle = 'rgba(255, 255, 0, 0.8)'; // 화살표는 노란색으로 변경하여 가독성 확보
            ctx.fillStyle = 'rgba(255, 255, 0, 0.8)';
            const headlen = 5; 
            flowLines.forEach(line => {
                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) < 1 && Math.abs(startY - endY) < 1) 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();
            });

            // 2. 안정화된 위치에 초록색 사각형 오버레이 그리기
            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.fillStyle = 'rgba(0, 255, 0, 0.25)'; // 반투명 녹색
                ctx.strokeStyle = 'rgba(0, 255, 0, 0.9)'; // 진한 녹색 테두리
                ctx.lineWidth = 2;

                ctx.fillRect(rectX, rectY, rectWidth, rectHeight);
                ctx.strokeRect(rectX, rectY, rectWidth, rectHeight);
            });
        }

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

    } catch (error) {
        console.error("Optical Flow 이미지 로딩 또는 처리 중 에러:", error);
        return rectangles;
    }
  }

  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;

    if(rectanglesOpticalFlow.length===0) {
        rectanglesOpticalFlow = prevRects;
    }

    isLoadingImage = true;
    selectedRectIds = [];

    if (opticalFlowCanvas) {
        const ctx = opticalFlowCanvas.getContext('2d');
        ctx?.clearRect(0, 0, opticalFlowCanvas.width, opticalFlowCanvas.height);
    }

    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();
        
        selectedDataNumberInfo = dataInfo;
        await tick();
        
        const conditions = {
            "1. 이전 이미지 경로 존재 여부": !!prevImgSrc,
            "2. 이전 사각형 존재 여부": prevRects.length > 0,
            "3. OpenCV 준비 여부": isCvReady
        };

        if (conditions["1. 이전 이미지 경로 존재 여부"] && conditions["2. 이전 사각형 존재 여부"] && conditions["3. OpenCV 준비 여부"]) {
            const stabilizedRects = await calculateAndApplyOpticalFlow(prevImgSrc!, newImgSrc, prevRects);
            //const stabilizedRects = await calculateAndApplyOpticalFlow(prevImgSrc!, newImgSrc, rectanglesOpticalFlow);
            
            //rectangles = stabilizedRects;
            rectangles = newRectsFromAPI;
        } else {
            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;
    }
  }

  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) {
            const errorBody = await response.text();
            throw new Error(`Failed to create rectangle on server: ${errorBody}`);
        }
        const newRectFromServer = await response.json();
        const finalNewRect = { ...defaultRect, id: newRectFromServer.id };
        rectangles = [...rectangles, finalNewRect];
        selectedRectIds = [finalNewRect.id];
    } catch (error) {
        console.error(error);
        alert((error as Error).message);
    }
  }

  async function saveRectangle(rect: Rectangle) {
    const url = `/api/rectangles/by-id/${rect.id}`;
    try {
      const response = await fetch(url, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(rect),
      });
      if (!response.ok) throw new Error('Failed to save rectangle');
    } 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(async response => {
            if (!response.ok) {
              throw new Error(`Failed to create rectangle on server`);
            }
            return response.json();
          })
        )
      );
      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 };
    
    // SVG 요소의 화면상 크기와 위치를 가져옵니다.
    const svgRect = svgElement.getBoundingClientRect();
    
    // imageElement는 실제 렌더링된 이미지 DOM 요소입니다.
    const { naturalWidth, naturalHeight, clientWidth, clientHeight } = imageElement;

    // object-contain으로 인해 계산된 스케일과 여백을 구합니다.
    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;
    
    // 마우스 좌표(event.clientX, Y)를 이미지의 원본 좌표계로 변환합니다.
    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) {
        if (alreadySelected) {
            selectedRectIds = selectedRectIds.filter(id => id !== rect.id);
        } else {
            selectedRectIds = [...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 || !activeInteraction.initialRects || activeInteraction.initialRects.length === 0) 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; }
      if (width < 10) width = 10;
      if (height < 10) height = 10;
      rectangles[i] = { ...rectangles[i], x, y, width, 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 && activeInteraction.initialRects) {
      const rectsToSave = rectangles.filter(r =>
        activeInteraction!.initialRects!.some(ir => ir.id === r.id)
      );
    }
    activeInteraction = null;
    window.removeEventListener('mousemove', handleMouseMove);
    window.removeEventListener('mouseup', handleMouseUp);
  }

  function deselectRect(event: MouseEvent) {
    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;
    switch (event.key) {
        case 'ArrowDown':
        case 'ArrowRight':
        case 'PageDown':
            event.preventDefault();
            if (currentIndex < dataNumbers.length - 1) nextIndex = currentIndex + 1;
            break;
        case 'ArrowUp':
        case 'ArrowLeft':
        case 'PageUp':
            event.preventDefault();
            if (currentIndex > 0) nextIndex = currentIndex - 1;
            break;
    }
    if (nextIndex !== -1) selectDataNumber(dataNumbers[nextIndex]);
  }

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

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

<div class="flex w-full h-full">
  <nav class="w-[15%] min-w-[200px] bg-white p-4 border-r border-gray-200 overflow-y-auto">
    <div class="nav-section border border-gray-200 rounded-lg p-3">
      <h2 class="text-sm font-bold text-gray-600 uppercase tracking-wider mb-2 px-1">Movie Index</h2>
      <ul class="space-y-1">
        {#if data.movies && data.movies.length > 0}
          {#each data.movies 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"
                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}
          <p class="text-sm text-gray-500 px-3">No movies found.</p>
        {/if}
      </ul>
    </div>
  </nav>

  <div bind:this={dataNumberPanel} class="w-[15%] min-w-[200px] bg-gray-50 p-4 border-r border-gray-200 overflow-y-auto">
    {#if selectedMovie}
      <div class="nav-section border border-gray-200 rounded-lg p-3 bg-white">
        <h2 class="text-sm font-bold text-gray-600 uppercase tracking-wider mb-2 px-1">Data Number</h2>
        {#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"
                  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}
      </div>
    {:else}
      <div class="text-center text-gray-400 text-sm mt-4">
        <p>Select a Movie Index to see data numbers.</p>
      </div>
    {/if}
  </div>

  <div class="w-[70%] flex flex-col bg-gray-200">
    <div class="flex-shrink-0 p-2 bg-white border-b border-gray-300 shadow-sm flex items-center gap-4">
        <div>
            <button on:click={addNewRectangle} disabled={!selectedDataNumberInfo}
              class="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed">
              Add Rectangle
            </button>
            <button on:click={copySelectedRect} disabled={selectedRectangles.length === 0}
              class="ml-2 px-4 py-2 text-sm font-medium text-white bg-indigo-600 rounded-md hover:bg-indigo-700 disabled:bg-gray-400 disabled:cursor-not-allowed">
              Copy
            </button>
            <button on:click={pasteRect} disabled={!clipboard || clipboard.length === 0}
              class="ml-2 px-4 py-2 text-sm font-medium text-white bg-purple-600 rounded-md hover:bg-purple-700 disabled:bg-gray-400 disabled:cursor-not-allowed">
              Paste
            </button>
            <button on:click={deleteSelectedRect} disabled={selectedRectangles.length === 0}
              class="ml-2 px-4 py-2 text-sm font-medium text-white bg-red-600 rounded-md hover:bg-red-700 disabled:bg-gray-400 disabled:cursor-not-allowed">
              Delete Selected
            </button>
        </div>
        {#if selectedRectangles.length === 1}
            {@const selectedRectangle = selectedRectangles[0]}
            <div class="text-xs text-gray-600 border-l-2 border-gray-300 pl-4 flex items-center gap-3">
                <span class="font-mono"><strong class="font-bold text-gray-800">ID:</strong> {selectedRectangle.id}</span>
                <span class="font-mono"><strong class="font-bold text-gray-800">X1:</strong> {Math.round(selectedRectangle.x)}</span>
                <span class="font-mono"><strong class="font-bold text-gray-800">Y1:</strong> {Math.round(selectedRectangle.y)}</span>
                <span class="font-mono"><strong class="font-bold text-gray-800">X2:</strong> {Math.round(selectedRectangle.x + selectedRectangle.width)}</span>
                <span class="font-mono"><strong class="font-bold text-gray-800">Y2:</strong> {Math.round(selectedRectangle.y + selectedRectangle.height)}</span>
            </div>
        {/if}
    </div>

    <div class="flex-grow 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}
          
          <svg 
            bind:this={svgElement} 
            viewBox={viewBox}  
            class="absolute top-0 left-0 w-full h-full" 
            on:mousedown|self={deselectRect} 
            role="region" 
            aria-label="Image annotation area">
            {#if !imageHasError}
              {#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}
          </svg>

          <canvas bind:this={opticalFlowCanvas} class="absolute top-0 left-0 w-full h-full pointer-events-none"></canvas>
        </div>
      {/if}
    </div>
  </div>
</div>

<style>
  .image-viewer-container {
      user-select: none;
      -webkit-user-select: none;
      -moz-user-select: none;
      -ms-user-select: none;
    }
    rect.selected {
        stroke: #0ea5e9;
        stroke-width: 3px;
        stroke-dasharray: 6 3;
    }
    .cursor-n-resize { cursor: n-resize; }
    .cursor-s-resize { cursor: s-resize; }
    .cursor-e-resize { cursor: e-resize; }
    .cursor-w-resize { cursor: w-resize; }
    .cursor-nw-resize { cursor: nw-resize; }
    .cursor-ne-resize { cursor: ne-resize; }
    .cursor-sw-resize { cursor: sw-resize; }
    .cursor-se-resize { cursor: se-resize; }
</style>