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

  // --- 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 selectedRectangles: Rectangle[] = [];
  $: 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) {
        console.log("✅ OpenCV was already initialized.");
        isCvReady = true;
        return;
      }
      if (cv) {
        console.log("cv object found, setting onRuntimeInitialized callback.");
        cv.onRuntimeInitialized = () => {
          console.log("✅✅✅ OpenCV runtime initialized successfully!");
          isCvReady = true;
        };
      }
    };
    const intervalId = setInterval(() => {
      if (typeof cv !== 'undefined') {
        clearInterval(intervalId);
        initialize();
      } else {
        console.log("Waiting for cv object to appear...");
      }
    }, 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;

            ctx.lineWidth = 2;
            ctx.strokeStyle = 'rgba(0, 255, 0, 0.8)';
            flowLines.forEach(line => {
                ctx.beginPath();
                ctx.moveTo((line.p1.x * scale) + offsetX, (line.p1.y * scale) + offsetY);
                ctx.lineTo((line.p2.x * scale) + offsetX, (line.p2.y * scale) + offsetY);
                ctx.stroke();
            });
        }

        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;

    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);
            rectangles = stabilizedRects;
        } 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();
    
    // --- getMousePosition 수정: object-fit: contain 비율 계산 추가 ---
    // 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)
      );
      // 자동 저장은 비활성화
      // Promise.all(rectsToSave.map(rect => saveRectangle(rect)));
    }
    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">
    </nav>
  <div bind:this={dataNumberPanel} class="w-[15%] min-w-[200px] bg-gray-50 p-4 border-r border-gray-200 overflow-y-auto">
    </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>

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