<?php

namespace App\Services\Video;

use Illuminate\Support\Facades\Log;

class ScreenAnalysisService
{
    private GeminiScriptService $gemini;

    public function __construct(GeminiScriptService $gemini)
    {
        $this->gemini = $gemini;
    }

    /**
     * 스크린샷 배열을 Gemini Vision으로 분석
     *
     * @param  array  $imagePaths  스크린샷 파일 경로 배열
     * @return array 분석 결과 배열
     */
    public function analyzeScreenshots(array $imagePaths): array
    {
        $results = [];

        foreach ($imagePaths as $index => $imagePath) {
            $screenNumber = $index + 1;

            Log::info("ScreenAnalysis: 스크린샷 {$screenNumber}/".count($imagePaths).' 분석 시작', [
                'path' => $imagePath,
            ]);

            $result = $this->analyzeSingleScreen($imagePath, $screenNumber, count($imagePaths));

            if ($result) {
                $result = $this->ensureStepsFormat($result);
                $results[] = $result;
            } else {
                Log::warning("ScreenAnalysis: 스크린샷 {$screenNumber} 분석 실패, 기본값 사용");
                $results[] = [
                    'screen_number' => $screenNumber,
                    'title' => "화면 {$screenNumber}",
                    'steps' => [
                        [
                            'step_number' => 1,
                            'narration' => '이 화면에서는 주요 기능을 확인할 수 있습니다.',
                            'focused_element' => null,
                            'duration' => 6,
                        ],
                    ],
                ];
            }
        }

        return $results;
    }

    /**
     * 기존 형식(narration/ui_elements) → steps[] 형식으로 변환 (하위 호환)
     * Job에서 DB 캐시된 기존 형식 변환에도 사용
     */
    public function ensureStepsFormatPublic(array $screen): array
    {
        return $this->ensureStepsFormat($screen);
    }

    /**
     * 기존 형식(narration/ui_elements) → steps[] 형식으로 변환 (하위 호환)
     */
    private function ensureStepsFormat(array $screen): array
    {
        if (! empty($screen['steps'])) {
            return $screen;
        }

        $narration = $screen['narration'] ?? '이 화면의 기능을 확인할 수 있습니다.';
        $uiElements = $screen['ui_elements'] ?? [];
        $duration = $screen['duration'] ?? 8;

        if (empty($uiElements)) {
            $screen['steps'] = [
                [
                    'step_number' => 1,
                    'narration' => $narration,
                    'focused_element' => null,
                    'duration' => $duration,
                ],
            ];
        } else {
            $steps = [];
            $sentences = preg_split('/(?<=[.。])\s*/u', $narration, -1, PREG_SPLIT_NO_EMPTY);
            foreach ($uiElements as $i => $el) {
                $steps[] = [
                    'step_number' => $i + 1,
                    'narration' => $sentences[$i] ?? $narration,
                    'focused_element' => [
                        'type' => $el['type'] ?? 'other',
                        'label' => $el['label'] ?? '',
                        'x' => $el['x'] ?? 0.5,
                        'y' => $el['y'] ?? 0.5,
                        'w' => 0.2,
                        'h' => 0.15,
                    ],
                    'duration' => max(5, (int) ($duration / count($uiElements))),
                ];
            }
            $screen['steps'] = $steps;
        }

        unset($screen['narration'], $screen['ui_elements'], $screen['duration']);

        return $screen;
    }

    /**
     * 단일 스크린샷 분석
     */
    private function analyzeSingleScreen(string $imagePath, int $screenNumber, int $totalScreens): ?array
    {
        if (! file_exists($imagePath)) {
            Log::error("ScreenAnalysis: 파일 없음 - {$imagePath}");

            return null;
        }

        // 그리드 오버레이가 있는 이미지 생성
        $gridImagePath = $this->createGridOverlay($imagePath);
        $analyzeImagePath = $gridImagePath ?: $imagePath;

        $imageData = base64_encode(file_get_contents($analyzeImagePath));
        $mimeType = mime_content_type($analyzeImagePath) ?: 'image/png';

        $prompt = <<<PROMPT
당신은 SAM(Smart Automation Management) 시스템의 사용자 매뉴얼 작성 전문가입니다.

이 스크린샷은 SAM 시스템의 화면입니다. (화면 {$screenNumber}/{$totalScreens})

=== 이미지 레이아웃 안내 ===
- 1920x1080 캔버스에 원본 스크린샷이 **비율 보존**으로 배치되어 있습니다.
- 하단 180px는 캡션 영역(검은 바)이므로, 이미지 배치 영역은 1920x900입니다.
- 원본 비율에 따라 좌우 또는 상하에 **검은 여백(레터박스)**이 있을 수 있습니다.
- 좌표는 검은 여백을 제외한 **실제 이미지 콘텐츠 영역** 기준입니다.
- 하단 캡션 영역(검은 바)은 좌표 계산에서 **제외**됩니다.

=== 그리드 오버레이 안내 ===
이미지 콘텐츠 영역 위에 **20x12 그리드**가 표시되어 있습니다.
- 가로 20칸, 세로 12칸
- **5칸마다(가로) / 3칸마다(세로) 주요 그리드선**(굵은 선)이 있습니다.
- 주요 교차점에 "X좌표,Y좌표" 형식의 라벨이 표시됩니다.
- 좌측/상단 테두리에 0~1000 좌표 눈금이 표시됩니다.
- 가로 주요 눈금: 0, 250, 500, 750, 1000
- 세로 주요 눈금: 0, 250, 500, 750, 1000
이 그리드와 좌표 라벨을 참고하여 각 UI 영역의 정확한 위치를 파악하세요.

이 화면을 분석하고, 사용자가 따라할 수 있는 **3~5개 단계(steps)**로 나누어 튜토리얼을 작성하세요.
각 단계마다 화면에서 집중해야 할 UI 영역(focused_element)을 지정합니다.

=== 분석 요구사항 ===
1. 화면의 주요 목적/기능을 파악
2. 사용자가 수행할 작업을 3~5단계로 분해
3. 각 단계마다 집중할 UI 영역의 위치와 크기를 0~1000 정수 좌표로 표시
4. 단계별 나레이션은 독립적인 문장으로 작성

=== 나레이션 작성 규칙 ===
- 친근한 존댓말 사용 (예: "~하실 수 있습니다", "~을 클릭하세요")
- TTS로 읽을 것이므로 이모지/특수기호 금지
- 순수 한글 텍스트만 작성
- 문장은 마침표로 끝내기
- 각 단계 나레이션은 30~80자 (약 3~7초 분량)

반드시 아래 JSON 형식으로만 응답하세요:
{
  "screen_number": {$screenNumber},
  "title": "이 화면의 제목 (10자 이내)",
  "steps": [
    {
      "step_number": 1,
      "narration": "이 단계에서 할 일을 안내하는 나레이션 (30~80자)",
      "focused_element": {
        "type": "button|input|table|menu|tab|sidebar|header|form|other",
        "label": "UI 영역에 표시된 텍스트 (10자 이내)",
        "x": 100,
        "y": 300,
        "w": 200,
        "h": 400
      },
      // 주의: w, h는 해당 UI 요소의 실제 보이는 영역 전체를 넉넉히 감싸야 합니다.
      // 너무 작으면 스포트라이트가 일부만 강조하여 사용자가 혼란스러워합니다.
      "duration": 6
    }
  ]
}

=== focused_element 좌표 (0~1000 정수 좌표계) ===
좌표는 **이미지 콘텐츠 영역**(검은 여백/캡션 제외)의 좌상단(0,0) ~ 우하단(1000,1000) 범위의 **정수**입니다.
그리드 오버레이의 눈금과 교차점 라벨을 활용하여 정확한 좌표를 지정하세요.

- x: 영역 좌측 가장자리의 가로 위치 (0=왼쪽 끝, 1000=오른쪽 끝)
- y: 영역 상단 가장자리의 세로 위치 (0=위쪽 끝, 1000=아래쪽 끝)
- w: 영역의 너비 (0~1000)
- h: 영역의 높이 (0~1000)

=== 그리드 참조 가이드 (20x12 그리드) ===
- 가로 주요 눈금: 0, 250, 500, 750, 1000 (5칸 간격)
- 세로 주요 눈금: 0, 250, 500, 750, 1000 (3칸 간격)
- 1칸 = 가로 50, 세로 약 83 좌표 단위
- 예) 좌상단 1/4 영역 → x=0, y=0, w=250, h=250
- 예) 중앙 절반 영역 → x=250, y=250, w=500, h=500
- 예) 우하단 1/4 영역 → x=750, y=750, w=250, h=250

