"""File-type handlers for inbound MFT files (SP27).

Each handler is a pure function that parses + persists + dispatches
events for one file type. The scheduler and the FastAPI endpoints
both delegate here; the ``HANDLERS`` registry maps ``file_type`` →
handler function.

Public API:
    HandleResult          — dataclass returned by every handler
    HANDLERS              — ``{"999": handle_999, "835": handle_835, ...}``
    handle_999, handle_ta1, handle_277ca, handle_835
        — call signatures: ``handle(text: str, source_file: str) -> HandleResult``

The handlers own their own DB session lifecycle. They emit pubsub
events via the optional ``event_bus`` parameter (the FastAPI endpoint
injects ``app.state.event_bus``; the scheduler passes ``None``).
They never raise on per-segment problems; per-segment issues are
logged and folded into the result. Whole-document failures
(missing ISA, bad encoding) surface as ``CycloneParseError``, which
the caller catches and records as ``STATUS_ERROR``.
"""
from __future__ import annotations

import importlib
import logging

from ._ack_id import (
    ack_count_summary,
    ack_synthetic_source_batch_id,
    two77ca_synthetic_source_batch_id,
)
from .handle_result import HandleResult

log = logging.getLogger(__name__)

# Module-level HANDLERS dict populated lazily once handler modules
# ship. Keys are file_type strings, values are the ``handle``
# callable for that type.
HANDLERS: dict[str, object] = {}


# Candidate handlers, in registration order. Each tuple is
# (module-path, file_type). The 277/277CA mapping is added explicitly
# after registration when the 277CA handler is present.
_CANDIDATES: list[tuple[str, str]] = [
    ("cyclone.handlers.handle_999", "999"),
    ("cyclone.handlers.handle_ta1", "TA1"),
    ("cyclone.handlers.handle_277ca", "277CA"),
    ("cyclone.handlers.handle_835", "835"),
]


def register_handlers() -> None:
    """Populate ``HANDLERS`` from the per-type handler modules.

    Tolerates missing or broken handler modules so the package can be
    imported incrementally as each handler ships (Tasks 2-5), and so
    a partial-modification error in one handler module can't break
    scheduler / API import. Safe to call multiple times.
    """
    if HANDLERS:
        return
    for mod_path, file_type in _CANDIDATES:
        try:
            mod = importlib.import_module(mod_path)
            fn = getattr(mod, "handle")
        except Exception as exc:  # noqa: BLE001 — best-effort registry
            log.debug(
                "handler %s unavailable: %s",
                mod_path, exc,
            )
            continue
        HANDLERS[file_type] = fn
        # The 277 filename maps to the same 277CA handler.
        if file_type == "277CA":
            HANDLERS["277"] = fn


register_handlers()


# Re-export handler functions on this package so callers can use the
# flat import (``from cyclone.handlers import handle_999``) once each
# module ships. Set after registration so we know what's present.
def _reexport_handlers() -> None:
    """Re-export each handler's ``handle`` fn as ``handle_<type>``.

    No-op for absent handlers. Re-run on each import so freshly-
    installed handler modules (e.g. Tasks 2-5 commits) are visible
    after ``register_handlers()`` without a process restart.
    """
    for file_type, fn in list(HANDLERS.items()):
        if file_type in ("277",):  # alias of 277CA; don't re-export twice
            continue
        globals()[f"handle_{file_type.lower()}"] = fn


_reexport_handlers()


__all__ = [
    "HANDLERS",
    "HandleResult",
    "register_handlers",
    "ack_count_summary",
    "ack_synthetic_source_batch_id",
    "two77ca_synthetic_source_batch_id",
    # handler_* names are added at module load via _reexport_handlers
]
# Populate __all__ with the present handler symbols.
for _h in ("handle_999", "handle_ta1", "handle_277ca", "handle_835"):
    if _h in globals():
        __all__.append(_h)  # noqa: PYI056
