"""Custom helpers for the Handlebars template engine.

## Key helpers:

| Helper         | Description                                         |
|----------------|-----------------------------------------------------|
| `history`      | Create a dotprompt history marker.                  |
| `ifEquals`     | Compare two values and return content if equal.     |
| `json`         | Convert a value to a JSON string.                   |
| `media`        | Create a dotprompt media marker.                    |
| `role`         | Create a dotprompt role marker.                     |
| `section`      | Create a dotprompt section marker.                  |
| `unlessEquals` | Compare two values and return content unless equal. |

## Convenience functions:

| Function               | Description                               |
|------------------------|-------------------------------------------|
| `register_all_helpers` | Registers all the helpers in this module. |

"""

import json
from typing import Any

from handlebarrz import Handlebars, HelperFn, HelperOptions


def _strip_auto_vars(obj: Any) -> Any:  # noqa: ANN401
    """Recursively strip auto-injected UPPERCASE keys from dicts.

    Auto-variables use fully UPPERCASE keys (e.g. ``NAME``, ``TIME``,
    ``SCHEMA``).  When ``{{json this}}`` serializes the full context,
    these system keys should be excluded so only user data appears.

    Args:
        obj: The value to clean.

    Returns:
        A copy of *obj* with uppercase dict keys removed.
    """
    if isinstance(obj, dict):
        return {k: _strip_auto_vars(v) for k, v in obj.items() if not (isinstance(k, str) and k.isupper())}
    if isinstance(obj, list):
        return [_strip_auto_vars(item) for item in obj]
    return obj


def json_helper(params: list[Any], options: HelperOptions) -> str:
    """Convert a value to a JSON string.

    Auto-injected UPPERCASE keys (``NAME``, ``TIME``, ``SCHEMA``, etc.)
    are automatically filtered out of dict values so that
    ``{{json this}}`` only serializes user-provided data.

    Args:
        params: List of values to convert to JSON
        options: Handlebars helper options.

    Returns:
        JSON string representation of the value.

    Raises:
        TypeError: If the value cannot be serialized to JSON.
        ValueError: If the value contains invalid data for JSON serialization.
    """
    if not params or len(params) < 1:
        return ''

    obj = _strip_auto_vars(params[0])
    indent = options.hash_value('indent') or 0
    try:
        if isinstance(indent, str):
            indent = int(indent)
    except (ValueError, TypeError):
        indent = 0

    # Let serialization errors propagate like JS JSON.stringify
    if indent == 0:
        return json.dumps(obj, separators=(',', ':'), ensure_ascii=False)
    return json.dumps(obj, indent=indent, ensure_ascii=False)


def role_helper(params: list[Any], options: HelperOptions) -> str:
    """Create a dotprompt role marker.

    Example:
        ```handlebars
        {{role "system"}}
        ```

    Args:
        params: List of values.
        options: Handlebars helper options.

    Returns:
        Role marker of the form `<<<dotprompt:role:...>>>`.
    """
    if not params or len(params) < 1:
        return ''

    role_name = str(params[0])
    return f'<<<dotprompt:role:{role_name}>>>'


def history_helper(params: list[Any], options: HelperOptions) -> str:
    """Create a dotprompt history marker.

    Example:
        ```handlebars
        {{history}}
        ```

    Args:
        params: List of values.
        options: Handlebars helper options.

    Returns:
        History marker of the form `<<<dotprompt:history>>>`.
    """
    return '<<<dotprompt:history>>>'


def section_helper(params: list[Any], options: HelperOptions) -> str:
    """Create a dotprompt section marker.

    Example:
        ```handlebars
        {{section "name"}}
        ```

    Args:
        params: List of values.
        options: Handlebars helper options.

    Returns:
        Section marker of the form `<<<dotprompt:section ...>>>`.
    """
    if not params or len(params) < 1:
        return ''

    section_name = str(params[0])
    return f'<<<dotprompt:section {section_name}>>>'


def media_helper(params: list[Any], options: HelperOptions) -> str:
    """Create a dotprompt media marker.

    Example:
        ```handlebars
        {{media url="https://example.com/image.png" contentType="image/png"}}
        ```

    Args:
        params: List of values.
        options: Handlebars helper options.

    Returns:
        Media marker of the form `<<<dotprompt:media:url ...>>>`).
    """
    url = options.hash_value('url')
    if not url:
        return ''

    content_type = options.hash_value('contentType')
    if content_type:
        return f'<<<dotprompt:media:url {url} {content_type}>>>'
    else:
        return f'<<<dotprompt:media:url {url}>>>'


def if_equals_helper(params: list[Any], options: HelperOptions) -> str:
    """Compares two values and returns appropriate content.

    Example:
        ```handlebars
        {{#ifEquals arg1 arg2}}
            <p>arg1 is equal to arg2</p>
        {{else}}
            <p>arg1 is not equal to arg2</p>
        {{/ifEquals}}
        ```
    Args:
        params: List containing the two values to compare.
        options: Handlebars helper options.

    Returns:
        Rendered content based on equality check.
    """
    if len(params) < 2:
        return ''

    a, b = params[0], params[1]
    return options.fn() if a == b else options.inverse()


def unless_equals_helper(params: list[Any], options: HelperOptions) -> str:
    """Compares two values and returns appropriate content.

    Example:
        ```handlebars
        {{#unlessEquals arg1 arg2}}
            <p>arg1 is not equal to arg2</p>
        {{else}}
            <p>arg1 is equal to arg2</p>
        {{/unlessEquals}}
        ```
    Args:
        params: List containing the two values to compare.
        options: Handlebars helper options.

    Returns:
        Rendered content based on inequality check.
    """
    if len(params) < 2:
        return ''

    a, b = params[0], params[1]
    return options.fn() if a != b else options.inverse()


BUILTIN_HELPERS: dict[str, HelperFn] = {
    'history': history_helper,
    'ifEquals': if_equals_helper,
    'json': json_helper,
    'media': media_helper,
    'role': role_helper,
    'section': section_helper,
    'unlessEquals': unless_equals_helper,
}


def register_all_helpers(handlebars: Handlebars) -> None:
    """Register all builtin helpers with the handlebars instance.

    Args:
        handlebars: An instance of the Handlebars template engine.

    Returns:
        None.
    """
    for name, fn in BUILTIN_HELPERS.items():
        handlebars.register_helper(name, fn)
