"""Image utility functions for Sphinx."""

from __future__ import annotations

import base64
from pathlib import Path
from typing import TYPE_CHECKING, NamedTuple, overload
from urllib.parse import unquote_to_bytes

import imagesize

if TYPE_CHECKING:
    from os import PathLike

try:
    from PIL import Image

    PILLOW_AVAILABLE = True
except ImportError:
    PILLOW_AVAILABLE = False

mime_suffixes = {
    '.gif': 'image/gif',
    '.jpg': 'image/jpeg',
    '.png': 'image/png',
    '.pdf': 'application/pdf',
    '.svg': 'image/svg+xml',
    '.svgz': 'image/svg+xml',
    '.ai': 'application/illustrator',
    '.webp': 'image/webp',
}
_suffix_from_mime = {v: k for k, v in reversed(mime_suffixes.items())}


class DataURI(NamedTuple):
    mimetype: str
    charset: str
    data: bytes


def get_image_size(filename: str | PathLike[str]) -> tuple[int, int] | None:
    filename = Path(filename)
    try:
        size = imagesize.get(filename)
        if size[0] == -1:
            size = None
        elif isinstance(size[0], float) or isinstance(size[1], float):
            size = (int(size[0]), int(size[1]))

        if size is None and PILLOW_AVAILABLE:  # fallback to Pillow
            with Image.open(filename) as im:
                size = im.size

        return size
    except Exception:
        return None


@overload
def guess_mimetype(filename: PathLike[str] | str, default: str) -> str: ...


@overload
def guess_mimetype(
    filename: PathLike[str] | str, default: None = None
) -> str | None: ...


def guess_mimetype(
    filename: PathLike[str] | str = '',
    default: str | None = None,
) -> str | None:
    filename = Path(filename)
    ext = filename.suffix.lower()
    if ext in mime_suffixes:
        return mime_suffixes[ext]
    if filename.exists():
        try:
            imgtype = _image_type_from_file(filename)
        except ValueError:
            pass
        else:
            return 'image/' + imgtype
    return default


def get_image_extension(mimetype: str) -> str | None:
    return _suffix_from_mime.get(mimetype)


def parse_data_uri(uri: str) -> DataURI | None:
    if not uri.startswith('data:'):
        return None

    if ',' not in uri:
        msg = 'malformed data URI'
        raise ValueError(msg)

    # data:[<MIME-type>][;charset=<encoding>][;base64],<data>
    mimetype = 'text/plain'
    charset = 'US-ASCII'

    uri = uri[5:]
    properties, _, data = uri.partition(',')
    for prop in properties.split(';'):
        if prop == 'base64':
            pass  # skip
        elif prop.lower().startswith('charset='):
            charset = prop[8:]
        elif prop:
            mimetype = prop.lower()

    image_data = unquote_to_bytes(data)  # data might be percent-encoded
    if properties.endswith(';base64'):
        image_data = base64.decodebytes(image_data)
    return DataURI(mimetype, charset, image_data)


def _image_type_from_file(filename: PathLike[str] | str) -> str:
    with open(filename, 'rb') as f:
        header = f.read(32)  # 32 bytes

    # Bitmap
    # https://en.wikipedia.org/wiki/BMP_file_format#Bitmap_file_header
    if header.startswith(b'BM'):
        return 'bmp'

    # GIF
    # https://en.wikipedia.org/wiki/GIF#File_format
    if header.startswith((b'GIF87a', b'GIF89a')):
        return 'gif'

    # JPEG data
    # https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format#File_format_structure
    if header.startswith(b'\xff\xd8'):
        return 'jpeg'

    # Portable Network Graphics
    # https://en.wikipedia.org/wiki/PNG#File_header
    if header.startswith(b'\x89PNG\r\n\x1a\n'):
        return 'png'

    # Scalable Vector Graphics
    # https://svgwg.org/svg2-draft/struct.html
    if b'<svg' in header.lower():
        return 'svg+xml'

    # TIFF
    # https://en.wikipedia.org/wiki/TIFF#Byte_order
    if header.startswith((b'MM', b'II')):
        return 'tiff'

    # WebP
    # https://en.wikipedia.org/wiki/WebP#Technology
    if header.startswith(b'RIFF') and header[8:12] == b'WEBP':
        return 'webp'

    msg = 'Could not detect image type!'
    raise ValueError(msg)
