"""V1 provider-worker roster compatibility parsing.

Profile 파일에서 권장 worker 목록을 뽑고(`resolve_profile_workers`), 사용자
오버라이드와 합쳐 정규화한다(`normalize_workers`). bash workers.sh 의
동등한 python 구현.
"""
from __future__ import annotations

from pathlib import Path

from .models import provider_ids

ALLOWED_WORKERS = provider_ids("analyser") + ["report-writer"]
DEFAULT_WORKERS = ["claude", "codex", "report-writer"]
PROFILE_BULLET_HEADERS = {
    "- Workers:",
    "- Required workers:",
    "- Reviewers:",
    "- Analysers:",
}
PROFILE_BULLET_HEADERS_OPTIONAL = {
    "- Optional workers:",
    "- Optional workers (opt-in via `--workers`):",
}


class WorkersError(Exception):
    """invalid worker selection — surface to user."""


def _resolve_v1_workers_under(profile_path: Path, headers: set[str]) -> list[str]:
    """Read legacy provider-worker bullets without interpreting role YAML.

    Collect `  - <id> …` sub-bullets under any of `headers`
    until the next top-level bullet. Returns first token (before any
    ` — ` / ` -- ` / whitespace) of each captured line.
    """
    if not Path(profile_path).is_file():
        return []
    capturing = False
    out: list[str] = []
    for line in Path(profile_path).read_text(encoding="utf-8").splitlines():
        stripped = line.strip()
        if stripped in headers:
            capturing = True
            continue
        if not capturing:
            continue
        if line.startswith("- "):
            break
        if line.startswith("  - "):
            body = line[4:].strip()
            token = body.split(" — ", 1)[0].split(" -- ", 1)[0].split()[0]
            out.append(token)
            continue
        if stripped:
            break
    return out


def resolve_profile_workers(profile_path: Path) -> list[str]:
    """`prompts/profiles/<task-type>.md` 본문의 `- Required workers:` (또는
    `- Workers:` / `- Reviewers:` / `- Analysers:`) 섹션 아래 sub-bullet 들을
    worker id 리스트로 돌려준다. profile 파일이 없거나 섹션이 없으면 빈 리스트.
    """
    return _resolve_v1_workers_under(profile_path, PROFILE_BULLET_HEADERS)


def resolve_optional_workers(profile_path: Path) -> list[str]:
    r"""`- Optional workers (opt-in via \`--workers\`):` 섹션 아래 sub-bullet
    들에서 worker id 만 추출한다. (`  - antigravity — when added …` → `antigravity`)
    Required 와 중복되는 항목은 제거. ALLOWED_WORKERS 밖 토큰도 제거.
    """
    raw = _resolve_v1_workers_under(profile_path, PROFILE_BULLET_HEADERS_OPTIONAL)
    required = set(resolve_profile_workers(profile_path))
    seen: set[str] = set()
    out: list[str] = []
    for w in raw:
        if w in required or w in seen or w not in ALLOWED_WORKERS:
            continue
        seen.add(w)
        out.append(w)
    return out


def normalize_workers(value: str) -> list[str]:
    """CSV 입력을 정규화한다.

    - 공백 strip, 소문자화, 중복 제거(첫 출현 우선).
    - 빈 입력이면 `DEFAULT_WORKERS` 를 default 로 사용 (antigravity 제외).
    - 허용 외 worker 가 포함되면 `WorkersError`.
    """
    items = [v.strip().lower() for v in (value or "").split(",") if v.strip()]
    source = items or DEFAULT_WORKERS
    unknown = [v for v in source if v not in ALLOWED_WORKERS]
    if unknown:
        raise WorkersError(f"unknown workers: {','.join(unknown)}")
    seen: set[str] = set()
    out: list[str] = []
    for v in source:
        if v in seen:
            continue
        seen.add(v)
        out.append(v)
    return out


def validate_workers_against_profile(
    workers: list[str],
    profile_workers: list[str],
    optional_workers: list[str] | None = None,
) -> None:
    """프로파일이 `Required workers:` 로 로스터를 선언했다면, 사용자
    override 가 (required ∪ optional) 의 부분집합인지 검증한다.

    `Required workers:` 는 이 시스템에서 **allowlist + 기본 로스터**다 —
    전원 필수 집합이 아니다. 사용자는 그 안에서 좁혀 고를 수 있고(최소 1),
    wizard 는 분석 워커만 고르게 한 뒤 report-writer 를 뒤에서 강제
    추가하므로 누락 검증을 여기서 하면 그 흐름이 깨진다.

    `profile_workers` 가 비어 있으면(프로파일이 로스터를 선언하지 않은
    구버전) 검증을 건너뛴다.
    """
    if not profile_workers:
        return
    allowed = set(profile_workers) | set(optional_workers or [])
    extras = [w for w in workers if w not in allowed]
    if extras:
        allow_str = ",".join(profile_workers + list(optional_workers or []))
        raise WorkersError(
            "workers not allowed by profile roster: "
            f"{','.join(extras)} (profile allows: {allow_str})"
        )