=== 좌표 정확도 예시 (ERP/관리 시스템 화면 기준) ===
- 왼쪽 사이드바 메뉴: x=0, y=50, w=150, h=950
- 상단 헤더/네비게이션: x=0, y=0, w=1000, h=60
- 메인 콘텐츠의 데이터 테이블: x=180, y=150, w=800, h=600
- 상단 검색/필터 영역: x=180, y=80, w=800, h=80
- 우측 상단 버튼 그룹: x=700, y=80, w=280, h=50
- 탭 메뉴: x=180, y=100, w=600, h=40
- 모달/팝업: x=250, y=200, w=500, h=500

=== UI 요소 타입별 최소 크기 가이드라인 ===
focused_element의 w, h는 해당 UI 요소가 화면에서 차지하는 실제 영역을 충분히 포함해야 합니다.
각 type별 최소 권장 크기입니다. 실제 요소가 더 크면 실제 크기에 맞추세요.

- button: 최소 w=100, h=50
- input: 최소 w=250, h=60 (검색창은 아이콘~버튼까지 전체 포함)
- form: 최소 w=400, h=200 (라벨+입력+버튼 전체)
- table: 최소 w=500, h=300 (헤더+본문 포함)
- tab: 최소 w=300, h=50 (탭 그룹 전체)
- menu/sidebar: 최소 w=150, h=400
- header: 최소 w=600, h=60
- other: 최소 w=150, h=80

중요:
- "기간을 설정하세요" → 시작일~종료일 input 2개 + 사이 텍스트 모두 포함
- "검색창을 클릭하세요" → 검색 아이콘부터 검색 버튼까지 전체 포함
- 하나의 논리적 UI 그룹은 분리하지 말고 하나의 focused_element로 잡으세요
- 요소 경계에서 약 10~20 단위의 여유를 두세요

