"""처분(disposition)의 어휘와 그것이 진행을 막는지의 판정.

`Blocks` 값과 답변 처분만 보고 결정한다 — 파일도, 표도 열지 않는다. 게이트를
읽는 쪽(`.scan`)과 리포트를 읽는 쪽(`.rows`)이 같은 정의를 쓰게 하는 것이
이 층이 따로 있는 이유다.
"""
from __future__ import annotations

from collections.abc import Mapping


APPROVAL_BLOCKS = frozenset({"approval"})


USER_INPUT_BLOCKS = frozenset({"approval", "next-phase"})


PROCEEDING_DISPOSITIONS = frozenset({"answer", "select", "accept-risk"})


RETURN_DISPOSITIONS = frozenset({"request-revision", "reject"})


INCORPORATED_LEDGER_DISPOSITIONS = frozenset(
    {"superseded", "no-dependent-statement"}
)


def clarification_disposition(row: Mapping[str, object]) -> str:
    """행에 기록된 사용자 처분. 없으면 빈 문자열."""
    resolution = row.get("resolution")
    if not isinstance(resolution, Mapping):
        context = row.get("approvalContext")
        if isinstance(context, Mapping):
            resolution = context.get("resolution")
    if isinstance(resolution, Mapping):
        disposition = str(resolution.get("disposition") or "").strip()
        if disposition:
            return disposition
    user_input = str(row.get("userInput") or "").strip()
    options = row.get("options")
    if user_input and isinstance(options, list):
        for option in options:
            if (
                isinstance(option, Mapping)
                and str(option.get("answer") or "").strip() == user_input
            ):
                return str(option.get("disposition") or "").strip()
    return ""


def incorporated_clarification_ids(
    report_data: Mapping[str, object] | None,
) -> frozenset[str]:
    """이 런이 답을 본문에 반영했다고 원장에 적은 C-id.

    `superseded` 와 `no-dependent-statement` 만 센다. 원장에 없는
    `request-revision` / `reject` 는 아직 다음 계획을 막는 되돌림이다.
    """
    if not isinstance(report_data, Mapping):
        return frozenset()
    planning = report_data.get("implementationPlanning")
    if not isinstance(planning, Mapping):
        return frozenset()
    ledger = planning.get("supersessionLedger")
    if not isinstance(ledger, list):
        return frozenset()
    ids: set[str] = set()
    for entry in ledger:
        if not isinstance(entry, Mapping):
            continue
        row_id = str(entry.get("clarificationId") or "").strip()
        disposition = str(entry.get("disposition") or "").strip().lower()
        if row_id and disposition in INCORPORATED_LEDGER_DISPOSITIONS:
            ids.add(row_id)
    return frozenset(ids)


def row_blocks_progress(
    status: str,
    disposition: str = "",
    *,
    incorporated: bool = False,
) -> bool:
    """이 행이 승인·다음 단계 진입을 막는가.

    진행 처분(`accept-risk` / `select` / `answer`)은 고치지 않은 DISAGREE 를
    행과 투표에 남긴 채로 게이트만 내린다. `request-revision` / `reject` 는
    사용자가 진행을 거절한 것이므로 막는다. 다만 이 보고서 원장이 그 답을
    이미 반영했으면(`incorporated`) 같은 되돌림이 다음 계획 런을 강제하지
    않는다. 처분이 없는 `answered` 도 판단 기록이므로 막지 않는다.
    """
    normalized_status = status.strip().lower()
    normalized_disposition = disposition.strip().lower()
    if normalized_status == "obsolete":
        return False
    if normalized_disposition in PROCEEDING_DISPOSITIONS:
        return False
    if normalized_disposition in RETURN_DISPOSITIONS:
        return not incorporated
    return normalized_status not in {"answered", "resolved"}


def progress_blocking_ids(
    rows: object,
    blocking_values: frozenset[str] = APPROVAL_BLOCKS,
    *,
    report_data: Mapping[str, object] | None = None,
) -> list[str]:
    """게이트를 아직 막는 행 id. 사용자 진행 처분이 있는 행은 빠진다.

    ``report_data`` 가 있으면 원장에 반영된 되돌림 행도 빠진다.
    """
    if not isinstance(rows, list):
        return []
    incorporated = incorporated_clarification_ids(report_data)
    ids: list[str] = []
    for row in rows:
        if not isinstance(row, Mapping):
            continue
        blocks = str(row.get("blocks") or "").strip().lower()
        status = str(row.get("status") or "").strip()
        row_id = row.get("id")
        if (
            blocks in blocking_values
            and row_blocks_progress(
                status,
                clarification_disposition(row),
                incorporated=isinstance(row_id, str) and row_id in incorporated,
            )
            and isinstance(row_id, str)
            and row_id
        ):
            ids.append(row_id)
    return ids
