"""
Utility helpers for Pheser.
"""

from __future__ import annotations
import os
import hashlib
from datetime import datetime
from typing import Any

# ── GPS helpers ────────────────────────────────────────────────────────────────

def rational_to_float(rational: tuple) -> float:
    """Convert a piexif rational tuple (numerator, denominator) to float."""
    num, den = rational
    return num / den if den != 0 else 0.0


def gps_to_deg(value: list[tuple], ref: bytes | str = b"N") -> float:
    """
    Convert GPS EXIF rational tuples to signed decimal degrees.
    Negative for S (latitude) or W (longitude).
    """
    d = rational_to_float(value[0])
    m = rational_to_float(value[1])
    s = rational_to_float(value[2])
    degrees = d + (m / 60.0) + (s / 3600.0)
    if ref in (b"S", "S", b"W", "W"):
        degrees = -degrees
    return round(degrees, 8)


def deg_to_rational(degrees: float) -> list[tuple[int, int]]:
    """Convert decimal degrees back to piexif rational tuples."""
    degrees = abs(degrees)
    d = int(degrees)
    m = int((degrees - d) * 60)
    s = round(((degrees - d) * 60 - m) * 60 * 10000)
    return [(d, 1), (m, 1), (s, 10000)]


# ── Value decoding ─────────────────────────────────────────────────────────────

def decode_value(v: Any) -> Any:
    """Decode bytes to string where possible; leave other types untouched."""
    if isinstance(v, bytes):
        try:
            return v.decode("utf-8").strip("\x00")
        except (UnicodeDecodeError, AttributeError):
            return v.hex()
    if isinstance(v, tuple) and len(v) == 2 and isinstance(v[0], int):
        # Single rational
        try:
            return rational_to_float(v)
        except Exception:
            return v
    return v


# ── File helpers ───────────────────────────────────────────────────────────────

def file_hash(path: str, algorithm: str = "sha256") -> str:
    """Return hex digest of a file."""
    h = hashlib.new(algorithm)
    with open(path, "rb") as f:
        for chunk in iter(lambda: f.read(65536), b""):
            h.update(chunk)
    return h.hexdigest()


def file_size_fmt(path: str) -> str:
    """Return human-readable file size."""
    size = os.path.getsize(path)
    for unit in ("B", "KB", "MB", "GB"):
        if size < 1024:
            return f"{size:.1f} {unit}"
        size /= 1024
    return f"{size:.1f} TB"


def timestamp() -> str:
    return datetime.now().strftime("%Y-%m-%d %H:%M:%S")


def output_path(input_file: str, prefix: str, out_dir: str | None = None) -> str:
    """
    Build an output file path.
    If out_dir is given, place the file there; otherwise alongside the input.
    """
    base = os.path.basename(input_file)
    name, ext = os.path.splitext(base)
    out_name = f"{prefix}_{name}{ext}"
    directory = out_dir if out_dir else os.path.dirname(os.path.abspath(input_file))
    return os.path.join(directory, out_name)


# ── Supported formats ──────────────────────────────────────────────────────────

SUPPORTED_EXTENSIONS = {".jpg", ".jpeg", ".tiff", ".tif", ".webp"}

def is_supported(path: str) -> bool:
    return os.path.splitext(path)[1].lower() in SUPPORTED_EXTENSIONS