"""
pint.formatter
~~~~~~~~~~~~~~

Format units for pint.

:copyright: 2016 by Pint Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""

from __future__ import annotations

from collections.abc import Iterable
from numbers import Number

from .delegates.formatter._format_helpers import (
    _PRETTY_EXPONENTS,  # noqa: F401
)
from .delegates.formatter._format_helpers import (
    join_u as _join,  # noqa: F401
)
from .delegates.formatter._format_helpers import (
    pretty_fmt_exponent as _pretty_fmt_exponent,  # noqa: F401
)
from .delegates.formatter._spec_helpers import (
    _BASIC_TYPES,  # noqa: F401
    FORMATTER,  # noqa: F401
    REGISTERED_FORMATTERS,
    extract_custom_flags,  # noqa: F401
    remove_custom_flags,  # noqa: F401
)
from .delegates.formatter._spec_helpers import (
    parse_spec as _parse_spec,  # noqa: F401
)
from .delegates.formatter._spec_helpers import (
    split_format as split_format,  # noqa: F401
)

# noqa
from .delegates.formatter._to_register import register_unit_format  # noqa: F401

# Backwards compatiblity stuff
from .delegates.formatter.latex import (
    _EXP_PATTERN,  # noqa: F401
    latex_escape,  # noqa: F401
    matrix_to_latex,  # noqa: F401
    ndarray_to_latex,  # noqa: F401
    ndarray_to_latex_parts,  # noqa: F401
    siunitx_format_unit,  # noqa: F401
    vector_to_latex,  # noqa: F401
)


def formatter(
    items: Iterable[tuple[str, Number]],
    as_ratio: bool = True,
    single_denominator: bool = False,
    product_fmt: str = " * ",
    division_fmt: str = " / ",
    power_fmt: str = "{} ** {}",
    parentheses_fmt: str = "({0})",
    exp_call: FORMATTER = "{:n}".format,
    sort: bool = True,
) -> str:
    """Format a list of (name, exponent) pairs.

    Parameters
    ----------
    items : list
        a list of (name, exponent) pairs.
    as_ratio : bool, optional
        True to display as ratio, False as negative powers. (Default value = True)
    single_denominator : bool, optional
        all with terms with negative exponents are
        collected together. (Default value = False)
    product_fmt : str
        the format used for multiplication. (Default value = " * ")
    division_fmt : str
        the format used for division. (Default value = " / ")
    power_fmt : str
        the format used for exponentiation. (Default value = "{} ** {}")
    parentheses_fmt : str
        the format used for parenthesis. (Default value = "({0})")
    exp_call : callable
         (Default value = lambda x: f"{x:n}")
    sort : bool, optional
        True to sort the formatted units alphabetically (Default value = True)

    Returns
    -------
    str
        the formula as a string.

    """

    join_u = _join

    if sort is False:
        items = tuple(items)
    else:
        items = sorted(items)

    if not items:
        return ""

    if as_ratio:
        fun = lambda x: exp_call(abs(x))
    else:
        fun = exp_call

    pos_terms, neg_terms = [], []

    for key, value in items:
        if value == 1:
            pos_terms.append(key)
        elif value > 0:
            pos_terms.append(power_fmt.format(key, fun(value)))
        elif value == -1 and as_ratio:
            neg_terms.append(key)
        else:
            neg_terms.append(power_fmt.format(key, fun(value)))

    if not as_ratio:
        # Show as Product: positive * negative terms ** -1
        return _join(product_fmt, pos_terms + neg_terms)

    # Show as Ratio: positive terms / negative terms
    pos_ret = _join(product_fmt, pos_terms) or "1"

    if not neg_terms:
        return pos_ret

    if single_denominator:
        neg_ret = join_u(product_fmt, neg_terms)
        if len(neg_terms) > 1:
            neg_ret = parentheses_fmt.format(neg_ret)
    else:
        neg_ret = join_u(division_fmt, neg_terms)

    # TODO: first or last pos_ret should be pluralized

    return _join(division_fmt, [pos_ret, neg_ret])


def format_unit(unit, spec: str, registry=None, **options):
    # registry may be None to allow formatting `UnitsContainer` objects
    # in that case, the spec may not be "Lx"

    if not unit:
        if spec.endswith("%"):
            return ""
        else:
            return "dimensionless"

    if not spec:
        spec = "D"

    if registry is None:
        _formatter = REGISTERED_FORMATTERS.get(spec, None)
    else:
        try:
            _formatter = registry.formatter._formatters[spec]
        except Exception:
            _formatter = registry.formatter._formatters.get(spec, None)

    if _formatter is None:
        raise ValueError(f"Unknown conversion specified: {spec}")

    return _formatter.format_unit(unit)
