"""Type stubs for the ``fitsy`` extension module.

These stubs mirror the runtime API exposed by ``src/python/`` and are
maintained by hand. They are consumed by IDEs and static type
checkers; Sphinx autodoc reads the compiled module directly.
"""

from __future__ import annotations

from os import PathLike
from typing import Any, Iterator, Mapping, Optional, Sequence, Union

import numpy as np

__all__ = [
    "FitsError",
    "FitsFile",
    "FitsDiff",
    "Header",
    "HeaderCommentary",
    "ImageHdu",
    "BinTable",
    "AsciiTable",
    "RandomGroups",
    "Wcs",
    "WcsFit",
    "ImageBuilder",
    "BinTableBuilder",
    "AsciiTableBuilder",
    "open",
    "image",
    "compressed_image",
    "bintable",
    "ascii_table",
    "write",
    "append",
    "diff",
    "info",
    "getdata",
    "getheader",
    "getval",
    "setval",
    "delval",
    "fit_wcs",
]

# Path-like accepted by IO functions.
StrPath = Union[str, PathLike[str]]

# Scalar value types appearing in headers.
HeaderScalar = Union[bool, int, float, complex, str, None]

class FitsError(OSError):
    """Base class for all FITS-related errors raised by :mod:`fitsy`."""

class Header:
    """Dict-like view of a FITS header. See :class:`fitsy.Header` docs."""

    @property
    def read_only(self) -> bool: ...
    def __contains__(self, key: str) -> bool: ...
    def __getitem__(
        self, key: Union[str, tuple[str, int]]
    ) -> Union[HeaderScalar, "HeaderCommentary"]: ...
    def __setitem__(
        self,
        key: str,
        value: Union[HeaderScalar, tuple[HeaderScalar, Optional[str]]],
    ) -> None: ...
    def __delitem__(self, key: str) -> None: ...
    def __len__(self) -> int: ...
    def __iter__(self) -> Iterator[str]: ...
    def add_commentary(self, kind: str, text: str) -> None: ...
    def update(self, other: Union["Header", Mapping[str, Any]]) -> None: ...
    def get(self, key: str, default: Any = ...) -> Any: ...
    def keys(self) -> list[str]: ...
    def items(self) -> list[tuple[str, HeaderScalar]]: ...
    def comment(self, key: str) -> Optional[str]: ...
    def cards(self, key: str) -> list[tuple[Any, Optional[str]]]: ...
    def to_dict(self) -> dict[str, HeaderScalar]: ...
    def tostring(self) -> str: ...
    def __bytes__(self) -> bytes: ...
    def set(
        self,
        keyword: str,
        value: HeaderScalar = None,
        comment: Optional[str] = None,
        *,
        before: Optional[str] = None,
        after: Optional[str] = None,
    ) -> None: ...
    def insert(
        self,
        position: Union[int, str],
        keyword: str,
        value: HeaderScalar = None,
        comment: Optional[str] = None,
        *,
        after: bool = False,
    ) -> None: ...
    def rename_keyword(self, oldname: str, newname: str) -> None: ...

class HeaderCommentary:
    """List-like view of repeated commentary cards (COMMENT, HISTORY, blank)."""

    def __len__(self) -> int: ...
    def __getitem__(self, idx: int) -> str: ...
    def __iter__(self) -> Iterator[str]: ...
    def __str__(self) -> str: ...

class ImageHdu:
    """Image HDU. Construct from a numpy array, or get from :meth:`FitsFile.hdu`."""

    def __init__(
        self,
        data: np.ndarray,
        header: Optional[Union[Header, Mapping[str, Any]]] = None,
        name: Optional[str] = None,
    ) -> None: ...
    @property
    def header(self) -> Header: ...
    @property
    def axes(self) -> list[int]: ...
    @property
    def bitpix(self) -> int: ...
    @property
    def data(self) -> Optional[np.ndarray]: ...
    @data.setter
    def data(self, value: Optional[np.ndarray]) -> None: ...
    @property
    def section(self) -> "_ImageSection": ...
    def wcs(self, alt: str = " ") -> Optional[Wcs]: ...

