"""Cross-platform clipboard image support.

Reads image data from the system clipboard on macOS, Windows, and Linux.
Falls back gracefully when platform-specific dependencies are unavailable.
"""

from __future__ import annotations

import io
import mimetypes
import os
import subprocess
import sys
from pathlib import Path
from typing import Optional

# Image file extensions we recognize
IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".tiff", ".svg"}

# Map extensions to MIME types
MIME_MAP = {
    ".png": "image/png",
    ".jpg": "image/jpeg",
    ".jpeg": "image/jpeg",
    ".gif": "image/gif",
    ".webp": "image/webp",
    ".bmp": "image/bmp",
    ".tiff": "image/tiff",
    ".svg": "image/svg+xml",
}


def get_clipboard_image() -> Optional[tuple[bytes, str]]:
    """Read image data from the system clipboard.

    Returns:
        Tuple of (image_bytes, media_type) or None if no image on clipboard.
        media_type is e.g. "image/png".
    """
    if sys.platform == "darwin":
        return _clipboard_macos()
    elif sys.platform == "win32":
        return _clipboard_windows()
    else:
        return _clipboard_linux()


def read_image_file(path: str) -> Optional[tuple[bytes, str]]:
    """Read an image from a file path.

    Args:
        path: Path to the image file.

    Returns:
        Tuple of (image_bytes, media_type) or None if file doesn't exist or isn't an image.
    """
    p = Path(path.strip().strip("'\""))
    if not p.exists() or not p.is_file():
        return None

    ext = p.suffix.lower()
    if ext not in IMAGE_EXTENSIONS:
        return None

    media_type = MIME_MAP.get(ext, "image/png")
    try:
        data = p.read_bytes()
        if len(data) == 0:
            return None
        return (data, media_type)
    except OSError:
        return None


def extract_image_paths(text: str) -> list[str]:
    """Extract potential image file paths from user input.

    Handles:
    - Bare paths: /path/to/image.png
    - Quoted paths: "/path/to/image.png"
    - Tilde paths: ~/screenshots/img.jpg
    - Relative paths: ./screenshot.png

    Returns:
        List of extracted path strings.
    """
    import re

    paths = []
    # Match quoted paths first
    for match in re.finditer(r'["\']([^"\']+\.(png|jpe?g|gif|webp|bmp|tiff|svg))["\']', text, re.IGNORECASE):
        paths.append(match.group(1))

    # Match unquoted paths (must start with /, ~, or ./)
    for match in re.finditer(
        r'(?:^|\s)((?:[/~]|\./)[\w/._ -]+\.(png|jpe?g|gif|webp|bmp|tiff|svg))',
        text,
        re.IGNORECASE,
    ):
        candidate = match.group(1)
        if candidate not in paths:
            paths.append(candidate)

    # Expand ~ in paths
    return [os.path.expanduser(p) for p in paths]


def _clipboard_macos() -> Optional[tuple[bytes, str]]:
    """Read image from macOS clipboard. Tries multiple methods in order."""
    import tempfile

    # Method 1: osascript — write clipboard image directly to temp file (no deps)
    try:
        with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
            tmp_path = tmp.name

        script = f'''
try
    set theImage to the clipboard as «class PNGf»
    set theFile to open for access (POSIX file "{tmp_path}") with write permission
    write theImage to theFile
    close access theFile
    return "PNG"
on error
    try
        set theImage to the clipboard as «class TIFF»
        set theFile to open for access (POSIX file "{tmp_path}") with write permission
        write theImage to theFile
        close access theFile
        return "TIFF"
    on error
        return "NONE"
    end try
end try
'''
        result = subprocess.run(
            ["osascript", "-e", script],
            capture_output=True,
            text=True,
            timeout=5,
        )
        fmt = result.stdout.strip()
        if fmt in ("PNG", "TIFF") and os.path.exists(tmp_path):
            data = Path(tmp_path).read_bytes()
            os.unlink(tmp_path)
            if data:
                media = "image/png" if fmt == "PNG" else "image/tiff"
                return (data, media)
        if os.path.exists(tmp_path):
            os.unlink(tmp_path)
    except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
        try:
            os.unlink(tmp_path)
        except (OSError, UnboundLocalError):
            pass

    # Method 2: AppKit (PyObjC) — direct pasteboard access
    try:
        import AppKit

        pb = AppKit.NSPasteboard.generalPasteboard()
        for pasteboard_type in [
            AppKit.NSPasteboardTypePNG,
            AppKit.NSPasteboardTypeTIFF,
            "public.jpeg",
        ]:
            data = pb.dataForType_(pasteboard_type)
            if data:
                raw = bytes(data)
                if pasteboard_type == AppKit.NSPasteboardTypePNG:
                    return (raw, "image/png")
                elif pasteboard_type == "public.jpeg":
                    return (raw, "image/jpeg")
                else:
                    # TIFF — convert to PNG
                    try:
                        rep = AppKit.NSBitmapImageRep.imageRepWithData_(data)
                        if rep:
                            png_data = rep.representationUsingType_properties_(
                                AppKit.NSBitmapImageFileTypePNG, None
                            )
                            if png_data:
                                return (bytes(png_data), "image/png")
                    except Exception:
                        pass
                    return (raw, "image/tiff")
    except ImportError:
        pass

    # Method 3: pngpaste CLI tool
    try:
        with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
            tmp_path = tmp.name

        result = subprocess.run(
            ["pngpaste", tmp_path],
            capture_output=True,
            timeout=5,
        )
        if result.returncode == 0 and os.path.exists(tmp_path):
            data = Path(tmp_path).read_bytes()
            os.unlink(tmp_path)
            if data:
                return (data, "image/png")
        if os.path.exists(tmp_path):
            os.unlink(tmp_path)
    except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
        pass

    return None


