"""다음 run 으로 넘길 clarification 응답 본문을 만든다.

이전 리포트의 §1 과 사용자 답변 사이드카를 합쳐 하나의 이어받기 본문으로
정리한다. 답이 온 행은 상태를 갱신하고, 아직 열린 행은 그대로 넘긴다.
"""
from __future__ import annotations

from ..md_table import is_separator_row, split_pipe_row, to_cell_text
from pathlib import Path
from typing import Optional
import re
from .parsing import (
    SECTION_HEADING_PATTERN,
    _section_1_slice,
    _split_pipe_row,
    parse_meta_cell,
)
from .rows import (
    _structured_report_data,
)
from .sidecars import (
    attached_user_responses_section,
    sidecar_answers,
)


UNRESOLVED_STATUSES = {"open"}


def clarification_response_with_sidecars(source: Path) -> str:
    """clarification-response 본문에 `user-responses/` 사이드카를 덧붙인 본문.

    `resume-clarification` 의 설계된 입력은 사용자가 §1 의 `User input` 열을
    채운 **직전 final-report 자체**다. 그 전문을 그대로 복사하면 리포트가
    instruction-set 안에 두 번째로 존재하게 되고, report-writer 는 같은 내용을
    `clarification-response.md` 로 한 번, 직전 리포트 경로로 다시 한 번 읽는다.
    리포트는 run 이 누적될수록 커지므로 이 중복은 스스로 악화된다(실측: 283K
    리포트가 892K 의 중복 읽기를 만들어 report-writer 를 timeout 시켰다).

    그래서 소스가 final-report 일 때는 답변이 실린 clarification 행만 잘라내고
    원문은 경로로 가리킨다 — `attached_user_responses_section` 이 implementation
    carry-in 에서 이미 쓰는 "원문은 경로로, 답변만 첨부" 규칙과 같다. 행을 어디서
    읽는지는 스키마가 정한다(v1 은 §1 표, v2 는 data.json). clarification 을
    아예 담지 않은 소스(사용자가 직접 쓴 답변 파일)만 원문 그대로 복사한다.
    """
    text = source.read_text(encoding="utf-8")
    section = attached_user_responses_section(source)
    answers = sidecar_answers(source)
    body = _clarification_carry_body(source, text, answers)
    if not section:
        return body
    return body.rstrip("\n") + "\n\n---\n\n" + section


SECTION_1_HEADING = "## 1. Clarification Items"


_SECTION_1_TABLE_HEADER = (
    "| Record | Statement | Expected form | User input |\n"
    "|---|---|---|---|"
)


_SECTION_1_EMPTY_STATE = "- The source report recorded no clarification items."


def _clarification_carry_body(
    source: Path, text: str, answers: dict[str, str]
) -> str:
    """final-report 소스는 §1 + 원문 포인터로 좁히고, 그 외는 원문 그대로.

    §1 이 있으면 사이드카 답변을 그 표의 `User input` 열에 병합해, 답이 표 안에
    자리하도록 한다(파일 헤더가 선언하는 "답은 User input 열에" 계약을 실제로
    참으로 만든다)."""
    carried = _carry_section_1(source, text)
    if carried is None:
        return text
    heading, section_body = carried
    if answers:
        section_body = _reconcile_user_input(section_body, answers)
    return (
        "# Clarification Response (carry-in)\n\n"
        f"- Source report: `{source}`\n"
        "- This file carries **only** the source report's Clarification Items "
        "section; the report itself is read from the path above when a phase "
        "needs it. Do not re-read the source report to find the answers — they "
        "are in the `User input` column below.\n\n"
        f"{heading}\n{section_body}\n"
    )


def carried_clarification_rows(source: Path) -> str:
    """``source`` 리포트의 §1 표 본문만. clarification 을 담지 않았으면 빈 문자열.

    `clarification_response_with_sidecars` 가 carry-in 본문에 싣는 것과 **같은**
    잘라내기다. 직전 계획 run 의 clarification 을 packet 에 실을 때 이 함수를
    쓰지 않고 따로 긁으면 같은 리포트가 소비처마다 다른 clarification 집합으로
    읽힌다. 리포트 전문은 절대 싣지 않는다 — 그것이 이 모듈이 존재하는 이유다.
    """
    carried = _carry_section_1(source)
    return "" if carried is None else carried[1].strip()


def _carry_section_1(
    source: Path, text: Optional[str] = None
) -> Optional[tuple[str, str]]:
    """carry-in 본문에 실을 (헤딩, §1 본문). clarification 을 담지 않은 소스면
    ``None``.

    schema-v2 는 행을 data.json 에 들고 AI 마크다운에는 §1 표가 없다. §1 슬라이스
    만 보던 동안 v2 소스는 "좁힐 것이 없다" 로 판정돼 **리포트 전문이 그대로
    복사**됐다 — 이 좁히기가 막으려던 바로 그 중복이다. carry-in 은 파생 문서이고
    다운스트림(승인 게이트·프롬프트 빌더·검증 워커)이 §1 표 하나만 읽으므로, v2
    행도 같은 표로 렌더한다.

    ``text`` 를 생략하면 v1 슬라이스가 필요할 때만 소스를 읽는다. v2/v3 소스는
    본문이 data.json 에 있어 마크다운을 읽을 이유가 없고, 그 파일은 실측 693K
    까지 커진다."""
    data = _structured_report_data(source)
    if data is not None:
        entries = data.get("clarificationItems")
        return SECTION_1_HEADING, _structured_section_1_body(
            entries if isinstance(entries, list) else []
        )
    if text is None:
        text = source.read_text(encoding="utf-8", errors="replace")
    slice_ = _section_1_slice(text)
    if slice_ is None:
        return None
    heading = SECTION_HEADING_PATTERN.search(text)
    assert heading is not None  # _section_1_slice returned a slice
    return heading.group(0), slice_.rstrip()