class _ImageSection:
    """Lazy slicing view over an :class:`ImageHdu` data array.

    Internal type. Acquire instances via :attr:`ImageHdu.section`;
    do not construct directly. The leading underscore signals that
    the class itself is not part of the public API surface, only the
    ``__getitem__`` / ``__setitem__`` protocol it implements is.

    For files opened with ``mode='update'``, ``section[key] = value``
    writes only the affected pixel bytes to disk via positional
    ``pwrite`` -- O(patch) work, no full-image rewrite.
    """

    def __getitem__(self, key: Any) -> np.ndarray: ...
    def __setitem__(self, key: Any, value: Any) -> None: ...
    def __repr__(self) -> str: ...

class BinTable:
    """Binary table HDU."""

    @property
    def header(self) -> Header: ...
    @property
    def n_rows(self) -> int: ...
    @property
    def column_names(self) -> list[str]: ...
    @property
    def data(self) -> Any: ...
    def column(self, name: str) -> Any: ...
    def to_dict(self) -> dict[str, Any]: ...
    def __getitem__(self, key: Any) -> Any: ...
    def __len__(self) -> int: ...
    def __contains__(self, name: str) -> bool: ...
    def __iter__(self) -> Any: ...

class AsciiTable:
    """ASCII ``TABLE`` HDU."""

    @property
    def header(self) -> Header: ...
    @property
    def n_rows(self) -> int: ...
    @property
    def column_names(self) -> list[str]: ...
    @property
    def data(self) -> Any: ...
    def column(self, name: str) -> Any: ...
    def to_dict(self) -> dict[str, Any]: ...
    def __getitem__(self, key: Any) -> Any: ...
    def __len__(self) -> int: ...
    def __contains__(self, name: str) -> bool: ...
    def __iter__(self) -> Any: ...

class RandomGroups:
    """Random-groups HDU (legacy radio interferometry format)."""

    @property
    def header(self) -> Header: ...
    @property
    def gcount(self) -> int: ...
    @property
    def pcount(self) -> int: ...
    @property
    def parameter_names(self) -> list[str]: ...
    def parameter(self, index_or_name: Union[int, str]) -> np.ndarray: ...
    def data(self) -> np.ndarray: ...

Hdu = Union[ImageHdu, BinTable, AsciiTable, RandomGroups]

class FitsFile:
    """Mutable, ordered list of HDUs. See :func:`open`."""

    def __init__(self) -> None: ...
    @property
    def read_only(self) -> bool: ...
    def __len__(self) -> int: ...
    def __enter__(self) -> "FitsFile": ...
    def __exit__(self, exc_type: Any, exc: Any, tb: Any) -> None: ...
    def __iter__(self) -> Iterator[Hdu]: ...
    def __getitem__(self, index: Union[int, str, tuple[str, int]]) -> Hdu: ...
    def __setitem__(self, index: int, value: Union[Hdu, Builder]) -> None: ...
    def __delitem__(self, index: int) -> None: ...
    def append(self, value: Union[Hdu, Builder]) -> None: ...
    def insert(self, index: int, value: Union[Hdu, Builder]) -> None: ...
    def hdu(self, index: int) -> Hdu: ...
    def hdu_by_name(self, name: str, ver: Optional[int] = None) -> Hdu: ...
    def wcs(self, index: int = 0, alt: str = " ") -> Optional[Wcs]: ...
    def writeto(self, path: StrPath, overwrite: bool = False) -> None: ...
    def verify_checksums(self) -> list[dict[str, Optional[bool] | int]]: ...
    def add_checksums(self) -> None: ...
    def flush(self) -> None: ...
    def close(self) -> None: ...

class Wcs:
    """World Coordinate System."""

    def __init__(self, header: Header, alt: str = " ") -> None: ...
    @property
    def naxis(self) -> int: ...
    @property
    def ctype(self) -> list[str]: ...
    @property
    def cunit(self) -> list[str]: ...
    @property
    def crval(self) -> list[float]: ...
    @property
    def is_celestial(self) -> bool: ...
    def celestial_axes(self) -> Optional[tuple[int, int]]: ...
    def pixel_to_world(self, pix: Sequence[float], origin: int = 0) -> list[float]: ...
    def world_to_pixel(
        self, world: Sequence[float], origin: int = 0
    ) -> list[float]: ...
    def pixel_to_celestial(
        self, px: float, py: float, origin: int = 0
    ) -> tuple[float, float]: ...
    def celestial_to_pixel(
        self, ra: float, dec: float, origin: int = 0
    ) -> tuple[float, float]: ...
    def pixel_scale_at(
        self, px: float, py: float, origin: int = 0
    ) -> tuple[float, float]: ...
    def pixel_to_celestial_many(
        self, pixels: np.ndarray, origin: int = 0
    ) -> np.ndarray: ...
    def celestial_to_pixel_many(
        self, sky: np.ndarray, origin: int = 0
    ) -> np.ndarray: ...
    def to_header(self, alt: str = " ") -> Header: ...

