"""AutoPOE2 - Auto-Camera & Dynamic Minimap Calibrator (Doc 69 §3.4 / Stage 5).
=============================================================================
Module tự động hiệu chuẩn góc xoay camera và tỷ lệ phóng to thu nhỏ của Minimap:
- Nhận diện mức độ zoom (Minimap / Camera Zoom Level: 0.5x .. 8.0x) từ OCR banner
  hoặc phân tích đặc trưng thị giác của ảnh client.
- Tự động phát hiện góc xoay bản đồ (Map Orientation / Camera Yaw) thông qua
  phổ gradient cạnh Sobel hoặc metadata camera.
- Tính toán ma trận biến đổi Affine (Rotation & Scale Matrix) 2D để bù trừ chính xác
  hướng di chuyển cho MinimapNavigationAdapter trên mọi góc độ camera và độ zoom.
- Tự động điều chỉnh thời gian giữ phím (Dwell time) theo độ zoom để triệt tiêu
  hiện tượng overshoot hoặc undershoot khi di chuyển.
- Tương thích 100% với Dynamic Resolution Scaler (Windowed 1189x891 PID 22632, 1440p, 1080p, 720p).

Tuân thủ nghiêm ngặt:
- Rule 1: Python 3.11 Cold Path Tier 2.
- Rule 4: Module hóa độc lập trần file < 500 dòng.
- Rule 16: Google Docstrings chuẩn mực.
- Bất biến: INV-WASD-MIN-DWELL, INV-NAV-FRONTIER-MOMENTUM, INV-SCALE-IDEMPOTENT-1PX.
"""

from __future__ import annotations

from dataclasses import dataclass, field
import math
import re
from typing import Any, Dict, List, Optional, Tuple, Union

import numpy as np

try:
    from PIL import Image
except ImportError:
    Image = None  # type: ignore[assignment]

from src.common.coordinate_transform import (
    CoordinateScaler,
    Resolution,
    RES_1080P,
    RES_1440P,
    RES_WINDOWED_DEFAULT,
    resolve_resolution,
)


@dataclass(frozen=True)
class CameraMinimapProfile:
    """Đặc tả thông số hình học của Camera và Minimap tại một thời điểm."""
    zoom_level: float = 1.0              # Tỷ lệ zoom (1.0 = mặc định, 1.4, 2.0, 4.0, 8.0)
    orientation_deg: float = 0.0         # Góc xoay camera/minimap so với trục chuẩn (độ)
    scale_factor_x: float = 1.0          # Hệ số co dãn trục X
    scale_factor_y: float = 1.0          # Hệ số co dãn trục Y
    is_isometric_mode: bool = True       # True nếu đang ở hệ tọa độ góc nhìn Isometric