def _clipboard_windows() -> Optional[tuple[bytes, str]]:
    """Read image from Windows clipboard."""
    # Method 1: Try win32clipboard
    try:
        import win32clipboard

        win32clipboard.OpenClipboard()
        try:
            # CF_DIB = 8 (device-independent bitmap)
            if win32clipboard.IsClipboardFormatAvailable(8):
                data = win32clipboard.GetClipboardData(8)
                if data:
                    # Convert DIB to PNG using PIL if available
                    try:
                        from PIL import Image

                        img = Image.open(io.BytesIO(data))
                        buf = io.BytesIO()
                        img.save(buf, format="PNG")
                        return (buf.getvalue(), "image/png")
                    except (ImportError, Exception):
                        return (data, "image/bmp")
        finally:
            win32clipboard.CloseClipboard()
    except ImportError:
        pass

    # Method 2: PowerShell fallback
    try:
        import tempfile

        with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
            tmp_path = tmp.name

        ps_script = f"""
Add-Type -AssemblyName System.Windows.Forms
$img = [System.Windows.Forms.Clipboard]::GetImage()
if ($img -ne $null) {{
    $img.Save('{tmp_path}', [System.Drawing.Imaging.ImageFormat]::Png)
    Write-Output 'OK'
}} else {{
    Write-Output 'NONE'
}}
"""
        result = subprocess.run(
            ["powershell", "-NoProfile", "-Command", ps_script],
            capture_output=True,
            text=True,
            timeout=10,
        )
        if "OK" in result.stdout and os.path.exists(tmp_path):
            data = Path(tmp_path).read_bytes()
            os.unlink(tmp_path)
            if data:
                return (data, "image/png")
        if os.path.exists(tmp_path):
            os.unlink(tmp_path)
    except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
        pass

    return None


def _clipboard_linux() -> Optional[tuple[bytes, str]]:
    """Read image from Linux clipboard using xclip or wl-paste."""
    # Method 1: wl-paste (Wayland)
    if os.environ.get("WAYLAND_DISPLAY"):
        try:
            result = subprocess.run(
                ["wl-paste", "--type", "image/png"],
                capture_output=True,
                timeout=5,
            )
            if result.returncode == 0 and result.stdout:
                return (result.stdout, "image/png")
        except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
            pass

    # Method 2: xclip (X11)
    try:
        result = subprocess.run(
            ["xclip", "-selection", "clipboard", "-t", "image/png", "-o"],
            capture_output=True,
            timeout=5,
        )
        if result.returncode == 0 and result.stdout:
            return (result.stdout, "image/png")
    except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
        pass

    # Method 3: xsel doesn't support binary, so skip it
    return None


def format_image_size(data: bytes) -> str:
    """Format image data size for display."""
    size = len(data)
    if size < 1024:
        return f"{size} B"
    elif size < 1024 * 1024:
        return f"{size / 1024:.1f} KB"
    else:
        return f"{size / (1024 * 1024):.1f} MB"


def clipboard_fingerprint(data: bytes) -> str:
    """Return a short hash of image data for dedup tracking."""
    import hashlib
    return hashlib.sha256(data[:2048]).hexdigest()[:16]


# Tracks the last attached clipboard image fingerprint per-session
_last_clipboard_fp: Optional[str] = None


def auto_attach_clipboard(pending_images: list, quiet: bool = False) -> bool:
    """Check clipboard for a NEW image and auto-attach it.

    Compares against the last fingerprint to avoid re-attaching the same
    screenshot on every message. Returns True if an image was attached.

    Args:
        pending_images: List to append (bytes, media_type) tuples to.
        quiet: If True, don't print anything.

    Returns:
        True if a new image was attached.
    """
    global _last_clipboard_fp

    result = get_clipboard_image()
    if result is None:
        return False

    data, media_type = result
    fp = clipboard_fingerprint(data)

    # Skip if we already attached this exact image
    if fp == _last_clipboard_fp:
        return False

    _last_clipboard_fp = fp
    pending_images.append((data, media_type))
    return True


def reset_clipboard_tracking():
    """Reset the clipboard fingerprint so the next check will attach."""
    global _last_clipboard_fp
    _last_clipboard_fp = None