class WcsFit:
    """Result of :func:`fit_wcs`."""

    @property
    def wcs(self) -> Wcs: ...
    @property
    def rms_arcsec(self) -> float: ...
    @property
    def max_arcsec(self) -> float: ...
    @property
    def residuals_arcsec(self) -> np.ndarray: ...

class ImageBuilder:
    """Opaque image-HDU spec; see :func:`image`."""

class BinTableBuilder:
    """Opaque BINTABLE-HDU spec; see :func:`bintable`."""

class AsciiTableBuilder:
    """Opaque ASCII-table HDU spec; see :func:`ascii_table`."""

Builder = Union[ImageBuilder, BinTableBuilder, AsciiTableBuilder]

def open(
    path: StrPath,
    mode: str = "readonly",
    lenient: bool = False,
) -> FitsFile: ...
def image(
    data: np.ndarray,
    header: Optional[Mapping[str, Any]] = None,
    primary: bool = True,
) -> ImageBuilder: ...
def compressed_image(
    data: np.ndarray,
    header: Optional[Mapping[str, Any]] = None,
    algorithm: str = "RICE_1",
    tile_size: Optional[Sequence[int]] = None,
    quantize_level: Optional[float] = None,
) -> ImageBuilder: ...
def bintable(
    columns: Mapping[str, Any],
    units: Optional[Mapping[str, str]] = None,
    extname: Optional[str] = None,
) -> BinTableBuilder: ...
def ascii_table(
    columns: Mapping[str, Any],
    formats: Optional[Mapping[str, str]] = None,
    tnulls: Optional[Mapping[str, str]] = None,
    units: Optional[Mapping[str, str]] = None,
    extname: Optional[str] = None,
) -> AsciiTableBuilder: ...
def write(
    path: StrPath,
    hdus: Sequence[Builder],
    overwrite: bool = False,
    *,
    checksums: bool = False,
) -> None: ...
def append(path: StrPath, hdu: Builder) -> None: ...
def info(path: StrPath) -> str: ...
def getdata(
    path: StrPath,
    ext: Union[int, str] = 0,
    *,
    header: bool = False,
) -> Any: ...
def getheader(
    path: StrPath,
    ext: Union[int, str] = 0,
) -> Header: ...
def getval(
    path: StrPath,
    keyword: str,
    ext: Union[int, str] = 0,
) -> HeaderScalar: ...
def setval(
    path: StrPath,
    keyword: str,
    value: HeaderScalar = None,
    comment: Optional[str] = None,
    ext: Union[int, str] = 0,
) -> None: ...
def delval(
    path: StrPath,
    keyword: str,
    ext: Union[int, str] = 0,
) -> None: ...

class FitsDiff:
    """Result of comparing two FITS files; see :func:`diff`."""

    @property
    def identical(self) -> bool: ...
    @property
    def hdu_counts(self) -> tuple[int, int]: ...
    @property
    def diff_hdu_count(self) -> int: ...
    def diff_hdu_indices(self) -> list[int]: ...
    def report(self) -> str: ...
    def __str__(self) -> str: ...
    def __repr__(self) -> str: ...
    def __bool__(self) -> bool: ...

def diff(
    a: StrPath,
    b: StrPath,
    *,
    rtol: float = 0.0,
    max_diffs: int = 10,
    ignore_keywords: Optional[Sequence[str]] = None,
) -> FitsDiff: ...
def fit_wcs(
    pixels: np.ndarray,
    sky: np.ndarray,
    projection: str = "TAN",
    crpix: Optional[tuple[float, float]] = None,
    crval: Optional[tuple[float, float]] = None,
    sip_order: Optional[int] = None,
    fit_sip_inverse: bool = True,
    frame: str = "equatorial",
    origin: int = 0,
) -> WcsFit: ...
