# #region ClarificationHelpers [C:3] [TYPE Module] [SEMANTICS dataset, clarification, question, selection, normalization]
# @BRIEF Pure helper functions for clarification engine — question selection, counting, normalization, finding upsert, and readiness derivation.
# @LAYER Domain
# @RELATION DEPENDS_ON -> [DatasetReviewModels]

from __future__ import annotations

from datetime import UTC, datetime
import uuid

from src.models.dataset_review import (
    AnswerKind,
    ClarificationQuestion,
    ClarificationSession,
    DatasetReviewSession,
    FindingArea,
    FindingSeverity,
    QuestionState,
    ReadinessState,
    RecommendedAction,
    ResolutionState,
    ValidationFinding,
)


# #region select_next_open_question [C:2] [TYPE Function]
# @BRIEF Select the next unresolved question in deterministic priority order.
def select_next_open_question(
    clarification_session: ClarificationSession,
) -> ClarificationQuestion | None:
    open_questions = [
        q for q in clarification_session.questions if q.state == QuestionState.OPEN
    ]
    if not open_questions:
        return None
    open_questions.sort(key=lambda item: (-int(item.priority), item.created_at, item.question_id))
    return open_questions[0]


# #endregion select_next_open_question


# #region count_resolved_questions [C:1] [TYPE Function]
# @BRIEF Count questions whose answers fully resolved the ambiguity.
def count_resolved_questions(clarification_session: ClarificationSession) -> int:
    return sum(1 for q in clarification_session.questions if q.state == QuestionState.ANSWERED)


# #endregion count_resolved_questions


# #region count_remaining_questions [C:1] [TYPE Function]
# @BRIEF Count questions still unresolved or deferred after clarification interaction.
def count_remaining_questions(clarification_session: ClarificationSession) -> int:
    return sum(
        1
        for q in clarification_session.questions
        if q.state in {QuestionState.OPEN, QuestionState.SKIPPED, QuestionState.EXPERT_REVIEW}
    )


# #endregion count_remaining_questions


# #region normalize_answer_value [C:2] [TYPE Function]
# @BRIEF Validate and normalize answer payload based on answer kind and active question options.
def normalize_answer_value(
    answer_kind: AnswerKind,
    answer_value: str | None,
    question: ClarificationQuestion,
) -> str | None:
    normalized = str(answer_value).strip() if answer_value is not None else None
    if answer_kind in {AnswerKind.SELECTED, AnswerKind.CUSTOM} and not normalized:
        raise ValueError("answer_value is required for selected or custom clarification answers")
    if answer_kind == AnswerKind.SELECTED:
        allowed_values = {option.value for option in question.options}
        if normalized not in allowed_values:
            raise ValueError("answer_value must match one of the current clarification options")
    if answer_kind == AnswerKind.SKIPPED:
        return normalized or "skipped"
    if answer_kind == AnswerKind.EXPERT_REVIEW:
        return normalized or "expert_review"
    return normalized


# #endregion normalize_answer_value


# #region build_impact_summary [C:1] [TYPE Function]
# @BRIEF Build a compact audit note describing how the clarification answer affects session state.
def build_impact_summary(
    question: ClarificationQuestion,
    answer_kind: AnswerKind,
    answer_value: str | None,
) -> str:
    if answer_kind == AnswerKind.SKIPPED:
        return f"Clarification for {question.topic_ref} was skipped and remains unresolved."
    if answer_kind == AnswerKind.EXPERT_REVIEW:
        return f"Clarification for {question.topic_ref} was deferred for expert review."
    return f"Clarification for {question.topic_ref} recorded as '{answer_value}'."


# #endregion build_impact_summary


# #region upsert_clarification_finding [C:3] [TYPE Function]
# @BRIEF Keep one finding per clarification topic aligned with answer outcome and unresolved visibility rules.
# @RELATION DEPENDS_ON -> [ValidationFinding]
def upsert_clarification_finding(
    session: DatasetReviewSession,
    question: ClarificationQuestion,
    answer_kind: AnswerKind,
    answer_value: str | None,
    db_session,
) -> ValidationFinding | None:
    caused_by_ref = f"clarification:{question.question_id}"
    existing = next(
        (f for f in session.findings if f.area == FindingArea.CLARIFICATION and f.caused_by_ref == caused_by_ref),
        None,
    )

    if answer_kind in {AnswerKind.SELECTED, AnswerKind.CUSTOM}:
        resolution_state = ResolutionState.RESOLVED
        resolved_at = datetime.now(UTC)
        message = f"Clarified '{question.topic_ref}' with answer '{answer_value}'."
    elif answer_kind == AnswerKind.SKIPPED:
        resolution_state = ResolutionState.SKIPPED
        resolved_at = None
        message = f"Clarification for '{question.topic_ref}' was skipped and still needs review."
    else:
        resolution_state = ResolutionState.EXPERT_REVIEW
        resolved_at = None
        message = f"Clarification for '{question.topic_ref}' requires expert review."

    if existing is None:
        existing = ValidationFinding(
            finding_id=str(uuid.uuid4()),
            session_id=session.session_id,
            area=FindingArea.CLARIFICATION,
            severity=FindingSeverity.WARNING,
            code="CLARIFICATION_PENDING",
            title="Clarification pending",
            message=message,
            resolution_state=resolution_state,
            resolution_note=None,
            caused_by_ref=caused_by_ref,
            created_at=datetime.now(UTC),
            resolved_at=resolved_at,
        )
        db_session.add(existing)
        session.findings.append(existing)
    else:
        existing.message = message
        existing.resolution_state = resolution_state
        existing.resolved_at = resolved_at

    if answer_kind in {AnswerKind.SELECTED, AnswerKind.CUSTOM}:
        existing.code = "CLARIFICATION_RESOLVED"
        existing.title = "Clarification resolved"
    elif answer_kind == AnswerKind.SKIPPED:
        existing.code = "CLARIFICATION_SKIPPED"
        existing.title = "Clarification skipped"
    else:
        existing.code = "CLARIFICATION_EXPERT_REVIEW"
        existing.title = "Clarification requires expert review"

    return existing


# #endregion upsert_clarification_finding


# #region derive_readiness_state [C:2] [TYPE Function]
# @BRIEF Recompute readiness after clarification mutation while preserving unresolved visibility semantics.
def derive_readiness_state(
    session: DatasetReviewSession,
    clarification_session: ClarificationSession | None,
) -> ReadinessState:
    if clarification_session is None:
        return session.readiness_state
    if clarification_session.current_question_id:
        return ReadinessState.CLARIFICATION_ACTIVE
    if clarification_session.remaining_count > 0:
        return ReadinessState.CLARIFICATION_NEEDED
    return ReadinessState.REVIEW_READY


# #endregion derive_readiness_state


# #region derive_recommended_action [C:2] [TYPE Function]
# @BRIEF Recompute next-action guidance after clarification mutations.
def derive_recommended_action(
    session: DatasetReviewSession,
    clarification_session: ClarificationSession | None,
) -> RecommendedAction:
    if clarification_session is None:
        return session.recommended_action
    if clarification_session.current_question_id:
        return RecommendedAction.ANSWER_NEXT_QUESTION
    if clarification_session.remaining_count > 0:
        return RecommendedAction.START_CLARIFICATION
    return RecommendedAction.REVIEW_DOCUMENTATION


# #endregion derive_recommended_action


# #endregion ClarificationHelpers