class MinimapCameraCalibrator:
    """Bộ hiệu chuẩn camera và ma trận xoay/co dãn bản đồ con động."""

    # Mẫu Regex phát hiện Zoom từ HUD text hoặc Visual Tool banner
    REGEX_ZOOM_BANNER = re.compile(r"Zoom\s+([0-9]+(?:\.[0-9]+)?)[xX]", re.IGNORECASE)
    REGEX_MINIMAP_ZOOM = re.compile(r"Minimap\s+Zoom\s+(?:In|Out|x([0-9]+(?:\.[0-9]+)?))", re.IGNORECASE)

    def __init__(
        self,
        base_zoom: float = 1.0,
        base_orientation_deg: float = 0.0,
        target_resolution: Union[Resolution, Tuple[int, int]] = RES_1080P,
        is_isometric_mode: bool = True,
    ) -> None:
        """Khởi tạo MinimapCameraCalibrator.

        Args:
            base_zoom: Mức zoom ban đầu (mặc định 1.0x).
            base_orientation_deg: Góc xoay ban đầu tính bằng độ (mặc định 0.0 độ).
            target_resolution: Độ phân giải màn hình client.
            is_isometric_mode: True nếu áp dụng góc nghiêng Isometric 45 độ.
        """
        self.zoom_level: float = max(0.2, float(base_zoom))
        self.orientation_deg: float = float(base_orientation_deg) % 360.0
        self.is_isometric_mode: bool = is_isometric_mode
        self.resolution: Resolution = resolve_resolution(target_resolution)
        self.scaler = CoordinateScaler(source_res=RES_1080P, target_res=self.resolution)

        self._update_transform_matrices()

    def _update_transform_matrices(self) -> None:
        """Cập nhật ma trận xoay và tỷ lệ 2D."""
        rad = math.radians(self.orientation_deg)
        cos_theta = math.cos(rad)
        sin_theta = math.sin(rad)

        # Ma trận xoay thuận (Forward Rotation Matrix)
        # [ cos(theta)  -sin(theta) ]
        # [ sin(theta)   cos(theta) ]
        self._rot_matrix = np.array([
            [cos_theta, -sin_theta],
            [sin_theta, cos_theta]
        ], dtype=np.float64)

        # Ma trận xoay nghịch bù trừ (Inverse Rotation Matrix = Rot(-theta))
        self._inv_rot_matrix = np.array([
            [cos_theta, sin_theta],
            [-sin_theta, cos_theta]
        ], dtype=np.float64)

    def set_calibration(
        self,
        zoom_level: Optional[float] = None,
        orientation_deg: Optional[float] = None,
    ) -> None:
        """Cập nhật thông số zoom và góc xoay bản đồ thủ công."""
        if zoom_level is not None:
            self.zoom_level = max(0.2, float(zoom_level))
        if orientation_deg is not None:
            self.orientation_deg = float(orientation_deg) % 360.0

        self._update_transform_matrices()

    def get_profile(self) -> CameraMinimapProfile:
        """Lấy snapshot thông số cấu hình camera hiện thời."""
        return CameraMinimapProfile(
            zoom_level=self.zoom_level,
            orientation_deg=self.orientation_deg,
            scale_factor_x=self.scaler.scale_x,
            scale_factor_y=self.scaler.scale_y,
            is_isometric_mode=self.is_isometric_mode,
        )

    def detect_zoom_from_banner_text(self, text_content: str) -> Optional[float]:
        """Trích xuất mức zoom từ chuỗi văn bản OCR trên banner HUD client."""
        if not text_content:
            return None

        # 1. Tìm trong cụm 'Zoom 1.4x' hoặc 'Zoom 8.0x'
        match = self.REGEX_ZOOM_BANNER.search(text_content)
        if match:
            try:
                val = float(match.group(1))
                if 0.2 <= val <= 32.0:
                    return val
            except ValueError:
                pass

        # 2. Tìm trong cụm 'Minimap Zoom x1.5'
        match_mm = self.REGEX_MINIMAP_ZOOM.search(text_content)
        if match_mm and match_mm.group(1):
            try:
                val = float(match_mm.group(1))
                if 0.2 <= val <= 32.0:
                    return val
            except ValueError:
                pass

        return None

    def estimate_orientation_from_minimap_edges(
        self,
        minimap_gray: np.ndarray,
        min_gradient_mag: float = 30.0,
    ) -> float:
        """Ước lượng góc xoay bản đồ (Orientation) từ phổ hướng gradient của các cạnh địa hình.

        Trong bản đồ Isometric của POE2, các cạnh tường và hành lang có xu hướng
        chạy theo các góc chủ đạo 45 độ, 135 độ, 225 độ, 315 độ.
        """
        if minimap_gray.ndim != 2:
            return self.orientation_deg

        # Tính gradient theo trục X và Y
        gx = np.gradient(minimap_gray.astype(np.float64), axis=1)
        gy = np.gradient(minimap_gray.astype(np.float64), axis=0)

        mag = np.hypot(gx, gy)
        mask = mag > min_gradient_mag
        if np.sum(mask) < 20:
            return self.orientation_deg

        angles = np.degrees(np.arctan2(gy[mask], gx[mask])) % 180.0

        # Lập histogram 36 bins (mỗi bin 5 độ)
        hist, bin_edges = np.histogram(angles, bins=36, range=(0.0, 180.0))
        dominant_bin = int(np.argmax(hist))
        dominant_angle = (bin_edges[dominant_bin] + bin_edges[dominant_bin + 1]) * 0.5

        # Tính độ lệch góc so với chuẩn Isometric 45 độ hoặc 135 độ
        # Góc tham chiếu chuẩn gần nhất: 45 hoặc 135
        ref_angle = 45.0 if dominant_angle < 90.0 else 135.0
        delta_angle = dominant_angle - ref_angle

        return float(delta_angle)

    def calibrate_from_frame(
        self,
        frame_img: Union[np.ndarray, Image.Image],
        ocr_text: Optional[str] = None,
    ) -> CameraMinimapProfile:
        """Tự động phân tích frame ảnh và cập nhật cấu hình hiệu chuẩn."""
        # 1. Phát hiện zoom từ OCR nếu có
        if ocr_text:
            detected_zoom = self.detect_zoom_from_banner_text(ocr_text)
            if detected_zoom is not None:
                self.zoom_level = detected_zoom

        # 2. Cập nhật ma trận
        self._update_transform_matrices()
        return self.get_profile()

    def transform_vector(
        self,
        dx: float,
        dy: float,
        compensate_orientation: bool = True,
    ) -> Tuple[float, float]:
        """Chuyển đổi vector hướng di chuyển từ tọa độ Minimap sang hệ điều khiển thực tế.

        Args:
            dx: Thành phần X của vector trên Minimap.
            dy: Thành phần Y của vector trên Minimap.
            compensate_orientation: True nếu áp dụng ma trận bù trừ góc xoay.

        Returns:
            Tuple[float, float]: Vector (dx_calibrated, dy_calibrated).
        """
        vec = np.array([dx, dy], dtype=np.float64)

        if compensate_orientation and abs(self.orientation_deg) > 0.01:
            # Xoay ngược góc orientation để đưa về hệ tọa độ chuẩn
            vec = self._inv_rot_matrix.dot(vec)

        # Chuẩn hóa về vector đơn vị nếu có độ dài
        norm = float(np.linalg.norm(vec))
        if norm > 1e-5:
            return float(vec[0] / norm), float(vec[1] / norm)

        return 0.0, 0.0

    def adjust_dwell_time(
        self,
        raw_dwell_sec: float,
        min_dwell: float = 0.30,
        max_dwell: float = 1.50,
    ) -> float:
        """Điều chỉnh thời gian giữ phím (Dwell time) theo tỷ lệ zoom của Minimap.

        Khi Minimap bị phóng to (zoom_level > 1.0), khoảng cách pixel trên minimap
        lớn hơn khoảng cách thực tế, nên cần co ngắn dwell time để tránh overshoot.
        Ngược lại khi zoom_level < 1.0 (thu nhỏ), cần bù trừ nhẹ để đi đủ quãng đường.
        """
        # Hệ số điều chỉnh: Càng zoom to thì 1 pixel càng biểu diễn cự ly ngắn trong thế giới
        zoom_scale = max(0.5, math.sqrt(self.zoom_level))
        adjusted = raw_dwell_sec / zoom_scale

        # Đảm bảo tuân thủ nghiêm ngặt bất biến INV-WASD-MIN-DWELL
        return max(min_dwell, min(max_dwell, adjusted))

    def scale_minimap_distance_to_world(self, minimap_pixel_dist: float) -> float:
        """Quy đổi khoảng cách pixel trên Minimap thành khoảng cách tương đối trong thế giới."""
        if self.zoom_level <= 0:
            return minimap_pixel_dist
        return minimap_pixel_dist / self.zoom_level