=== duration 규칙 ===
- 각 단계는 5~8초 (나레이션 길이에 비례)
- 전체 steps의 duration 합계가 20~40초가 되도록 조절
PROMPT;

        $parts = [
            ['text' => $prompt],
            [
                'inlineData' => [
                    'mimeType' => $mimeType,
                    'data' => $imageData,
                ],
            ],
        ];

        $result = $this->gemini->callGeminiWithParts($parts, 0.3, 2048);

        // 그리드 이미지 정리
        if ($gridImagePath && file_exists($gridImagePath)) {
            @unlink($gridImagePath);
        }

        if (! $result) {
            return null;
        }

        $parsed = $this->gemini->parseJson($result);

        if (! $parsed || ! isset($parsed['screen_number'])) {
            Log::warning('ScreenAnalysis: JSON 파싱 실패 또는 형식 불일치', [
                'result' => substr($result, 0, 300),
            ]);

            return null;
        }

        // screen_number 강제 보정
        $parsed['screen_number'] = $screenNumber;

        // 좌표 검증 루프 (normalizeCoordinates 이전, 0~1000 좌표 상태에서 실행)
        $parsed = $this->runCoordinateVerification($imagePath, $parsed);

        // 0~1000 좌표 → 0~1 비율로 변환
        $this->normalizeCoordinates($parsed);

        return $parsed;
    }

    private const ANALYSIS_WIDTH = 1920;

    private const ANALYSIS_HEIGHT = 1080;

    private const CAPTION_HEIGHT = 180; // SlideAnnotationService::CAPTION_HEIGHT와 동일

    /**
     * 스크린샷에 그리드 오버레이 생성 (annotateSlideWithSpotlight와 동일한 레이아웃)
     *
     * Phase 1: 비율 보존 레터박스 배치 (종횡비 일치)
     * Phase 2: 20x12 고밀도 그리드 + 주요 그리드선 + 좌표 라벨
     */
    private function createGridOverlay(string $imagePath): ?string
    {
        try {
            $info = getimagesize($imagePath);
            if (! $info) {
                return null;
            }

            $source = match ($info[2]) {
                IMAGETYPE_PNG => imagecreatefrompng($imagePath),
                IMAGETYPE_JPEG => imagecreatefromjpeg($imagePath),
                IMAGETYPE_GIF => imagecreatefromgif($imagePath),
                IMAGETYPE_WEBP => imagecreatefromwebp($imagePath),
                default => null,
            };

            if (! $source) {
                return null;
            }

            $srcW = imagesx($source);
            $srcH = imagesy($source);

            // Phase 1: annotateSlideWithSpotlight()와 동일한 레이아웃
            // 1920x1080 캔버스, 하단 180px 캡션 제외 → 이미지 배치 영역: 1920x900
            $canvas = imagecreatetruecolor(self::ANALYSIS_WIDTH, self::ANALYSIS_HEIGHT);
            $black = imagecolorallocate($canvas, 0, 0, 0);
            imagefill($canvas, 0, 0, $black);

            $availH = self::ANALYSIS_HEIGHT - self::CAPTION_HEIGHT;
            $scale = min(self::ANALYSIS_WIDTH / $srcW, $availH / $srcH);
            $newW = (int) ($srcW * $scale);
            $newH = (int) ($srcH * $scale);
            $offsetX = (int) ((self::ANALYSIS_WIDTH - $newW) / 2);
            $offsetY = (int) (($availH - $newH) / 2);

            imagecopyresampled($canvas, $source, $offsetX, $offsetY, 0, 0, $newW, $newH, $srcW, $srcH);
            imagedestroy($source);

            Log::debug('ScreenAnalysis: 이미지 정규화 (비율 보존 레터박스)', [
                'original' => "{$srcW}x{$srcH}",
                'scaled' => "{$newW}x{$newH}",
                'offset' => "({$offsetX}, {$offsetY})",
                'availArea' => self::ANALYSIS_WIDTH.'x'.$availH,
            ]);

            // Phase 2: 20x12 고밀도 그리드 (이미지 콘텐츠 영역에만)
            $gridColor = imagecolorallocatealpha($canvas, 255, 0, 0, 110);
            $majorGridColor = imagecolorallocatealpha($canvas, 255, 0, 0, 90);
            $labelBg = imagecolorallocatealpha($canvas, 0, 0, 0, 60);
            $labelText = imagecolorallocate($canvas, 255, 255, 0);
            $tickColor = imagecolorallocate($canvas, 255, 80, 80);
            $captionColor = imagecolorallocate($canvas, 200, 200, 200);
            $captionBg = imagecolorallocatealpha($canvas, 0, 0, 0, 50);

            $cols = 20;
            $rows = 12;
            $cellW = $newW / $cols;
            $cellH = $newH / $rows;
            $majorColInterval = 5;  // 가로 5칸마다 주요선 → 좌표 0,250,500,750,1000
            $majorRowInterval = 3;  // 세로 3칸마다 주요선 → 좌표 0,250,500,750,1000

            // 세로선 (이미지 콘텐츠 영역 내)
            for ($i = 1; $i < $cols; $i++) {
                $x = $offsetX + (int) ($i * $cellW);
                $isMajor = ($i % $majorColInterval === 0);
                $color = $isMajor ? $majorGridColor : $gridColor;
                imageline($canvas, $x, $offsetY, $x, $offsetY + $newH, $color);
                if ($isMajor) {
                    imageline($canvas, $x + 1, $offsetY, $x + 1, $offsetY + $newH, $color);
                }
            }

            // 가로선 (이미지 콘텐츠 영역 내)
            for ($j = 1; $j < $rows; $j++) {
                $y = $offsetY + (int) ($j * $cellH);
                $isMajor = ($j % $majorRowInterval === 0);
                $color = $isMajor ? $majorGridColor : $gridColor;
                imageline($canvas, $offsetX, $y, $offsetX + $newW, $y, $color);
                if ($isMajor) {
                    imageline($canvas, $offsetX, $y + 1, $offsetX + $newW, $y + 1, $color);
                }
            }

            // 상단 라벨: 가로 좌표 눈금 (0~1000 좌표계)
            for ($i = 0; $i <= $cols; $i += $majorColInterval) {
                $x = $offsetX + (int) ($i * $cellW);
                $coordVal = (int) round($i * 1000 / $cols);
                $label = (string) $coordVal;
                $lx = max($offsetX, $x - 2);
                imagefilledrectangle($canvas, $lx, $offsetY, $lx + strlen($label) * 7 + 4, $offsetY + 14, $labelBg);
                imagestring($canvas, 2, $lx + 2, $offsetY + 1, $label, $labelText);
            }

            // 좌측 라벨: 세로 좌표 눈금 (0~1000 좌표계)
            for ($j = 0; $j <= $rows; $j += $majorRowInterval) {
                $y = $offsetY + (int) ($j * $cellH);
                $coordVal = (int) round($j * 1000 / $rows);
                $label = (string) $coordVal;
                $ly = max($offsetY, $y - 1);
                imagefilledrectangle($canvas, $offsetX, $ly, $offsetX + strlen($label) * 7 + 4, $ly + 14, $labelBg);
                imagestring($canvas, 2, $offsetX + 2, $ly + 1, $label, $labelText);
            }

            // 교차점 마커 (축소: 일반 2px, 주요 4px)
            for ($i = 1; $i < $cols; $i++) {
                for ($j = 1; $j < $rows; $j++) {
                    $cx = $offsetX + (int) ($i * $cellW);
                    $cy = $offsetY + (int) ($j * $cellH);
                    $isMajor = ($i % $majorColInterval === 0) && ($j % $majorRowInterval === 0);
                    $sz = $isMajor ? 4 : 2;
                    imageline($canvas, $cx - $sz, $cy, $cx + $sz, $cy, $tickColor);
                    imageline($canvas, $cx, $cy - $sz, $cx, $cy + $sz, $tickColor);
                }
            }

            // 주요 교차점에 좌표값 라벨 표시
            for ($i = $majorColInterval; $i < $cols; $i += $majorColInterval) {
                for ($j = $majorRowInterval; $j < $rows; $j += $majorRowInterval) {
                    $cx = $offsetX + (int) ($i * $cellW);
                    $cy = $offsetY + (int) ($j * $cellH);
                    $coordX = (int) round($i * 1000 / $cols);
                    $coordY = (int) round($j * 1000 / $rows);
                    $label = "{$coordX},{$coordY}";
                    $lw = strlen($label) * 6 + 6;
                    imagefilledrectangle($canvas, $cx + 3, $cy - 12, $cx + 3 + $lw, $cy + 2, $labelBg);
                    imagestring($canvas, 1, $cx + 5, $cy - 10, $label, $labelText);
                }
            }

            // 하단 캡션 영역에 좌표 기준 안내 텍스트
            $captionY = self::ANALYSIS_HEIGHT - self::CAPTION_HEIGHT + 10;
            $guideText = 'Coords: image content area (0,0)~(1000,1000) | Grid: 20x12 | Caption area excluded';
            imagefilledrectangle($canvas, 8, $captionY, strlen($guideText) * 7 + 16, $captionY + 16, $captionBg);
            imagestring($canvas, 2, 12, $captionY + 2, $guideText, $captionColor);

            $gridPath = $imagePath.'_grid.png';
            imagepng($canvas, $gridPath, 6);
            imagedestroy($canvas);

            Log::debug('ScreenAnalysis: 그리드 오버레이 생성 완료', ['path' => $gridPath]);

            return $gridPath;
        } catch (\Exception $e) {
            Log::warning('ScreenAnalysis: 그리드 오버레이 생성 실패', ['error' => $e->getMessage()]);

            return null;
        }
    }

    // UI 요소 타입별 최소 크기 (0~1 비율)
    private const MIN_ELEMENT_SIZES = [
        'button' => ['w' => 0.06, 'h' => 0.04],
        'input' => ['w' => 0.15, 'h' => 0.05],
        'form' => ['w' => 0.25, 'h' => 0.15],
        'table' => ['w' => 0.30, 'h' => 0.20],
        'tab' => ['w' => 0.20, 'h' => 0.05],
        'menu' => ['w' => 0.10, 'h' => 0.25],
        'sidebar' => ['w' => 0.10, 'h' => 0.40],
        'header' => ['w' => 0.40, 'h' => 0.06],
        'other' => ['w' => 0.08, 'h' => 0.06],
    ];

    private const SPOTLIGHT_PADDING = 0.015; // 전체 크기 대비 1.5% 패딩

    // 좌표 검증 루프용 색상 (step별)
    private const VERIFICATION_COLORS = [
        1 => [255, 60, 60],    // 빨강
        2 => [60, 200, 60],    // 초록
        3 => [60, 100, 255],   // 파랑
        4 => [255, 160, 0],    // 주황
        5 => [180, 60, 255],   // 보라
    ];

    /**
     * 0~1000 정수 좌표 → 0~1 비율로 변환 + 타입별 최소 크기 보정 + 패딩
     *
     * Gemini가 반환한 0~1000 좌표를 하류 파이프라인이 사용하는 0~1 비율로 정규화
     */
    public function normalizeCoordinates(array &$parsed): void
    {
        if (empty($parsed['steps'])) {
            return;
        }

        foreach ($parsed['steps'] as $stepIdx => &$step) {
            if (empty($step['focused_element'])) {
                continue;
            }

            $el = &$step['focused_element'];
            $stepNum = $step['step_number'] ?? ($stepIdx + 1);

            // Step 1: 0~1000 → 0~1 비율 변환
            $isMilliCoords = ($el['x'] ?? 0) > 1 || ($el['y'] ?? 0) > 1
                || ($el['w'] ?? 0) > 1 || ($el['h'] ?? 0) > 1;

            $rawX = $el['x'] ?? 0;
            $rawY = $el['y'] ?? 0;
            $rawW = $el['w'] ?? 200;
            $rawH = $el['h'] ?? 200;

            if ($isMilliCoords) {
                $el['x'] = round($rawX / 1000, 4);
                $el['y'] = round($rawY / 1000, 4);
                $el['w'] = round($rawW / 1000, 4);
                $el['h'] = round($rawH / 1000, 4);
            }

            Log::debug("ScreenAnalysis: 좌표 변환 Step {$stepNum} [1/4 비율변환]", [
                'raw' => "x={$rawX}, y={$rawY}, w={$rawW}, h={$rawH}",
                'normalized' => "x={$el['x']}, y={$el['y']}, w={$el['w']}, h={$el['h']}",
                'label' => $el['label'] ?? '',
            ]);

            // Step 2: 타입별 최소 크기 보정 (중심점 기준 확장)
            $type = $el['type'] ?? 'other';
            $minSize = self::MIN_ELEMENT_SIZES[$type] ?? self::MIN_ELEMENT_SIZES['other'];

            if ($el['w'] < $minSize['w']) {
                $centerX = $el['x'] + $el['w'] / 2;
                $el['w'] = $minSize['w'];
                $el['x'] = $centerX - $el['w'] / 2;
            }
            if ($el['h'] < $minSize['h']) {
                $centerY = $el['y'] + $el['h'] / 2;
                $el['h'] = $minSize['h'];
                $el['y'] = $centerY - $el['h'] / 2;
            }

            Log::debug("ScreenAnalysis: 좌표 변환 Step {$stepNum} [2/4 최소크기]", [
                'type' => $type,
                'after' => "x={$el['x']}, y={$el['y']}, w={$el['w']}, h={$el['h']}",
            ]);

            // Step 3: 패딩 추가 (경계 인접 시 편향 패딩)
            $pad = self::SPOTLIGHT_PADDING;
            $spaceLeft = max(0, $el['x']);
            $spaceRight = max(0, 1 - $el['x'] - $el['w']);
            $spaceTop = max(0, $el['y']);
            $spaceBottom = max(0, 1 - $el['y'] - $el['h']);

            $leftPad = min($pad, $spaceLeft);
            $rightPad = min($pad, $spaceRight);
            $topPad = min($pad, $spaceTop);
            $bottomPad = min($pad, $spaceBottom);

            // 부족분을 반대 방향으로 보상
            if ($leftPad < $pad) {
                $rightPad = min($spaceRight, $rightPad + ($pad - $leftPad));
            }
            if ($rightPad < $pad) {
                $leftPad = min($spaceLeft, $leftPad + ($pad - $rightPad));
            }
            if ($topPad < $pad) {
                $bottomPad = min($spaceBottom, $bottomPad + ($pad - $topPad));
            }
            if ($bottomPad < $pad) {
                $topPad = min($spaceTop, $topPad + ($pad - $bottomPad));
            }

            $el['x'] -= $leftPad;
            $el['y'] -= $topPad;
            $el['w'] += $leftPad + $rightPad;
            $el['h'] += $topPad + $bottomPad;

            Log::debug("ScreenAnalysis: 좌표 변환 Step {$stepNum} [3/4 패딩]", [
                'padding' => "L={$leftPad}, R={$rightPad}, T={$topPad}, B={$bottomPad}",
                'after' => "x={$el['x']}, y={$el['y']}, w={$el['w']}, h={$el['h']}",
            ]);

            // Step 4: 경계 클램핑 (0~1 범위 내로 수렴)
            $el['x'] = max(0, $el['x']);
            $el['y'] = max(0, $el['y']);
            if ($el['x'] + $el['w'] > 1) {
                $el['w'] = 1 - $el['x'];
            }
            if ($el['y'] + $el['h'] > 1) {
                $el['h'] = 1 - $el['y'];
            }
            $el['w'] = max($minSize['w'], $el['w']);
            $el['h'] = max($minSize['h'], $el['h']);

            Log::debug("ScreenAnalysis: 좌표 변환 Step {$stepNum} [4/4 최종]", [
                'final' => "x={$el['x']}, y={$el['y']}, w={$el['w']}, h={$el['h']}",
            ]);
        }
        unset($step, $el);
    }

    /**
     * 좌표 검증 루프 오케스트레이터
     *
     * 1-pass로 추정한 좌표를 원본 이미지 위에 렌더링한 뒤,
     * Gemini에게 2-pass로 위치 정확도를 확인/보정하게 한다.
     */
    private function runCoordinateVerification(string $imagePath, array $parsed): array
    {
        // focused_element가 있는 step만 추출
        $stepsWithElement = [];
        foreach ($parsed['steps'] ?? [] as $step) {
            if (! empty($step['focused_element'])) {
                $stepsWithElement[] = $step;
            }
        }

        if (empty($stepsWithElement)) {
            Log::debug('ScreenAnalysis: 좌표 검증 건너뜀 - focused_element 없음');

            return $parsed;
        }

        Log::info('ScreenAnalysis: 좌표 검증 루프 시작', [
            'screen' => $parsed['screen_number'] ?? '?',
            'steps_count' => count($stepsWithElement),
        ]);

        // 검증 이미지 생성
        $verificationImagePath = $this->createVerificationImage($imagePath, $stepsWithElement);
        if (! $verificationImagePath) {
            Log::warning('ScreenAnalysis: 검증 이미지 생성 실패, 원본 좌표 유지');

            return $parsed;
        }

        // Gemini 2-pass 검증 호출
        $verifiedSteps = $this->verifyCoordinates($verificationImagePath, $stepsWithElement);

        // 보정 적용 + 검증 메타데이터 저장
        $parsed = $this->applyVerifiedCoordinates($parsed, $verifiedSteps);

        // 3-pass: 크롭 검증
        $this->cropVerifyCoordinates($imagePath, $parsed);

        // 스크린 단위 검증 통계 저장 (2-pass + 3-pass 통합)
        $accurateCount = 0;
        $correctedCount = 0;
        $cropVerifiedCount = 0;
        $cropCorrectedCount = 0;

        if ($verifiedSteps) {
            foreach ($verifiedSteps as $v) {
                if ($v['accurate'] ?? true) {
                    $accurateCount++;
                } else {
                    $correctedCount++;
                }
            }
        }

        // 3-pass 크롭 검증 통계 수집
        foreach ($parsed['steps'] ?? [] as $step) {
            if (empty($step['focused_element']['_verification'])) {
                continue;
            }
            $sv = $step['focused_element']['_verification'];
            if ($sv['crop_verified'] ?? false) {
                $cropVerifiedCount++;
            }
            if ($sv['crop_corrected'] ?? false) {
                $cropCorrectedCount++;
            }
        }

        $parsed['_verification'] = [
            'verified_at' => now()->toIso8601String(),
            'total_steps' => count($stepsWithElement),
            'accurate' => $accurateCount,
            'corrected' => $correctedCount,
            'crop_verified' => $cropVerifiedCount,
            'crop_corrected' => $cropCorrectedCount,
        ];

        // 검증 이미지 정리
        if (file_exists($verificationImagePath)) {
            @unlink($verificationImagePath);
        }

        Log::info('ScreenAnalysis: 좌표 검증 루프 완료', [
            '2pass' => "정확:{$accurateCount}, 보정:{$correctedCount}",
            '3pass' => "크롭확인:{$cropVerifiedCount}, 크롭보정:{$cropCorrectedCount}",
        ]);

        return $parsed;
    }

    /**
     * 검증용 이미지 생성
     *
     * 원본 이미지 위에 각 step의 focused_element를 색상별 반투명 사각형 + 번호 원으로 표시.
     * 그리드 없이 깨끗한 원본 위에 렌더링하여 AI가 UI 요소를 명확히 볼 수 있도록 한다.
     */
    private function createVerificationImage(string $imagePath, array $steps): ?string
    {
        try {
            $info = getimagesize($imagePath);
            if (! $info) {
                return null;
            }

            $source = match ($info[2]) {
                IMAGETYPE_PNG => imagecreatefrompng($imagePath),
                IMAGETYPE_JPEG => imagecreatefromjpeg($imagePath),
                IMAGETYPE_GIF => imagecreatefromgif($imagePath),
                IMAGETYPE_WEBP => imagecreatefromwebp($imagePath),
                default => null,
            };

            if (! $source) {
                return null;
            }

            $srcW = imagesx($source);
            $srcH = imagesy($source);

            // annotateSlideWithSpotlight()와 동일한 레터박스 레이아웃
            $canvas = imagecreatetruecolor(self::ANALYSIS_WIDTH, self::ANALYSIS_HEIGHT);
            $black = imagecolorallocate($canvas, 0, 0, 0);
            imagefill($canvas, 0, 0, $black);

            $availH = self::ANALYSIS_HEIGHT - self::CAPTION_HEIGHT;
            $scale = min(self::ANALYSIS_WIDTH / $srcW, $availH / $srcH);
            $newW = (int) ($srcW * $scale);
            $newH = (int) ($srcH * $scale);
            $offsetX = (int) ((self::ANALYSIS_WIDTH - $newW) / 2);
            $offsetY = (int) (($availH - $newH) / 2);

            imagecopyresampled($canvas, $source, $offsetX, $offsetY, 0, 0, $newW, $newH, $srcW, $srcH);
            imagedestroy($source);

            // 각 step의 focused_element를 색상별 반투명 사각형 + 번호 원으로 표시
            $colorNames = [1 => 'Red', 2 => 'Green', 3 => 'Blue', 4 => 'Orange', 5 => 'Purple'];

            foreach ($steps as $idx => $step) {
                $stepNum = $step['step_number'] ?? ($idx + 1);
                $colorIdx = min($stepNum, 5);
                [$r, $g, $b] = self::VERIFICATION_COLORS[$colorIdx];

                $el = $step['focused_element'];
                $x = $el['x'] ?? 0;
                $y = $el['y'] ?? 0;
                $w = $el['w'] ?? 200;
                $h = $el['h'] ?? 200;

                // 0~1000 좌표를 캔버스 좌표로 변환
                $cx1 = (int) ($offsetX + ($x / 1000) * $newW);
                $cy1 = (int) ($offsetY + ($y / 1000) * $newH);
                $cx2 = (int) ($offsetX + (($x + $w) / 1000) * $newW);
                $cy2 = (int) ($offsetY + (($y + $h) / 1000) * $newH);

                // 반투명 사각형 (테두리 + 내부 채움)
                $fillColor = imagecolorallocatealpha($canvas, $r, $g, $b, 100);
                $borderColor = imagecolorallocate($canvas, $r, $g, $b);

                imagefilledrectangle($canvas, $cx1, $cy1, $cx2, $cy2, $fillColor);
                // 테두리 3px
                for ($t = 0; $t < 3; $t++) {
                    imagerectangle($canvas, $cx1 + $t, $cy1 + $t, $cx2 - $t, $cy2 - $t, $borderColor);
                }

                // 번호 원 (좌상단에 배치)
                $circleX = $cx1 + 12;
                $circleY = $cy1 + 12;
                $circleR = 12;
                $white = imagecolorallocate($canvas, 255, 255, 255);
                imagefilledellipse($canvas, $circleX, $circleY, $circleR * 2, $circleR * 2, $borderColor);
                imagestring($canvas, 5, $circleX - 4, $circleY - 7, (string) $stepNum, $white);
            }

            // 하단 캡션에 범례 표시
            $captionY = self::ANALYSIS_HEIGHT - self::CAPTION_HEIGHT + 10;
            $legendX = 12;
            $captionTextColor = imagecolorallocate($canvas, 220, 220, 220);

            imagestring($canvas, 3, $legendX, $captionY, 'Verification Overlay - Step Legend:', $captionTextColor);
            $legendX = 12;
            $captionY += 20;

            foreach ($steps as $idx => $step) {
                $stepNum = $step['step_number'] ?? ($idx + 1);
                $colorIdx = min($stepNum, 5);
                [$r, $g, $b] = self::VERIFICATION_COLORS[$colorIdx];
                $label = $step['focused_element']['label'] ?? '?';
                $colorName = $colorNames[$colorIdx] ?? '?';

                $swatchColor = imagecolorallocate($canvas, $r, $g, $b);
                imagefilledrectangle($canvas, $legendX, $captionY, $legendX + 14, $captionY + 14, $swatchColor);
                $legendText = "#{$stepNum} ({$colorName}): {$label}";
                imagestring($canvas, 2, $legendX + 18, $captionY + 1, $legendText, $captionTextColor);
                $legendX += strlen($legendText) * 7 + 30;

                // 줄바꿈 (너비 초과 시)
                if ($legendX > self::ANALYSIS_WIDTH - 200) {
                    $legendX = 12;
                    $captionY += 18;
                }
            }

            $outputPath = $imagePath.'_verify.png';
            imagepng($canvas, $outputPath, 6);
            imagedestroy($canvas);

            Log::debug('ScreenAnalysis: 검증 이미지 생성 완료', ['path' => $outputPath]);

            return $outputPath;
        } catch (\Exception $e) {
            Log::warning('ScreenAnalysis: 검증 이미지 생성 실패', ['error' => $e->getMessage()]);

            return null;
        }
    }

    /**
     * Gemini 2-pass 검증 호출
     *
     * 검증 이미지를 Gemini에 보내 각 하이라이트의 위치 정확도를 확인한다.
     */
    private function verifyCoordinates(string $verificationImagePath, array $steps): ?array
    {
        try {
            $imageData = base64_encode(file_get_contents($verificationImagePath));
            $mimeType = 'image/png';

            $colorNames = [1 => 'Red', 2 => 'Green', 3 => 'Blue', 4 => 'Orange', 5 => 'Purple'];

            // 각 step 정보를 텍스트로 나열
            $stepDescriptions = '';
            foreach ($steps as $idx => $step) {
                $stepNum = $step['step_number'] ?? ($idx + 1);
                $colorIdx = min($stepNum, 5);
                $colorName = $colorNames[$colorIdx] ?? '?';
                $el = $step['focused_element'];
                $label = $el['label'] ?? '?';
                $x = $el['x'] ?? 0;
                $y = $el['y'] ?? 0;
                $w = $el['w'] ?? 0;
                $h = $el['h'] ?? 0;
                $stepDescriptions .= "- Step {$stepNum} ({$colorName}): \"{$label}\" → x={$x}, y={$y}, w={$w}, h={$h}\n";
            }

            $prompt = <<<PROMPT
이 이미지에는 번호가 매겨진 색상 하이라이트(반투명 사각형)가 표시되어 있습니다.
각 하이라이트가 의도한 UI 요소를 정확히 감싸고 있는지 확인하세요.

=== 이미지 레이아웃 안내 ===
- 1920x1080 캔버스에 원본 스크린샷이 비율 보존으로 배치되어 있습니다.
- 하단 180px는 범례 영역이므로 UI 분석에서 제외하세요.
- 색상 사각형이 표시된 영역이 해당 UI 요소의 실제 위치와 일치하는지 판단하세요.

=== 각 하이라이트의 의도한 대상 ===
{$stepDescriptions}

=== 좌표 체계 ===
- 0~1000 정수 좌표 (이미지 콘텐츠 영역 기준)
- x: 영역 좌측 가장자리 (0=왼쪽 끝, 1000=오른쪽 끝)
- y: 영역 상단 가장자리 (0=위쪽 끝, 1000=아래쪽 끝)
- w: 영역 너비, h: 영역 높이

=== 판단 기준 ===
- 하이라이트가 해당 UI 요소를 70% 이상 포함하고, 요소 밖으로 크게 벗어나지 않으면 "accurate": true
- 하이라이트가 완전히 다른 위치에 있거나, 대상 요소의 50% 미만만 포함하면 "accurate": false

각 단계별로 판단하세요:
- "accurate": true이면 현재 좌표 유지
- "accurate": false이면 corrected_x, corrected_y, corrected_w, corrected_h를 제공
  (0~1000 정수 좌표, 실제 UI 요소를 정확히 감싸는 위치)

반드시 아래 JSON 형식으로만 응답하세요:
{
  "verifications": [
    {
      "step_number": 1,
      "accurate": true
    },
    {
      "step_number": 2,
      "accurate": false,
      "corrected_x": 150,
      "corrected_y": 200,
      "corrected_w": 300,
      "corrected_h": 80
    }
  ]
}
PROMPT;

            $parts = [
                ['text' => $prompt],
                [
                    'inlineData' => [
                        'mimeType' => $mimeType,
                        'data' => $imageData,
                    ],
                ],
            ];

            $result = $this->gemini->callGeminiWithParts($parts, 0.1, 1024);

            if (! $result) {
                Log::warning('ScreenAnalysis: 좌표 검증 API 호출 실패');

                return null;
            }

            $verified = $this->gemini->parseJson($result);

            if (! $verified || ! isset($verified['verifications'])) {
                Log::warning('ScreenAnalysis: 좌표 검증 JSON 파싱 실패', [
                    'result' => substr($result, 0, 300),
                ]);

                return null;
            }

            // 검증 결과 로그
            $accurateCount = 0;
            $correctedCount = 0;
            foreach ($verified['verifications'] as $v) {
                if ($v['accurate'] ?? true) {
                    $accurateCount++;
                } else {
                    $correctedCount++;
                }
            }
            Log::info("ScreenAnalysis: 좌표 검증 결과 - 정확: {$accurateCount}, 보정: {$correctedCount}");

            return $verified['verifications'];
        } catch (\Exception $e) {
            Log::warning('ScreenAnalysis: 좌표 검증 예외 발생', ['error' => $e->getMessage()]);

            return null;
        }
    }

    /**
     * 검증 결과를 원본 $parsed에 적용
     *
     * accurate: true → 좌표 유지, accurate: false → corrected 좌표로 교체
     */
    private function applyVerifiedCoordinates(array $parsed, ?array $verifiedSteps): array
    {
        if (! $verifiedSteps) {
            Log::debug('ScreenAnalysis: 검증 결과 없음, 원본 좌표 유지 (fallback)');

            return $parsed;
        }

        // step_number를 키로 하는 검증 맵 생성
        $verificationMap = [];
        foreach ($verifiedSteps as $v) {
            $verificationMap[$v['step_number']] = $v;
        }

        foreach ($parsed['steps'] as &$step) {
            if (empty($step['focused_element'])) {
                continue;
            }

            $stepNum = $step['step_number'] ?? 0;
            $verification = $verificationMap[$stepNum] ?? null;

            if (! $verification) {
                continue;
            }

            $isAccurate = $verification['accurate'] ?? true;

            if (! $isAccurate) {
                $oldX = $step['focused_element']['x'];
                $oldY = $step['focused_element']['y'];
                $oldW = $step['focused_element']['w'];
                $oldH = $step['focused_element']['h'];

                $step['focused_element']['x'] = $verification['corrected_x'] ?? $oldX;
                $step['focused_element']['y'] = $verification['corrected_y'] ?? $oldY;
                $step['focused_element']['w'] = $verification['corrected_w'] ?? $oldW;
                $step['focused_element']['h'] = $verification['corrected_h'] ?? $oldH;

                // 보정 전 좌표를 메타데이터로 저장
                $step['focused_element']['_verification'] = [
                    'accurate' => false,
                    'original' => ['x' => $oldX, 'y' => $oldY, 'w' => $oldW, 'h' => $oldH],
                ];

                Log::info("ScreenAnalysis: 좌표 보정 Step {$stepNum}", [
                    'label' => $step['focused_element']['label'] ?? '?',
                    'before' => "x={$oldX}, y={$oldY}, w={$oldW}, h={$oldH}",
                    'after' => "x={$step['focused_element']['x']}, y={$step['focused_element']['y']}, w={$step['focused_element']['w']}, h={$step['focused_element']['h']}",
                ]);
            } else {
                $step['focused_element']['_verification'] = [
                    'accurate' => true,
                ];
            }
        }
        unset($step);

        return $parsed;
    }

    /**
     * 3-pass 크롭 검증
     *
     * 2-pass 보정 후, 각 step의 좌표 영역을 원본에서 크롭하여
     * Gemini에게 해당 label이 실제로 포함되어 있는지 확인한다.
     * 불일치 시 전체 원본 이미지로 좌표를 재추정한다.
     */
    private function cropVerifyCoordinates(string $imagePath, array &$parsed): void
    {
        try {
            $info = getimagesize($imagePath);
            if (! $info) {
                return;
            }

            $source = match ($info[2]) {
                IMAGETYPE_PNG => imagecreatefrompng($imagePath),
                IMAGETYPE_JPEG => imagecreatefromjpeg($imagePath),
                IMAGETYPE_GIF => imagecreatefromgif($imagePath),
                IMAGETYPE_WEBP => imagecreatefromwebp($imagePath),
                default => null,
            };

            if (! $source) {
                return;
            }

            $srcW = imagesx($source);
            $srcH = imagesy($source);

            Log::info('ScreenAnalysis: 3-pass 크롭 검증 시작', [
                'screen' => $parsed['screen_number'] ?? '?',
                'image_size' => "{$srcW}x{$srcH}",
            ]);

            foreach ($parsed['steps'] as &$step) {
                if (empty($step['focused_element'])) {
                    continue;
                }

                $el = &$step['focused_element'];
                $stepNum = $step['step_number'] ?? 0;
                $label = $el['label'] ?? '';
                $type = $el['type'] ?? 'other';

                if (empty($label)) {
                    continue;
                }

                // 0~1000 좌표를 원본 이미지 픽셀로 변환
                $x = $el['x'] ?? 0;
                $y = $el['y'] ?? 0;
                $w = $el['w'] ?? 200;
                $h = $el['h'] ?? 200;

                $px = (int) (($x / 1000) * $srcW);
                $py = (int) (($y / 1000) * $srcH);
                $pw = max(1, (int) (($w / 1000) * $srcW));
                $ph = max(1, (int) (($h / 1000) * $srcH));

                // 패딩 확장: 주변 컨텍스트를 포함하여 Gemini가 판단하기 쉽게
                // 크롭 영역의 30% 또는 최소 80px 패딩 추가
                $padX = max(80, (int) ($pw * 0.3));
                $padY = max(80, (int) ($ph * 0.3));

                // 최소 크롭 크기: 200x150px (너무 작으면 단색으로 보임)
                $minCropW = 200;
                $minCropH = 150;
                if ($pw < $minCropW) {
                    $padX = max($padX, (int) (($minCropW - $pw) / 2));
                }
                if ($ph < $minCropH) {
                    $padY = max($padY, (int) (($minCropH - $ph) / 2));
                }

                $px = max(0, $px - $padX);
                $py = max(0, $py - $padY);
                $pw = min($pw + $padX * 2, $srcW - $px);
                $ph = min($ph + $padY * 2, $srcH - $py);

                // 패딩 적용 전 원본 영역의 크롭 내 상대 좌표 계산
                $origPx = (int) (($x / 1000) * $srcW);
                $origPy = (int) (($y / 1000) * $srcH);
                $origPw = max(1, (int) (($w / 1000) * $srcW));
                $origPh = max(1, (int) (($h / 1000) * $srcH));
                $targetRelX = $origPx - $px;
                $targetRelY = $origPy - $py;

                // 크롭 이미지 생성 + 타겟 영역 경계선 표시
                $crop = imagecreatetruecolor($pw, $ph);
                imagecopy($crop, $source, 0, 0, $px, $py, $pw, $ph);

                // 빨간 점선 사각형으로 실제 타겟 영역 표시 (false positive 방지)
                $targetBorder = imagecolorallocate($crop, 255, 50, 50);
                for ($t = 0; $t < 2; $t++) {
                    imagerectangle(
                        $crop,
                        $targetRelX + $t,
                        $targetRelY + $t,
                        $targetRelX + $origPw - $t,
                        $targetRelY + $origPh - $t,
                        $targetBorder
                    );
                }

                $cropPath = $imagePath."_crop_step{$stepNum}.png";
                imagepng($crop, $cropPath, 6);
                imagedestroy($crop);

                // Gemini에 크롭 검증 요청
                $result = $this->verifySingleCrop(
                    $cropPath,
                    $imagePath,
                    $label,
                    $type,
                    ['x' => $x, 'y' => $y, 'w' => $w, 'h' => $h]
                );

                // 결과 적용
                if ($result['match']) {
                    $el['_verification']['crop_verified'] = true;
                    Log::debug("ScreenAnalysis: 크롭 검증 Step {$stepNum} - 일치", ['label' => $label]);
                } elseif ($result['corrected'] && ! empty($result['new_coords'])) {
                    $el['_verification']['crop_verified'] = false;
                    $el['_verification']['crop_corrected'] = true;
                    $el['_verification']['crop_original'] = [
                        'x' => $el['x'],
                        'y' => $el['y'],
                        'w' => $el['w'],
                        'h' => $el['h'],
                    ];

                    $el['x'] = $result['new_coords']['x'];
                    $el['y'] = $result['new_coords']['y'];
                    $el['w'] = $result['new_coords']['w'];
                    $el['h'] = $result['new_coords']['h'];

                    Log::info("ScreenAnalysis: 크롭 보정 Step {$stepNum}", [
                        'label' => $label,
                        'before' => $el['_verification']['crop_original'],
                        'after' => $result['new_coords'],
                    ]);
                } else {
                    $el['_verification']['crop_verified'] = false;
                    Log::warning("ScreenAnalysis: 크롭 불일치 Step {$stepNum} 보정 실패", ['label' => $label]);
                }

                // 임시 크롭 파일 정리
                if (file_exists($cropPath)) {
                    @unlink($cropPath);
                }
            }
            unset($step, $el);

            imagedestroy($source);

            Log::info('ScreenAnalysis: 3-pass 크롭 검증 완료');
        } catch (\Exception $e) {
            Log::warning('ScreenAnalysis: 크롭 검증 예외 발생', ['error' => $e->getMessage()]);
        }
    }

    /**
     * 단일 크롭 이미지를 Gemini에 보내 label 포함 여부 판정
     *
     * 1차: 크롭 이미지만 전송 → match 판정
     * 2차 (match=false 시): 전체 원본 이미지 전송 → 좌표 재추정
     */
    private function verifySingleCrop(string $cropImagePath, string $fullImagePath, string $label, string $type, array $currentCoords): array
    {
        try {
            // 1차 요청: 크롭 이미지로 label 포함 여부 확인
            $cropData = base64_encode(file_get_contents($cropImagePath));

            $prompt1 = "이 이미지는 UI 화면에서 특정 영역과 그 주변을 잘라낸 것입니다.\n"
                ."빨간색 사각형 테두리가 실제 타겟 영역을 표시합니다.\n"
                ."빨간 테두리 안쪽에 '{$label}' ({$type}) UI 요소가 포함되어 있습니까?\n"
                ."(빨간 테두리 바깥의 주변 영역은 컨텍스트일 뿐이므로 판단에서 제외하세요)\n"
                ."반드시 아래 JSON 형식으로만 응답하세요:\n"
                ."{\"match\": true, \"actual_content\": \"빨간 테두리 안의 실제 내용 요약\"}\n"
                ."또는\n"
                .'{"match": false, "actual_content": "빨간 테두리 안의 실제 내용 요약"}';

            $parts1 = [
                ['text' => $prompt1],
                [
                    'inlineData' => [
                        'mimeType' => 'image/png',
                        'data' => $cropData,
                    ],
                ],
            ];

            $result1 = $this->gemini->callGeminiWithParts($parts1, 0.1, 512);

            if (! $result1) {
                return ['match' => true, 'corrected' => false, 'new_coords' => null];
            }

            $parsed1 = $this->gemini->parseJson($result1);

            if (! $parsed1) {
                return ['match' => true, 'corrected' => false, 'new_coords' => null];
            }

            $isMatch = $parsed1['match'] ?? true;

            Log::debug('ScreenAnalysis: 크롭 1차 판정', [
                'label' => $label,
                'match' => $isMatch,
                'actual' => $parsed1['actual_content'] ?? '?',
            ]);

            if ($isMatch) {
                return ['match' => true, 'corrected' => false, 'new_coords' => null];
            }

            // 2차 요청: 전체 원본 이미지로 정확한 좌표 재추정
            $fullData = base64_encode(file_get_contents($fullImagePath));
            $fullMime = mime_content_type($fullImagePath) ?: 'image/png';

            $x = $currentCoords['x'];
            $y = $currentCoords['y'];
            $w = $currentCoords['w'];
            $h = $currentCoords['h'];

            $prompt2 = "이 이미지에서 '{$label}' ({$type}) UI 요소의 정확한 위치를 찾아주세요.\n"
                ."이전 추정 좌표 (x={$x}, y={$y}, w={$w}, h={$h})는 부정확했습니다.\n"
                ."해당 영역을 잘라냈을 때 '{$label}'이 포함되지 않았습니다.\n\n"
                ."0~1000 정수 좌표로 응답해주세요.\n"
                ."- x: 영역 좌측 가장자리 (0=이미지 왼쪽 끝, 1000=오른쪽 끝)\n"
                ."- y: 영역 상단 가장자리 (0=이미지 위쪽 끝, 1000=아래쪽 끝)\n"
                ."- w: 영역 너비, h: 영역 높이\n\n"
                ."반드시 아래 JSON 형식으로만 응답하세요:\n"
                ."{\"found\": true, \"x\": 100, \"y\": 200, \"w\": 150, \"h\": 50}\n"
                ."또는 찾을 수 없는 경우:\n"
                .'{"found": false}';

            $parts2 = [
                ['text' => $prompt2],
                [
                    'inlineData' => [
                        'mimeType' => $fullMime,
                        'data' => $fullData,
                    ],
                ],
            ];

            $result2 = $this->gemini->callGeminiWithParts($parts2, 0.2, 512);

            if (! $result2) {
                return ['match' => false, 'corrected' => false, 'new_coords' => null];
            }

            $parsed2 = $this->gemini->parseJson($result2);

            if (! $parsed2 || ! ($parsed2['found'] ?? false)) {
                Log::debug('ScreenAnalysis: 크롭 2차 재추정 실패, 그리드 이미지로 재시도', ['label' => $label]);

                // 3차 재시도: 그리드 오버레이 이미지로 정확한 좌표 추정
                $gridImagePath = $this->createGridOverlay($fullImagePath);
                if ($gridImagePath && file_exists($gridImagePath)) {
                    $gridData = base64_encode(file_get_contents($gridImagePath));

                    $prompt3 = "이 이미지에는 20x12 그리드 오버레이가 표시되어 있습니다.\n"
                        ."그리드의 좌표 눈금(0, 250, 500, 750, 1000)을 참고하여\n"
                        ."'{$label}' ({$type}) UI 요소의 정확한 위치를 찾아주세요.\n\n"
                        ."이전 추정 좌표 (x={$x}, y={$y}, w={$w}, h={$h})는 부정확했습니다.\n\n"
                        ."0~1000 정수 좌표로 응답해주세요.\n"
                        ."- x: 영역 좌측 가장자리 (0=왼쪽 끝, 1000=오른쪽 끝)\n"
                        ."- y: 영역 상단 가장자리 (0=위쪽 끝, 1000=아래쪽 끝)\n"
                        ."- w: 영역 너비, h: 영역 높이\n\n"
                        ."반드시 아래 JSON 형식으로만 응답하세요:\n"
                        ."{\"found\": true, \"x\": 100, \"y\": 200, \"w\": 150, \"h\": 50}\n"
                        ."또는 찾을 수 없는 경우:\n"
                        .'{"found": false}';

                    $parts3 = [
                        ['text' => $prompt3],
                        [
                            'inlineData' => [
                                'mimeType' => 'image/png',
                                'data' => $gridData,
                            ],
                        ],
                    ];

                    $result3 = $this->gemini->callGeminiWithParts($parts3, 0.2, 512);
                    @unlink($gridImagePath);

                    if ($result3) {
                        $parsed3 = $this->gemini->parseJson($result3);
                        if ($parsed3 && ($parsed3['found'] ?? false)) {
                            Log::debug('ScreenAnalysis: 크롭 3차 그리드 재추정 성공', [
                                'label' => $label,
                                'new_coords' => "x={$parsed3['x']}, y={$parsed3['y']}, w={$parsed3['w']}, h={$parsed3['h']}",
                            ]);

                            return [
                                'match' => false,
                                'corrected' => true,
                                'new_coords' => [
                                    'x' => (int) ($parsed3['x'] ?? $x),
                                    'y' => (int) ($parsed3['y'] ?? $y),
                                    'w' => (int) ($parsed3['w'] ?? $w),
                                    'h' => (int) ($parsed3['h'] ?? $h),
                                ],
                            ];
                        }
                    }
                }

                Log::debug('ScreenAnalysis: 크롭 재추정 최종 실패', ['label' => $label]);

                return ['match' => false, 'corrected' => false, 'new_coords' => null];
            }

            Log::debug('ScreenAnalysis: 크롭 2차 재추정 성공', [
                'label' => $label,
                'new_coords' => "x={$parsed2['x']}, y={$parsed2['y']}, w={$parsed2['w']}, h={$parsed2['h']}",
            ]);

            return [
                'match' => false,
                'corrected' => true,
                'new_coords' => [
                    'x' => (int) ($parsed2['x'] ?? $x),
                    'y' => (int) ($parsed2['y'] ?? $y),
                    'w' => (int) ($parsed2['w'] ?? $w),
                    'h' => (int) ($parsed2['h'] ?? $h),
                ],
            ];
        } catch (\Exception $e) {
            Log::warning('ScreenAnalysis: 크롭 검증 예외', ['error' => $e->getMessage(), 'label' => $label]);

            return ['match' => true, 'corrected' => false, 'new_coords' => null];
        }
    }
}