def _structured_section_1_body(entries: list) -> str:
    """schema-v2 `clarificationItems[]` 를 §1 표 본문으로.

    메타 셀은 렌더러가 쓰는 모양 그대로다 — `Status:` 는 따옴표 없이 써야
    `_reconcile_user_input` 이 답을 병합하면서 상태를 answered 로 넘길 수 있다."""
    rows = [e for e in entries if isinstance(e, dict) and e.get("id")]
    if not rows:
        return f"\n{_SECTION_1_EMPTY_STATE}"
    lines = ["", _SECTION_1_TABLE_HEADER]
    for entry in rows:
        meta = (
            f"**{entry['id']}**"
            f"<br>Ticket: `{to_cell_text(entry.get('ticketId'))}`"
            f"<br>Kind: `{to_cell_text(entry.get('kind'))}`"
            f"<br>Blocks: `{to_cell_text(entry.get('blocks'))}`"
            f"<br>Status: {to_cell_text(entry.get('status'))}"
        )
        lines.append(
            f"| {meta} | {to_cell_text(entry.get('statement'))} "
            f"| {to_cell_text(entry.get('expectedForm'))} "
            f"| {to_cell_text(entry.get('userInput'))} |"
        )
    return "\n".join(lines)


_STATUS_ANSWER_RE = re.compile(r"(Status:\s*)(?:open|answered)\b", re.IGNORECASE)


def _locate_user_input_column(lines: list[str]) -> tuple[int, int]:
    """§1 데이터 표의 헤더 줄 인덱스와 `User input` 열 인덱스. 표가 없으면 (-1, -1)."""
    for idx, line in enumerate(lines):
        if not line.lstrip().startswith("|"):
            continue
        cells = [c.lower() for c in _split_pipe_row(line)]
        if "user input" in cells and any(c.startswith("statement") for c in cells):
            return idx, cells.index("user input")
    return -1, -1


def _reconcile_row(line: str, ui_col: int, answers: dict[str, str]) -> str:
    """답이 있고 open/answered 인 행이면 `User input` 칸을 그 답으로 채우고 Status 를
    answered 로 바꾼 줄을, 그 외에는 원본 줄을 그대로 돌려준다.

    칸에 이미 값이 있어도 사용자의 사이드카 답이 이긴다. 그 칸을 채우는 것은
    run 자신(직전 렌더가 옮겨 적은 값)이고, 사용자가 나중에 답을 바꾸면 둘이
    갈라진다 — 사용자가 쓴 쪽을 정본으로 삼지 않으면 run 이 자기가 적어둔 값으로
    계속 되돌아간다.

    판정은 앵커/백틱을 벗긴 셀(`_split_pipe_row`)로 — 그래야 `_meta_id` 가 스크롤
    앵커의 소문자 slug 대신 진짜 대문자 ID 를 읽는다. 재조립은 원본 셀
    (`split_pipe_row`)로 해서 앵커를 보존한다."""
    norm = _split_pipe_row(line)
    item = parse_meta_cell(norm[0]) if norm else None
    if item is None or item.row_id not in answers:
        return line
    if item.status not in UNRESOLVED_STATUSES:
        return line
    raw = split_pipe_row(line)
    if not 0 <= ui_col < len(raw):
        return line
    raw[ui_col] = answers[item.row_id]
    raw[0] = _STATUS_ANSWER_RE.sub(r"\1answered", raw[0])
    return "| " + " | ".join(to_cell_text(c) for c in raw) + " |"


def _reconcile_user_input(section: str, answers: dict[str, str]) -> str:
    """§1 표에서 사이드카 답이 있는 미해결 행의 `User input` 칸을 답으로 채우고
    Status 를 answered 로 바꾼 §1 본문을 돌려준다.

    답의 정본 위치를 §1 표 안으로 옮긴다 — 표만 읽는 승인 게이트·프롬프트
    빌더·검증 워커가 모두 답을 보게 하려는 것. 사이드카는 §1 표 밖 별도 섹션에만
    있어서 표만 신뢰하는 소비자는 그 답을 놓쳤다."""
    lines = section.splitlines()
    header_idx, ui_col = _locate_user_input_column(lines)
    if header_idx < 0:
        return section
    out = list(lines)
    body = False
    for i in range(header_idx + 1, len(lines)):
        line = lines[i]
        if not line.lstrip().startswith("|"):
            if body:
                break
            continue
        if is_separator_row(line):
            body = True
            continue
        if body:
            out[i] = _reconcile_row(line, ui_col, answers)
    return "\n".join(out)
