"""Read ``hooks.*`` from ``.agent-settings.yml`` into :class:`HookSettings`.

Mirror of the chat-history settings pattern (``scripts/command_suggester/
settings.py``):

* Lazy PyYAML import — the engine works without yaml installed when no
  settings file is present (test fixtures, cloud bundles).
* Default-permissive: a missing file or missing ``hooks:`` block returns
  :class:`HookSettings` with ``enabled=False`` — every hook off, every
  golden replay safe by construction.
* Malformed YAML / unreadable file → defaults; the engine degrades
  silently rather than crashing the CLI.
* Chat-history hooks gate on **two** switches: ``hooks.chat_history.
  enabled`` AND the global ``chat_history.enabled``. Either off → no
  chat-history hook registers.

Per road-to-portable-dev-preferences P3, the YAML read goes through
:func:`work_engine._lib.agent_settings.load_agent_settings`, which
cascades the whitelisted ``rule_loading_tier`` (and other DX-comfort keys)
from ``~/.event4u/agent-config/agent-settings.yml`` (legacy
``~/.config/agent-config/agent-settings.yml`` read as fallback) when
the project file omits them. Project values always win.
"""
from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from typing import Any

from work_engine._lib.agent_settings import load_agent_settings
from work_engine.scoring.decision_engine import (
    DecisionEngineConfigError,
    DecisionEngineSettings,
    parse as _parse_decision_engine,
)

DEFAULT_SETTINGS_FILE = ".agent-settings.yml"
DEFAULT_CHAT_HISTORY_SCRIPT = "scripts/chat_history.py"


@dataclass(frozen=True)
class HookSettings:
    """Resolved view of the ``hooks:`` block.

    ``enabled`` is the master switch. When ``False`` the registry stays
    empty regardless of the per-hook fields; this is the default when no
    settings file exists or no ``hooks`` block is declared, and it is
    what keeps golden-replay tests byte-stable.

    ``decision_engine`` carries the parsed gate config so
    :class:`DecisionGateHook` can read it without re-parsing
    ``.agent-settings.yml``. When the block is absent or malformed the
    field stays at the default (every gate ``off``).
    """

    enabled: bool = False
    trace: bool = False
    halt_surface_audit: bool = False
    state_shape_validation: bool = False
    directive_set_guard: bool = False
    decision_trace: bool = False
    memory_visibility: bool = False
    memory_visibility_off: bool = False
    memory_cadence: str = "always"
    chat_history_enabled: bool = False
    chat_history_script: str = DEFAULT_CHAT_HISTORY_SCRIPT
    decision_engine: DecisionEngineSettings = DecisionEngineSettings()


_DEFAULT = HookSettings()


def load_hook_settings(
    settings_path: Path | str | None = None,
    user_global_path: Path | str | None = None,
) -> HookSettings:
    """Return :class:`HookSettings` hydrated from ``.agent-settings.yml``.

    ``settings_path`` defaults to ``./.agent-settings.yml`` relative to
    the current working directory — same convention as chat-history.
    ``user_global_path`` defaults to
    ``~/.event4u/agent-config/agent-settings.yml`` (with a read fallback
    to the legacy ``~/.config/agent-config/agent-settings.yml``) and
    only cascades the whitelisted DX-comfort keys (currently
    ``rule_loading_tier`` and ``memory.cadence``) when the project file
    omits them. See road-to-portable-dev-preferences P3.
    """
    path = Path(settings_path) if settings_path else Path(DEFAULT_SETTINGS_FILE)
    raw = load_agent_settings(
        project_path=path,
        user_global_path=user_global_path,
    )
    if not raw:
        return _DEFAULT
    return _settings_from_raw(raw)


def _settings_from_raw(data: dict[str, Any]) -> HookSettings:
    hooks = data.get("hooks")
    if not isinstance(hooks, dict):
        return _DEFAULT
    enabled = _coerce_bool(hooks.get("enabled"), False)

    decision_engine_raw = data.get("decision_engine")
    try:
        decision_engine_settings = _parse_decision_engine(decision_engine_raw)
    except DecisionEngineConfigError:
        decision_engine_settings = DecisionEngineSettings()

    if not enabled:
        return HookSettings(
            enabled=False,
            decision_engine=decision_engine_settings,
        )

    chat_section = hooks.get("chat_history")
    if isinstance(chat_section, dict):
        chat_block_enabled = _coerce_bool(chat_section.get("enabled"), True)
        chat_script = str(
            chat_section.get("script") or DEFAULT_CHAT_HISTORY_SCRIPT
        )
    else:
        chat_block_enabled = True
        chat_script = DEFAULT_CHAT_HISTORY_SCRIPT

    global_chat = data.get("chat_history")
    global_chat_on = (
        isinstance(global_chat, dict)
        and _coerce_bool(global_chat.get("enabled"), False)
    )

    decision_trace_on = decision_engine_settings.surface_traces

    memory_section = data.get("memory")
    visibility_off = False
    memory_cadence = "always"
    if isinstance(memory_section, dict):
        raw = memory_section.get("visibility")
        if isinstance(raw, str) and raw.strip().lower() == "off":
            visibility_off = True
        elif isinstance(raw, bool) and raw is False:
            visibility_off = True
        cadence_raw = memory_section.get("cadence")
        if cadence_raw is not None:
            memory_cadence = str(cadence_raw).strip().lower() or "always"

    memory_hooks = hooks.get("memory_visibility")
    if isinstance(memory_hooks, dict):
        memory_visibility_on = _coerce_bool(
            memory_hooks.get("enabled"), True,
        )
    else:
        memory_visibility_on = True

    return HookSettings(
        enabled=True,
        trace=_coerce_bool(hooks.get("trace"), False),
        halt_surface_audit=_coerce_bool(
            hooks.get("halt_surface_audit"), True
        ),
        state_shape_validation=_coerce_bool(
            hooks.get("state_shape_validation"), True
        ),
        directive_set_guard=_coerce_bool(
            hooks.get("directive_set_guard"), True
        ),
        decision_trace=decision_trace_on,
        memory_visibility=memory_visibility_on,
        memory_visibility_off=visibility_off,
        memory_cadence=memory_cadence,
        chat_history_enabled=chat_block_enabled and global_chat_on,
        chat_history_script=chat_script,
        decision_engine=decision_engine_settings,
    )


def _coerce_bool(value: Any, default: bool) -> bool:
    if isinstance(value, bool):
        return value
    if value is None:
        return default
    if isinstance(value, str):
        s = value.strip().lower()
        if s in ("true", "yes", "on", "1"):
            return True
        if s in ("false", "no", "off", "0"):
            return False
    return default


__all__ = [
    "DEFAULT_CHAT_HISTORY_SCRIPT",
    "DEFAULT_SETTINGS_FILE",
    "HookSettings",
    "load_hook_settings",
]
