from __future__ import annotations

import dataclasses
import typing as t

from ansible.module_utils._internal import _ambient_context, _datatag

if t.TYPE_CHECKING:
    from ._engine import TemplateEngine, TemplateOptions


@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
class LazyOptions:
    """Templating options that apply to lazy containers, which are inherited by descendent lazy containers."""

    DEFAULT: t.ClassVar[t.Self]
    """A shared instance with the default options to minimize instance creation for arg defaults."""
    SKIP_TEMPLATES: t.ClassVar[t.Self]
    """A shared instance with only `template=False` set to minimize instance creation for arg defaults."""
    SKIP_TEMPLATES_AND_ACCESS: t.ClassVar[t.Self]
    """A shared instance with both `template=False` and `access=False` set to minimize instance creation for arg defaults."""

    template: bool = True
    """Enable/disable templating."""

    access: bool = True
    """Enable/disables access calls."""

    unmask_type_names: frozenset[str] = frozenset()
    """Disables template transformations for the provided type names."""


LazyOptions.DEFAULT = LazyOptions()
LazyOptions.SKIP_TEMPLATES = LazyOptions(template=False)
LazyOptions.SKIP_TEMPLATES_AND_ACCESS = LazyOptions(template=False, access=False)


class TemplateContext(_ambient_context.AmbientContextBase):
    def __init__(
        self,
        *,
        template_value: t.Any,
        templar: TemplateEngine,
        options: TemplateOptions,
        stop_on_template: bool = False,
        _render_jinja_const_template: bool = False,
    ):
        self._template_value = template_value
        self._templar = templar
        self._options = options
        self._stop_on_template = stop_on_template
        self._parent_ctx = TemplateContext.current(optional=True)
        self._render_jinja_const_template = _render_jinja_const_template

    @property
    def is_top_level(self) -> bool:
        return not self._parent_ctx

    @property
    def template_value(self) -> t.Any:
        return self._template_value

    @property
    def templar(self) -> TemplateEngine:
        return self._templar

    @property
    def options(self) -> TemplateOptions:
        return self._options

    @property
    def stop_on_template(self) -> bool:
        return self._stop_on_template


class _OmitType:
    """
    A placeholder singleton used to dynamically omit items from a dict/list/tuple/set when the value is `Omit`.

    The `Omit` singleton is accessible from all Ansible templating contexts via the Jinja global name `omit`.
    The `Omit` placeholder value will be visible to Jinja plugins during templating.
    Jinja plugins requiring omit behavior are responsible for handling encountered `Omit` values.
    `Omit` values remaining in template results will be automatically dropped during template finalization.
    When a finalized template renders to a scalar `Omit`, `AnsibleValueOmittedError` will be raised.
    Passing a value other than `Omit` for `value_for_omit` to the `template` call allows that value to be substituted instead of raising.
    """

    __slots__ = ()

    def __new__(cls):
        return Omit

    def __repr__(self):
        return "<<Omit>>"


Omit = object.__new__(_OmitType)

_datatag._untaggable_types.add(_OmitType)


IGNORE_SCALAR_VAR_TYPES = {value for value in _datatag._ANSIBLE_ALLOWED_SCALAR_VAR_TYPES if not issubclass(value, str)}
"""Scalar variable types that short-circuit bypass templating."""

PASS_THROUGH_SCALAR_VAR_TYPES = _datatag._ANSIBLE_ALLOWED_SCALAR_VAR_TYPES | {
    _OmitType,  # allow pass through of omit for later handling after top-level finalize completes
}
"""Scalar variable types which are allowed to appear in finalized template results."""
