"""호스트 선택기 한도 안에서 원래 선택지를 보존하는 두 가지 강등.

네이티브 질문 묶음(claude-code `AskUserQuestion` 의 질문 4개 × 옵션 4개)에
실리는 크기면 같은 화면 안의 체크박스 질문 여러 개로 자른다(`split_picker`):
사용자는 탭마다 체크하고, 답은 하나의 CSV 로 합쳐져(`merge_split_picker_answer`)
원래 step 의 제출 경로로 간다. 단일 선택(예: critic 처럼 최대 1개인 역할)도
같은 방식으로 자르되 통틀어 하나만 고른 답이어야 한다.

그 크기도 넘거나 질문 묶음이 없는 세션이면 전체 번호 목록을 한 번에 보인다.
`present_picker` 는 중복 라벨만 구분하고 선택지 순서와 값을 보존한다.
"""
import json
import math
from dataclasses import replace

from .ids import PICK_TYPE_CUSTOM
from .state import Prompt, WizardError

_SPLIT_SEPARATOR = "#"


def split_picker(
    prompt: Prompt, *, max_options: int, max_questions: int,
) -> Prompt:
    """옵션이 `max_options` 를 넘는 픽을 같은 화면의 체크박스 질문 묶음으로 자른다.

    조각은 원래 픽이 단일 선택이어도 체크박스다. 단일 선택 조각으로 두면 고를
    것이 없는 조각에도 답이 있어야 해서 조각마다 "여기 없음" 한 줄이 들어가고,
    조각당 실선택지가 하나 줄어 후보 13개짜리 critic 화면은 조각 5개가 돼
    묶음에 아예 못 실린다(claude-code 한도 4×4). 체크박스 조각은 아무것도
    고르지 않은 조각이 곧 답 없는 조각이라 그 줄이 필요 없다. 원래 픽이 단일
    선택이었다는 사실은 그룹의 `multi=False` 로 남고, `merge_split_picker_answer`
    가 통틀어 하나만 골랐는지 본다.

    질문 수는 옵션이 들어가는 최소 개수이고 옵션은 질문에 고르게 나눈다 —
    마지막 질문이 한 줄짜리가 되면 호스트 최소 옵션 수(2)에 걸려 묶음 전체가
    네이티브에 못 실린다. 모델 선택은 제공자 순서와 추천 표시를 각 조각에서도
    보존한다(`Prompt._check_recommendations`). `max_questions` 도
    넘으면 자르지 않고 그대로 돌려준다.
    """
    if prompt.kind != "pick" or len(prompt.options) <= max_options:
        return prompt
    count = len(prompt.options)
    questions = math.ceil(count / max_options)
    if questions > max_questions:
        return prompt
    base, extra = divmod(count, questions)
    chunks: list[Prompt] = []
    offset = 0
    for index in range(questions):
        size = base + (1 if index < extra else 0)
        shown = prompt.options[offset:offset + size]
        chunks.append(replace(
            prompt,
            step=f"{prompt.step}{_SPLIT_SEPARATOR}{index + 1}",
            label=f"{prompt.label} ({offset + 1}–{offset + size}/{count})",
            options=shown,
            multi=True,
        ))
        offset += size
    return Prompt(
        step=prompt.step,
        kind="pick_group",
        label=prompt.label,
        help=prompt.help,
        echo_template=prompt.echo_template,
        multi=prompt.multi,
        questions=chunks,
    )


def is_split_picker(prompt: Prompt) -> bool:
    return prompt.kind == "pick_group" and bool(prompt.questions) and all(
        question.multi
        and question.step.startswith(f"{prompt.step}{_SPLIT_SEPARATOR}")
        for question in prompt.questions
    )


def merge_split_picker_answer(prompt: Prompt, value: str) -> str:
    """질문 묶음 답(JSON, 조각 step → CSV)을 원래 픽의 CSV 한 줄로 합친다.

    원래 픽이 단일 선택이면(그룹의 `multi=False`) 통틀어 한 줄만 고른 답이어야
    한다 — 두 조각에서 고른 답을 그대로 합치면 원래 step 은 `a,b` 를 값 하나로
    받아 "선택지가 아니다" 라고만 말한다.
    """
    try:
        answers = json.loads(value or "{}")
    except json.JSONDecodeError as exc:
        raise WizardError(f"pick_group answer must be a JSON object: {exc}") from exc
    if not isinstance(answers, dict):
        raise WizardError("pick_group answer must be a JSON object")
    known = {question.step for question in prompt.questions}
    unknown = sorted(set(answers) - known)
    if unknown:
        raise WizardError(
            f"wizard step {prompt.step!r}: answer names unknown questions {unknown}; "
            f"expected {sorted(known)}"
        )
    chosen: list[str] = []
    for question in prompt.questions:
        raw = str(answers.get(question.step, "") or "")
        chosen.extend(item.strip() for item in raw.split(",") if item.strip())
    if not prompt.multi and len(chosen) > 1:
        raise WizardError(
            f"wizard step {prompt.step!r}: this screen takes one choice, "
            f"but {len(chosen)} were picked across its tabs: {', '.join(chosen)}"
        )
    return ",".join(chosen)


def present_picker(prompt: Prompt) -> Prompt:
    if prompt.kind != "pick" or not prompt.options:
        return prompt
    labels = [option.label for option in prompt.options]
    options = [replace(
        option,
        label=option.label + (f" [{option.value}]" if labels.count(option.label) > 1 else ""),
    ) for option in prompt.options]
    options = [o for o in options if o.value != PICK_TYPE_CUSTOM] + [
        o for o in options if o.value == PICK_TYPE_CUSTOM
    ]
    return replace(prompt, options=options)
