"""Language state base class.

Provides singleton-cached access to expensive computations that
are shared across an optimization session: parsed ASTs, reference
graphs, discovered functions, etc.

Follows the platform-libs pattern: ``AbstractCloudState`` holds
shared singletons (resource groups, storage accounts) that
multiple cloud components need.  Here, ``LanguageState`` holds
shared analysis artifacts that multiple pipeline steps need.

Usage from a language package::

    from codeflash_core import LanguageState

    @attrs.define
    class PythonState(LanguageState[PythonConfiguration]):
        _reference_graph: ReferenceGraph | None = None

        def reference_graph(self) -> ReferenceGraph:
            if self._reference_graph is None:
                self._reference_graph = build_reference_graph(
                    self.cfg.project_root,
                )
            return self._reference_graph

The state is mutable (``@attrs.define``) because singletons are
lazily populated.  It is *not* thread-safe — each optimization
session should have its own state instance.
"""

from __future__ import annotations

import logging
from typing import Generic, TypeVar

import attrs

from ._configuration import LanguageConfiguration

log = logging.getLogger(__name__)

CFG_T = TypeVar("CFG_T", bound=LanguageConfiguration)


@attrs.define
class LanguageState(Generic[CFG_T]):
    """Base state for a language optimization session.

    Carries the session configuration and a generic singleton
    cache.  Language packages subclass this to add typed
    singleton accessors for their analysis artifacts.

    The ``_singletons`` dict is a fallback for ad-hoc caching;
    prefer typed attributes with lazy accessors (see module
    docstring) for anything that's part of the language's
    public contract.
    """

    cfg: CFG_T
    """The configuration for this session."""

    _singletons: dict[str, object] = attrs.Factory(dict)
    """Generic cache for singleton objects.

    Language subclasses should prefer typed attributes, but
    this dict is available for one-off caching that doesn't
    warrant a dedicated field.
    """

    def get_singleton(self, key: str) -> object | None:
        """Look up a cached singleton by *key*."""
        return self._singletons.get(key)

    def set_singleton(self, key: str, value: object) -> None:
        """Cache a singleton under *key*."""
        self._singletons[key] = value

    def close(self) -> None:
        """Release resources held by cached singletons.

        Subclasses should override to close database connections,
        file handles, etc.  Always call ``super().close()``.
        """
        self._singletons.clear()
        log.debug("LanguageState closed for %s", type(self.cfg).__name__)
