"""ExecutePhaseDecisionHook — decision recording for the Execute phase.

Mirrors :class:`StrategizeDecisionHook` but for the Execute-phase decision
types: implementation choices, tool invocations, error recovery, validation
responses, subplan spawn, subplan parallel spawn, and resource selection.

Based on:
    - Forgejo Epic #8477 (Decision Recording & Persistence)
    - docs/adr/ADR-033-decision-recording-protocol.md
"""

from __future__ import annotations

from typing import Any

import structlog

from cleveragents.application.ports.decision_recorder import DecisionRecorder
from cleveragents.application.services.decision_context import capture_context_snapshot
from cleveragents.core.exceptions import ValidationError
from cleveragents.domain.models.core.decision import (
    Decision,
    DecisionType,
)
from cleveragents.domain.models.core.plan import PlanPhase

logger = structlog.get_logger(__name__)


class ExecutePhaseDecisionHook:
    """Hook for recording decisions during the Execute phase.

    Integrates with the execute actor to capture every decision point,
    including implementation choices, tool invocations, error recovery,
    and resource selection decisions made during execution.
    """

    def __init__(
        self,
        decision_service: DecisionRecorder,
        plan_id: str,
        parent_decision_id: str | None = None,
    ) -> None:
        if not plan_id or not plan_id.strip():
            raise ValidationError("plan_id must not be empty")

        self.decision_service = decision_service
        self.plan_id = plan_id
        self.parent_decision_id = parent_decision_id
        self._logger = logger.bind(hook="execute_decision", plan_id=plan_id)

    def _record(
        self,
        *,
        decision_type: DecisionType,
        log_label: str,
        question: str,
        chosen_option: str,
        alternatives_considered: list[str] | None,
        confidence_score: float | None,
        rationale: str,
        context_data: dict[str, Any] | None,
        actor_state: dict[str, Any] | None,
        relevant_resources: list[str] | None,
    ) -> Decision:
        """Shared validate/snapshot/record/log path for every record_* method.

        Centralising the pre/post boilerplate keeps the public surface
        slim and eliminates the duplicated validation + exception
        re-raise blocks that previously inflated uncovered line counts.
        """
        if not question or not question.strip():
            raise ValidationError("question must not be empty")
        if not chosen_option or not chosen_option.strip():
            raise ValidationError("chosen_option must not be empty")

        snapshot = capture_context_snapshot(
            context_data=context_data,
            actor_state=actor_state,
            relevant_resources=relevant_resources,
        )

        try:
            decision = self.decision_service.record_decision(
                plan_id=self.plan_id,
                decision_type=decision_type,
                question=question,
                chosen_option=chosen_option,
                parent_decision_id=self.parent_decision_id,
                alternatives_considered=alternatives_considered,
                confidence_score=confidence_score,
                rationale=rationale,
                context_snapshot=snapshot,
                plan_phase=PlanPhase.EXECUTE,
            )
        except Exception as exc:  # pragma: no cover - pass-through logging
            self._logger.warning(f"Failed to record {log_label}", error=str(exc))
            raise

        self._logger.debug(f"{log_label} recorded", decision_id=decision.decision_id)
        return decision

    def record_implementation_choice(
        self,
        question: str,
        chosen_option: str,
        alternatives_considered: list[str] | None = None,
        confidence_score: float | None = None,
        rationale: str = "",
        context_data: dict[str, Any] | None = None,
        actor_state: dict[str, Any] | None = None,
        relevant_resources: list[str] | None = None,
    ) -> Decision:
        """Record an IMPLEMENTATION_CHOICE during Execute."""
        return self._record(
            decision_type=DecisionType.IMPLEMENTATION_CHOICE,
            log_label="implementation_choice",
            question=question,
            chosen_option=chosen_option,
            alternatives_considered=alternatives_considered,
            confidence_score=confidence_score,
            rationale=rationale,
            context_data=context_data,
            actor_state=actor_state,
            relevant_resources=relevant_resources,
        )

    def record_tool_invocation(
        self,
        question: str,
        chosen_option: str,
        alternatives_considered: list[str] | None = None,
        confidence_score: float | None = None,
        rationale: str = "",
        context_data: dict[str, Any] | None = None,
        actor_state: dict[str, Any] | None = None,
        relevant_resources: list[str] | None = None,
    ) -> Decision:
        """Record a TOOL_INVOCATION during Execute."""
        return self._record(
            decision_type=DecisionType.TOOL_INVOCATION,
            log_label="tool_invocation",
            question=question,
            chosen_option=chosen_option,
            alternatives_considered=alternatives_considered,
            confidence_score=confidence_score,
            rationale=rationale,
            context_data=context_data,
            actor_state=actor_state,
            relevant_resources=relevant_resources,
        )

    def record_error_recovery(
        self,
        question: str,
        chosen_option: str,
        alternatives_considered: list[str] | None = None,
        confidence_score: float | None = None,
        rationale: str = "",
        context_data: dict[str, Any] | None = None,
        actor_state: dict[str, Any] | None = None,
        relevant_resources: list[str] | None = None,
    ) -> Decision:
        """Record an ERROR_RECOVERY during Execute."""
        return self._record(
            decision_type=DecisionType.ERROR_RECOVERY,
            log_label="error_recovery",
            question=question,
            chosen_option=chosen_option,
            alternatives_considered=alternatives_considered,
            confidence_score=confidence_score,
            rationale=rationale,
            context_data=context_data,
            actor_state=actor_state,
            relevant_resources=relevant_resources,
        )

    def record_validation_response(
        self,
        question: str,
        chosen_option: str,
        alternatives_considered: list[str] | None = None,
        confidence_score: float | None = None,
        rationale: str = "",
        context_data: dict[str, Any] | None = None,
        actor_state: dict[str, Any] | None = None,
        relevant_resources: list[str] | None = None,
    ) -> Decision:
        """Record a VALIDATION_RESPONSE during Execute."""
        return self._record(
            decision_type=DecisionType.VALIDATION_RESPONSE,
            log_label="validation_response",
            question=question,
            chosen_option=chosen_option,
            alternatives_considered=alternatives_considered,
            confidence_score=confidence_score,
            rationale=rationale,
            context_data=context_data,
            actor_state=actor_state,
            relevant_resources=relevant_resources,
        )

    def record_subplan_spawn(
        self,
        question: str,
        chosen_option: str,
        alternatives_considered: list[str] | None = None,
        confidence_score: float | None = None,
        rationale: str = "",
        context_data: dict[str, Any] | None = None,
        actor_state: dict[str, Any] | None = None,
        relevant_resources: list[str] | None = None,
    ) -> Decision:
        """Record a SUBPLAN_SPAWN during Execute.

        Recorded when the execution actor discovers additional
        decomposition needs at runtime that require creating child plans.
        """
        return self._record(
            decision_type=DecisionType.SUBPLAN_SPAWN,
            log_label="subplan_spawn",
            question=question,
            chosen_option=chosen_option,
            alternatives_considered=alternatives_considered,
            confidence_score=confidence_score,
            rationale=rationale,
            context_data=context_data,
            actor_state=actor_state,
            relevant_resources=relevant_resources,
        )

    def record_subplan_parallel_spawn(
        self,
        question: str,
        chosen_option: str,
        alternatives_considered: list[str] | None = None,
        confidence_score: float | None = None,
        rationale: str = "",
        context_data: dict[str, Any] | None = None,
        actor_state: dict[str, Any] | None = None,
        relevant_resources: list[str] | None = None,
    ) -> Decision:
        """Record a SUBPLAN_PARALLEL_SPAWN during Execute."""
        return self._record(
            decision_type=DecisionType.SUBPLAN_PARALLEL_SPAWN,
            log_label="subplan_parallel_spawn",
            question=question,
            chosen_option=chosen_option,
            alternatives_considered=alternatives_considered,
            confidence_score=confidence_score,
            rationale=rationale,
            context_data=context_data,
            actor_state=actor_state,
            relevant_resources=relevant_resources,
        )

    def record_resource_selection(
        self,
        question: str,
        chosen_option: str,
        alternatives_considered: list[str] | None = None,
        confidence_score: float | None = None,
        rationale: str = "",
        context_data: dict[str, Any] | None = None,
        actor_state: dict[str, Any] | None = None,
        relevant_resources: list[str] | None = None,
    ) -> Decision:
        """Record a RESOURCE_SELECTION during Execute.

        Resource selection is phase-agnostic but this records the
        selections made *during* execution (e.g., discovering which files
        to modify at runtime).
        """
        return self._record(
            decision_type=DecisionType.RESOURCE_SELECTION,
            log_label="resource_selection",
            question=question,
            chosen_option=chosen_option,
            alternatives_considered=alternatives_considered,
            confidence_score=confidence_score,
            rationale=rationale,
            context_data=context_data,
            actor_state=actor_state,
            relevant_resources=relevant_resources,
        )


__all__ = ["ExecutePhaseDecisionHook"]
