"""run-index row 의 생성/축약/복원 단일 참조점.

디스크에는 slim(14필드)로 저장하고, reader 는 hydrate 로 full(18필드)을 받는다.
파생 4필드(taskType/runSeq/taskKey/invocationFile)는 runId 와 평면
taskGroup/taskId 로부터 무손실 복원 가능하다. projectId 는 runId 슬러그가
대소문자를 정규화(lowercase/slugify)해 raw 값을 잃으므로 파생이 아니라
slim row 에 raw 로 보존한다 — taskGroup/taskId 와 동일한 이유다.
"""
from __future__ import annotations

from pathlib import Path
from typing import Optional

from .ids import build_run_id, parse_run_id
from .invocation import invocation_path
from .jsonl import read_jsonl, rewrite_jsonl

DERIVED_FIELDS = ("taskType", "runSeq", "taskKey", "invocationFile")


def build_run_index_row(*, project_id: str, project_root: str,
                        task_group: str, task_id: str, task_type: str,
                        run_seq: int, status: str, started_at: str,
                        finished_at: Optional[str], workers: list, lead_model: str,
                        validation: str, run_dir_rel: str,
                        final_report_record_rel: str, final_status_rel: str,
                        execution_manifest_path: str = "",
                        role_execution_refs: list | None = None) -> dict:
    """slim run-index row(14필드)를 만든다. 파생 4필드는 포함하지 않는다."""
    return {
        "runId": build_run_id(project_id, task_group, task_id,
                              task_type, run_seq),
        "projectId": project_id,
        "projectRoot": project_root,
        "taskGroup": task_group,
        "taskId": task_id,
        "status": status,
        "startedAt": started_at,
        "finishedAt": finished_at,
        "workers": workers,
        "leadModel": lead_model,
        "validation": validation,
        "finalReportRecordRel": final_report_record_rel,
        "finalStatusRel": final_status_rel,
        "runDirRel": run_dir_rel,
        **({
            "executionManifestPath": execution_manifest_path,
            "roleExecutionRefs": list(role_execution_refs or []),
        } if execution_manifest_path else {}),
    }


def slim_run_row(row: dict) -> dict:
    """파생 4필드를 제거한 새 dict. 이미 slim 이면 동일(멱등).
    runId 가 없는 row(예: 옛 event-log 스키마)는 변형 없이 사본 반환."""
    if "runId" not in row:
        return dict(row)
    return {k: v for k, v in row.items() if k not in DERIVED_FIELDS}


def hydrate_run_row(row: dict) -> dict:
    """slim/full row 를 받아 파생 4필드를 채운 full dict. 멱등.
    taskKey 는 runId(slug)가 아닌 평면 raw taskGroup/taskId 로 조합한다.
    projectId 는 raw 값이 slim row 에 보존되므로 runId 에서 복원하지 않는다 —
    슬러그가 대소문자를 잃기 때문. 누락 시(방어용)만 parsed 값으로 채운다.
    runId 가 없는 row(예: 옛 event-log 스키마)는 변형 없이 사본 반환.
    runId 가 있으나 형식이 깨진 row 는 5-세그먼트 구조가 남아있으면 taskType/
    runSeq 만 best-effort 로 복원한다 — slim row 는 이 둘을 따로 저장하지 않으므로,
    버전 차이/손상으로 strict 파싱이 실패하면 predict_next_run_seq 가 이 row 를
    max-seq 스캔에서 통째로 누락해 seq 가 충돌할 수 있기 때문. 그래도 한 줄의
    손상이 read_run_index 의 다른 소비자(list_runs/reconcile)를 죽이지 않는다.
    """
    if "runId" not in row:
        return dict(row)
    try:
        parsed = parse_run_id(row["runId"])
    except ValueError:
        out = dict(row)
        parts = str(row["runId"]).split("/")
        if len(parts) >= 5 and parts[-1][:1] == "r" and parts[-1][1:].isdigit():
            out.setdefault("taskType", parts[-2])
            out.setdefault("runSeq", int(parts[-1][1:]))
        return out
    out = dict(row)
    out.setdefault("projectId", parsed["projectId"])
    out["taskType"] = parsed["taskType"]
    out["runSeq"] = parsed["runSeq"]
    out["taskKey"] = f"{row.get('taskGroup', '')}/{row.get('taskId', '')}"
    out["invocationFile"] = str(invocation_path(
        Path(""), parsed["projectId"], parsed["taskGroup"], parsed["taskId"],
        parsed["taskType"], parsed["runSeq"]))
    return out


def read_run_index(path: Path) -> list:
    """run-index jsonl 을 읽어 각 row 를 hydrate 한 full row 목록 반환."""
    return [hydrate_run_row(r) for r in read_jsonl(path)]


def _rewrite_slim(path: Path) -> int:
    rows = read_jsonl(path)
    if not rows:
        return 0
    rewrite_jsonl(path, [slim_run_row(row) for row in rows])
    return len(rows)


def reindex_slim(home: Path) -> int:
    """기존 run-index 파일들을 slim 으로 일괄 재작성. 멱등. 변환 row 총수 반환."""
    total = 0
    targets = [home / "recent.jsonl", home / "active.jsonl"]
    projects = home / "projects"
    if projects.is_dir():
        targets += sorted(projects.glob("*/index.jsonl"))
    for path in targets:
        if path.is_file():
            total += _rewrite_slim(path)
    return total
