#!/usr/bin/env python3
from __future__ import annotations

import argparse
import json
import math
import re
import sys
from pathlib import Path
from typing import Any, Iterable


sys.dont_write_bytecode = True
ASSET_ROOT = Path(__file__).resolve().parent
if str(ASSET_ROOT) not in sys.path:
    sys.path.insert(0, str(ASSET_ROOT))


CONTRACT_VERSION = 1
PROFILE_VERSION = "semantic-sequence-markers-v1"
PAUSE_BOUNDARY_MS = 700
SIGNIFICANT_PAUSE_MS = 1500
MIN_CLAUSE_WORDS = 6
MAX_UNIT_WORDS = 32
ADVISORY_INPUT_BYTES = 160_000
RESPONSE_CAPTURE_CHARS = 200_000
SENTENCE_END_RE = re.compile(r'''[.!?]["')\]}]*$''')
CLAUSE_END_RE = re.compile(r"[,;:]$")
PUNCTUATION_ONLY_RE = re.compile(r"^[,.;:!?%)\]}]+$")


def canonical_json(value: Any) -> str:
    return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))


def _finite_number(value: Any, label: str) -> float:
    if not isinstance(value, (int, float)) or not math.isfinite(value):
        raise ValueError(f"{label} must be a finite number.")
    return float(value)


def _milliseconds(value: Any, label: str) -> int:
    return round(_finite_number(value, label) * 1000)


def _safe_string(value: Any, label: str, *, allow_empty: bool = False) -> str:
    if not isinstance(value, str):
        raise ValueError(f"{label} must be a string.")
    result = value.strip()
    if not result and not allow_empty:
        raise ValueError(f"{label} must not be empty.")
    if any(ord(character) < 32 and character not in "\t\n\r" for character in result):
        raise ValueError(f"{label} contains unsupported control characters.")
    return result


def _join_words(words: Iterable[dict[str, Any]]) -> str:
    output = ""
    for word in words:
        text = str(word["text"]).strip()
        if not output or PUNCTUATION_ONLY_RE.fullmatch(text):
            output += text
        elif output[-1] in "([{":
            output += text
        else:
            output += " " + text
    return output


def require_srt_toolkit(path: Path) -> None:
    required = (
        "sequence.py",
        "transcript.py",
        "caption_profile.py",
        "language.py",
        "timed_text.py",
        "operations.py",
    )
    missing = [name for name in required if not (path / name).is_file()]
    if missing:
        raise RuntimeError(
            "Semantic sequence markers require the complete sibling "
            f"'srt-caption-toolkit' asset; missing: {', '.join(missing)}."
        )


def _load_toolkit(path: Path) -> tuple[Any, Any]:
    require_srt_toolkit(path)
    if str(path) not in sys.path:
        sys.path.insert(0, str(path))
    from sequence import map_sequence_item_words
    from transcript import detect_transcript_language

    return map_sequence_item_words, detect_transcript_language


def _resolve_transcript(
    item: dict[str, Any],
    *,
    base_dir: Path,
    cache: dict[str, dict[str, Any]],
) -> tuple[dict[str, Any] | None, bool]:
    project_item_ref = _safe_string(
        item.get("projectItemRef", ""),
        "projectItemRef",
        allow_empty=True,
    )
    embedded = item.get("transcript")
    transcript_path_value = item.get("transcriptPath")
    if embedded is None and not (
        isinstance(transcript_path_value, str) and transcript_path_value.strip()
    ):
        return None, False
    cache_key = project_item_ref
    if not cache_key:
        cache_key = (
            f"embedded:{item.get('id', '')}"
            if isinstance(embedded, dict)
            else f"path:{transcript_path_value}"
        )
    if cache_key in cache:
        return cache[cache_key], True
    if embedded is not None:
        if not isinstance(embedded, dict):
            raise ValueError("transcript must be an object when present.")
        transcript = embedded
    else:
        transcript_path = Path(str(transcript_path_value))
        if not transcript_path.is_absolute():
            transcript_path = (base_dir / transcript_path).resolve()
        transcript = json.loads(transcript_path.read_text(encoding="utf-8"))
        if not isinstance(transcript, dict):
            raise ValueError("Transcript JSON root must be an object.")
    cache[cache_key] = transcript
    return transcript, False


def _interval_duration(intervals: list[tuple[int, int]]) -> int:
    if not intervals:
        return 0
    ordered = sorted(intervals)
    start, end = ordered[0]
    total = 0
    for next_start, next_end in ordered[1:]:
        if next_start <= end:
            end = max(end, next_end)
        else:
            total += end - start
            start, end = next_start, next_end
    return total + end - start


def _mapping_key(item: dict[str, Any], words: list[dict[str, Any]]) -> tuple[Any, ...]:
    return (
        str(item.get("projectItemRef", "")),
        _milliseconds(item.get("timelineStartSeconds"), "timelineStartSeconds"),
        _milliseconds(item.get("timelineEndSeconds"), "timelineEndSeconds"),
        _milliseconds(item.get("sourceInSeconds"), "sourceInSeconds"),
        _milliseconds(item.get("sourceOutSeconds"), "sourceOutSeconds"),
        tuple(
            (
                word["text"],
                word["sourceStartMs"],
                word["sourceEndMs"],
                word["startMs"],
                word["endMs"],
            )
            for word in words
        ),
    )


def _manifest_item_order_key(entry: tuple[int, Any]) -> tuple[Any, ...]:
    position, value = entry
    if not isinstance(value, dict):
        return (math.inf, math.inf, "", position)
    try:
        track_index = int(value.get("trackIndex"))
    except (TypeError, ValueError):
        track_index = math.inf
    try:
        timeline_start = _finite_number(
            value.get("timelineStartSeconds"),
            "timelineStartSeconds",
        )
    except ValueError:
        timeline_start = math.inf
    item_id = str(value.get("id", ""))
    return (track_index, timeline_start, item_id, position)


def _overlap_regions(words: list[dict[str, Any]]) -> list[dict[str, Any]]:
    seeds: list[tuple[int, int, set[str]]] = []
    ordered = sorted(words, key=lambda value: (value["startMs"], value["endMs"], value["itemId"]))
    active: list[dict[str, Any]] = []
    for current in ordered:
        active = [candidate for candidate in active if candidate["endMs"] > current["startMs"]]
        for candidate in active:
            if candidate["itemId"] == current["itemId"]:
                continue
            start = max(candidate["startMs"], current["startMs"])
            end = min(candidate["endMs"], current["endMs"])
            if end > start:
                seeds.append((start, end, {candidate["itemId"], current["itemId"]}))
        active.append(current)
    merged: list[tuple[int, int, set[str]]] = []
    for start, end, item_ids in sorted(seeds):
        if merged and (
            start < merged[-1][1]
            or (start == merged[-1][1] and item_ids == merged[-1][2])
        ):
            old_start, old_end, old_ids = merged[-1]
            merged[-1] = (old_start, max(old_end, end), old_ids | item_ids)
        else:
            merged.append((start, end, set(item_ids)))
    return [
        {
            "id": f"parallel_{index:04d}",
            "startMs": start,
            "endMs": end,
            "itemIds": sorted(item_ids),
        }
        for index, (start, end, item_ids) in enumerate(merged, start=1)
    ]


def _speaker_label(words: list[dict[str, Any]]) -> str:
    labels = {str(word.get("speakerName", "")).strip() for word in words}
    labels.discard("")
    labels.discard("Unknown")
    return next(iter(labels)) if len(labels) == 1 else ""


def _segment_item_words(words: list[dict[str, Any]]) -> list[list[dict[str, Any]]]:
    groups: list[list[dict[str, Any]]] = []
    current: list[dict[str, Any]] = []
    for word in words:
        if current:
            pause_ms = word["startMs"] - current[-1]["endMs"]
            group_changed = word.get("parallelGroupIds") != current[-1].get("parallelGroupIds")
            speaker_changed = (
                bool(word.get("speakerId"))
                and bool(current[-1].get("speakerId"))
                and word["speakerId"] != current[-1]["speakerId"]
            )
            if pause_ms >= PAUSE_BOUNDARY_MS or group_changed or speaker_changed:
                groups.append(current)
                current = []
        current.append(word)
        punctuation_boundary = len(current) >= MIN_CLAUSE_WORDS and bool(
            CLAUSE_END_RE.search(word["text"])
        )
        if (
            word.get("endOfSentence")
            or SENTENCE_END_RE.search(word["text"])
            or punctuation_boundary
            or len(current) >= MAX_UNIT_WORDS
        ):
            groups.append(current)
            current = []
    if current:
        groups.append(current)
    return groups


def _profile_request(payload: dict[str, Any]) -> dict[str, Any]:
    encoded = canonical_json(payload).encode("utf-8")
    unit_count = len(payload["units"])
    response_forecast = 4096 + unit_count * 220
    warnings: list[dict[str, Any]] = []
    if len(encoded) > ADVISORY_INPUT_BYTES:
        warnings.append(
            {
                "reasonCode": "INPUT_ABOVE_TESTED_PROFILE",
                "actualUtf8Bytes": len(encoded),
                "testedUtf8Bytes": ADVISORY_INPUT_BYTES,
            }
        )
    if response_forecast > int(RESPONSE_CAPTURE_CHARS * 0.8):
        warnings.append(
            {
                "reasonCode": "OUTPUT_FORECAST_NEAR_CAPTURE_LIMIT",
                "actualChars": response_forecast,
                "captureChars": RESPONSE_CAPTURE_CHARS,
            }
        )
    return {
        "profileVersion": PROFILE_VERSION,
        "requestUtf8Bytes": len(encoded),
        "estimatedInputTokens": len(encoded),
        "tokenEstimateMethod": "ONE_TOKEN_PER_UTF8_BYTE_CONSERVATIVE_FALLBACK",
        "responseForecastChars": response_forecast,
        "responseCaptureChars": RESPONSE_CAPTURE_CHARS,
        "aboveTestedProfile": bool(warnings),
        "warnings": warnings,
    }


def prepare_sequence(
    manifest: dict[str, Any],
    *,
    base_dir: Path,
    workflow: str,
    editor_context: str = "",
    srt_toolkit_dir: Path | None = None,
) -> dict[str, Any]:
    # This input is produced by srt-caption-toolkit's sequence manifest CLI.
    # Its public format uses `version`; `contractVersion` belongs to this
    # semantic adapter's own request/response artifacts.
    if manifest.get("version") != 1:
        raise ValueError("Sequence manifest version is unsupported.")
    if workflow not in {"chapters", "highlights"}:
        raise ValueError("workflow must be 'chapters' or 'highlights'.")
    raw_items = manifest.get("items")
    if not isinstance(raw_items, list):
        raise ValueError("Sequence manifest items must be a list.")
    sequence_name = _safe_string(manifest.get("sequenceName", ""), "sequenceName")
    sequence_start_ms = _milliseconds(manifest.get("sequenceStartSeconds", 0), "sequenceStartSeconds")
    sequence_end_ms = _milliseconds(manifest.get("sequenceEndSeconds"), "sequenceEndSeconds")
    if sequence_start_ms < 0 or sequence_end_ms <= sequence_start_ms:
        raise ValueError("Sequence range must be positive and ordered.")
    frame_duration_ms = max(
        1,
        _milliseconds(manifest.get("frameDurationSeconds", 1 / 30), "frameDurationSeconds"),
    )
    zero_point_ms = _milliseconds(manifest.get("zeroPointSeconds", 0), "zeroPointSeconds")
    map_words, detect_language = _load_toolkit(
        srt_toolkit_dir
        or (Path(__file__).resolve().parent.parent / "srt-caption-toolkit")
    )

    transcript_cache: dict[str, dict[str, Any]] = {}
    transcript_cache_hits = 0
    item_reports: list[dict[str, Any]] = []
    mapped_words: list[dict[str, Any]] = []
    mapped_item_intervals: list[tuple[int, int]] = []
    audible_item_intervals: list[tuple[int, int]] = []
    seen_mappings: set[tuple[Any, ...]] = set()
    languages: set[str] = set()
    language_issues: list[dict[str, str]] = []

    for position, raw_item in sorted(
        enumerate(raw_items),
        key=_manifest_item_order_key,
    ):
        if not isinstance(raw_item, dict):
            item_reports.append(
                {
                    "itemId": f"item_{position + 1:06d}",
                    "status": "UNSUPPORTED",
                    "reasonCode": "INVALID_ITEM",
                    "reason": "Manifest item is not an object.",
                }
            )
            continue
        item_id = _safe_string(
            raw_item.get("id") or f"item_{position + 1:06d}",
            f"items[{position}].id",
        )
        report: dict[str, Any] = {
            "itemId": item_id,
            "itemName": str(raw_item.get("itemName", "")),
            "trackIndex": raw_item.get("trackIndex"),
            "trackName": str(raw_item.get("trackName", "")),
            "status": "UNSUPPORTED",
        }
        discovery_warning = raw_item.get("discoveryWarning")
        if isinstance(discovery_warning, str) and discovery_warning.strip():
            report["discoveryWarning"] = discovery_warning.strip()
        try:
            track_index = int(raw_item.get("trackIndex"))
            if track_index < 0:
                raise ValueError("trackIndex must be non-negative.")
            if raw_item.get("trackMuted") is True:
                report.update(status="SKIPPED_MUTED", reasonCode="MUTED_TRACK", reason="Audio track is muted.")
                item_reports.append(report)
                continue
            if raw_item.get("disabled") is True:
                report.update(status="SKIPPED_DISABLED", reasonCode="DISABLED_ITEM", reason="Timeline item is disabled.")
                item_reports.append(report)
                continue
            if not isinstance(raw_item.get("trackMuted", False), bool) or not isinstance(raw_item.get("disabled", False), bool):
                raise ValueError("trackMuted and disabled must be normalized booleans.")
            timeline_start_ms = _milliseconds(raw_item.get("timelineStartSeconds"), "timelineStartSeconds")
            timeline_end_ms = _milliseconds(raw_item.get("timelineEndSeconds"), "timelineEndSeconds")
            source_in_ms = _milliseconds(raw_item.get("sourceInSeconds"), "sourceInSeconds")
            source_out_ms = _milliseconds(raw_item.get("sourceOutSeconds"), "sourceOutSeconds")
            if timeline_start_ms < sequence_start_ms or timeline_end_ms > sequence_end_ms:
                raise ValueError("Timeline range lies outside the captured sequence range.")
            if timeline_end_ms <= timeline_start_ms or source_out_ms <= source_in_ms:
                raise ValueError("Timeline and source ranges must have positive duration.")
            speed = _finite_number(raw_item.get("speed"), "speed")
            reversed_value = raw_item.get("reversed")
            if not isinstance(reversed_value, bool):
                raise ValueError("reversed must be a normalized boolean.")
            if reversed_value:
                raise ValueError("Reversed timeline items are not supported.")
            if abs(speed - 1.0) > 1e-6:
                raise ValueError("Only normal-forward 1x timeline items are supported.")
            if abs((timeline_end_ms - timeline_start_ms) - (source_out_ms - source_in_ms)) > frame_duration_ms:
                raise ValueError("Timeline and source durations differ by more than one sequence frame.")
            audible_item_intervals.append((timeline_start_ms, timeline_end_ms))
            transcript, cache_hit = _resolve_transcript(
                raw_item,
                base_dir=base_dir,
                cache=transcript_cache,
            )
            if cache_hit:
                transcript_cache_hits += 1
            if transcript is None:
                report.update(
                    status="SKIPPED_NO_TRANSCRIPT",
                    reasonCode="NO_TRANSCRIPT",
                    reason="No usable source transcript is available.",
                    timelineStartMs=timeline_start_ms,
                    timelineEndMs=timeline_end_ms,
                )
                item_reports.append(report)
                continue
            normalized_item = {**raw_item, "id": item_id, "transcript": transcript}
            mapped, timing_warnings = map_words(normalized_item)
            if not mapped:
                report.update(
                    status="SKIPPED_NO_TRANSCRIPT_IN_RANGE",
                    reasonCode="NO_TRANSCRIPT_IN_RANGE",
                    reason="The transcript has no usable words in this Timeline range.",
                    timelineStartMs=timeline_start_ms,
                    timelineEndMs=timeline_end_ms,
                )
                item_reports.append(report)
                continue
            words = [
                {
                    "itemId": item_id,
                    "trackIndex": track_index,
                    "trackName": str(raw_item.get("trackName", "")),
                    "itemName": str(raw_item.get("itemName", "")),
                    "projectItemRef": str(raw_item.get("projectItemRef", "")),
                    "text": value.word.text,
                    "startMs": value.word.start_ms,
                    "endMs": value.word.end_ms,
                    "sourceStartMs": value.source_start_ms,
                    "sourceEndMs": value.source_end_ms,
                    "segmentIndex": value.segment_index,
                    "wordIndex": value.word_index,
                    "speakerId": value.speaker_id,
                    "speakerName": value.speaker_name,
                    "endOfSentence": value.end_of_sentence,
                }
                for value in mapped
            ]
            mapping_key = _mapping_key(normalized_item, words)
            if mapping_key in seen_mappings:
                report.update(
                    status="DEDUPLICATED",
                    reasonCode="EXACT_MAPPING_DUPLICATE",
                    mappedWordCount=len(words),
                )
                item_reports.append(report)
                continue
            seen_mappings.add(mapping_key)
            mapped_words.extend(words)
            mapped_item_intervals.append((timeline_start_ms, timeline_end_ms))
            language, language_issue = detect_language(transcript)
            if language_issue:
                language_issues.append({"itemId": item_id, "issue": language_issue})
            elif language:
                languages.add(language)
            report.update(
                status="MAPPED",
                mappedWordCount=len(words),
                timelineStartMs=timeline_start_ms,
                timelineEndMs=timeline_end_ms,
                timingWarnings=timing_warnings,
                transcriptLanguage=language,
            )
        except (OSError, json.JSONDecodeError, TypeError, ValueError) as exc:
            report.update(
                status="UNSUPPORTED",
                reasonCode="UNSUPPORTED_OR_INVALID_ITEM",
                reason=str(exc),
            )
        item_reports.append(report)

    overlap_regions = _overlap_regions(mapped_words)
    for word in mapped_words:
        word["parallelGroupIds"] = [
            region["id"]
            for region in overlap_regions
            if word["itemId"] in region["itemIds"]
            and word["endMs"] > region["startMs"]
            and word["startMs"] < region["endMs"]
        ]

    ordered_words = sorted(
        mapped_words,
        key=lambda value: (
            value["startMs"],
            value["endMs"],
            value["trackIndex"],
            value["itemId"],
            value["sourceStartMs"],
        ),
    )
    for index, word in enumerate(ordered_words, start=1):
        word["wordId"] = f"w{index:06d}"

    grouped_by_item: dict[str, list[dict[str, Any]]] = {}
    for word in ordered_words:
        grouped_by_item.setdefault(word["itemId"], []).append(word)
    unit_groups: list[list[dict[str, Any]]] = []
    for item_words in grouped_by_item.values():
        unit_groups.extend(_segment_item_words(item_words))
    unit_groups.sort(
        key=lambda group: (
            group[0]["startMs"],
            group[-1]["endMs"],
            group[0]["trackIndex"],
            group[0]["itemId"],
        )
    )
    units: list[dict[str, Any]] = []
    seen_item_ids: set[str] = set()
    previous_unit: dict[str, Any] | None = None
    for index, group in enumerate(unit_groups, start=1):
        item_id = group[0]["itemId"]
        unit: dict[str, Any] = {
            "id": f"u{index:06d}",
            "text": _join_words(group),
            "startWordId": group[0]["wordId"],
            "endWordId": group[-1]["wordId"],
            "startMs": group[0]["startMs"],
            "endMs": group[-1]["endMs"],
            "itemId": item_id,
            "trackIndex": group[0]["trackIndex"],
            "editBoundaryBefore": item_id not in seen_item_ids,
        }
        parallel_group_ids = group[0].get("parallelGroupIds", [])
        if len(parallel_group_ids) == 1:
            unit["parallelGroupId"] = parallel_group_ids[0]
        elif parallel_group_ids:
            unit["parallelGroupIds"] = parallel_group_ids
        speaker = _speaker_label(group)
        if speaker:
            unit["speaker"] = speaker
        if previous_unit is not None and unit["startMs"] >= previous_unit["endMs"]:
            pause_ms = unit["startMs"] - previous_unit["endMs"]
            if pause_ms >= SIGNIFICANT_PAUSE_MS:
                unit["pauseBeforeSeconds"] = round(pause_ms / 1000, 3)
        units.append(unit)
        seen_item_ids.add(item_id)
        if previous_unit is None or unit["endMs"] >= previous_unit["endMs"]:
            previous_unit = unit

    request_units: list[dict[str, Any]] = []
    for unit in units:
        request_unit = {
            "id": unit["id"],
            "text": unit["text"],
            "editBoundaryBefore": unit["editBoundaryBefore"],
        }
        for optional in (
            "speaker",
            "pauseBeforeSeconds",
            "parallelGroupId",
            "parallelGroupIds",
        ):
            if unit.get(optional) is not None:
                request_unit[optional] = unit[optional]
        request_units.append(request_unit)

    mapped_speech_ms = _interval_duration(
        [(word["startMs"], word["endMs"]) for word in ordered_words]
    )
    mapped_item_ms = _interval_duration(mapped_item_intervals)
    audible_item_ms = _interval_duration(audible_item_intervals)
    excluded_count = sum(
        report["status"]
        not in {"MAPPED", "DEDUPLICATED", "SKIPPED_MUTED", "SKIPPED_DISABLED"}
        for report in item_reports
    )
    coverage_warning = excluded_count > 0 or not units
    language_hints = sorted(languages)
    request = {
        "contractVersion": CONTRACT_VERSION,
        "profileVersion": PROFILE_VERSION,
        "workflow": workflow,
        "editorContext": _safe_string(editor_context, "editorContext", allow_empty=True),
        "sequence": {
            "displayName": sequence_name,
            "languageHints": language_hints,
            "coverageWarning": coverage_warning,
            "parallelSpeechPresent": bool(overlap_regions),
        },
        "instructions": {
            "timing": "Return only the supplied text IDs; never invent timestamps.",
            "boundaries": (
                "Pauses, edit boundaries, and parallel-group labels are context clues, "
                "not mandatory editorial boundaries."
            ),
            "coverage": (
                "The transcript view may be incomplete when coverageWarning is true; "
                "do not claim to interpret missing or unsupported regions."
            ),
        },
        "units": request_units,
    }
    profile = _profile_request(request)
    coverage = {
        "contractVersion": CONTRACT_VERSION,
        "status": "READY_WITH_WARNINGS" if coverage_warning else "READY",
        "sequenceName": sequence_name,
        "sequenceStartMs": sequence_start_ms,
        "sequenceEndMs": sequence_end_ms,
        "sequenceDurationMs": sequence_end_ms - sequence_start_ms,
        "mappedSpeechDurationMs": mapped_speech_ms,
        "mappedItemTimelineDurationMs": mapped_item_ms,
        "audibleEligibleTimelineDurationMs": audible_item_ms,
        "mappedSpeechPercentOfSequence": round(mapped_speech_ms * 100 / (sequence_end_ms - sequence_start_ms), 3),
        "mappedItemPercentOfAudibleEligibleTimeline": (
            round(mapped_item_ms * 100 / audible_item_ms, 3) if audible_item_ms else 0
        ),
        "mappedItemCount": sum(report["status"] == "MAPPED" for report in item_reports),
        "deduplicatedItemCount": sum(report["status"] == "DEDUPLICATED" for report in item_reports),
        "excludedItemCount": excluded_count,
        "mutedItemCount": sum(report["status"] == "SKIPPED_MUTED" for report in item_reports),
        "disabledItemCount": sum(report["status"] == "SKIPPED_DISABLED" for report in item_reports),
        "wordCount": len(ordered_words),
        "unitCount": len(units),
        "parallelGroupCount": len(overlap_regions),
        "distinctTranscriptCount": len(transcript_cache),
        "transcriptReuseCount": transcript_cache_hits,
        "languageHints": language_hints,
        "languageIssues": language_issues,
        "itemReports": item_reports,
        "parallelGroups": overlap_regions,
        "providerProfile": profile,
    }
    sidecar = {
        "contractVersion": CONTRACT_VERSION,
        "workflow": workflow,
        "sequence": {
            "name": sequence_name,
            "ref": _safe_string(manifest.get("sequenceRef", ""), "sequenceRef", allow_empty=True),
            "startMs": sequence_start_ms,
            "endMs": sequence_end_ms,
            "frameDurationMs": frame_duration_ms,
            "zeroPointMs": zero_point_ms,
        },
        "words": ordered_words,
        "units": units,
        "parallelGroups": overlap_regions,
    }
    return {
        "request": request,
        "timingSidecar": sidecar,
        "coverageReport": coverage,
        "providerProfile": profile,
    }


def coverage_markdown(report: dict[str, Any]) -> str:
    lines = [
        "# Transcript coverage",
        "",
        f"- Status: `{report['status']}`",
        f"- Sequence: `{report['sequenceName']}`",
        f"- Mapped transcript items: `{report['mappedItemCount']}`",
        f"- Excluded or missing-transcript items: `{report['excludedItemCount']}`",
        f"- Exact duplicates ignored: `{report['deduplicatedItemCount']}`",
        f"- Transcript words / semantic units: `{report['wordCount']}` / `{report['unitCount']}`",
        f"- Parallel speech groups: `{report['parallelGroupCount']}`",
        f"- Mapped speech coverage: `{report['mappedSpeechPercentOfSequence']}%` of sequence duration",
        "",
        "## Item details",
        "",
    ]
    for item in report["itemReports"]:
        reason = f" — {item['reason']}" if item.get("reason") else ""
        lines.append(
            f"- `{item['itemId']}` / `{item.get('itemName', '')}`: "
            f"**{item['status']}**{reason}"
        )
    if report["providerProfile"]["warnings"]:
        lines.extend(["", "## Provider profile warnings", ""])
        for warning in report["providerProfile"]["warnings"]:
            lines.append(f"- `{warning['reasonCode']}`")
    return "\n".join(lines).rstrip() + "\n"


def _write_json(path: Path, value: Any) -> None:
    path.write_text(json.dumps(value, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")


def _prepare_command(args: argparse.Namespace) -> int:
    manifest_path = Path(args.manifest).resolve()
    manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
    if not isinstance(manifest, dict):
        raise ValueError("Sequence manifest JSON root must be an object.")
    if args.zero_point_seconds is not None:
        manifest["zeroPointSeconds"] = _finite_number(
            args.zero_point_seconds, "zeroPointSeconds"
        )
    editor_context = (
        Path(args.editor_context_file).resolve().read_text(encoding="utf-8").strip()
        if args.editor_context_file
        else args.editor_context
    )
    result = prepare_sequence(
        manifest,
        base_dir=manifest_path.parent,
        workflow=args.workflow,
        editor_context=editor_context,
        srt_toolkit_dir=Path(args.srt_toolkit_dir).resolve() if args.srt_toolkit_dir else None,
    )
    output = Path(args.output_dir).resolve()
    output.mkdir(parents=True, exist_ok=True)
    _write_json(output / "request.json", result["request"])
    _write_json(output / "timing-sidecar.json", result["timingSidecar"])
    _write_json(output / "coverage-report.json", result["coverageReport"])
    _write_json(output / "provider-profile.json", result["providerProfile"])
    (output / "coverage-report.md").write_text(
        coverage_markdown(result["coverageReport"]),
        encoding="utf-8",
    )
    return 0


def _agent_request_command(args: argparse.Namespace) -> int:
    from contracts import global_agent_request

    request = json.loads(Path(args.request).read_text(encoding="utf-8"))
    agent_request = global_agent_request(
        request,
        workflow=args.workflow,
        density=args.density,
        emphasis=args.emphasis,
        candidate_count=args.candidate_count,
    )
    output = Path(args.output_dir).resolve()
    output.mkdir(parents=True, exist_ok=True)
    _write_json(output / "global-agent-request.json", agent_request)
    _write_json(output / "global-output-schema.json", agent_request["outputSchema"])
    (output / "global-system-prompt.txt").write_text(
        agent_request["systemPrompt"],
        encoding="utf-8",
    )
    (output / "global-user-prompt.txt").write_text(
        agent_request["userPrompt"],
        encoding="utf-8",
    )
    return 0


def _validate_command(args: argparse.Namespace) -> int:
    from contracts import boundary_agent_request, validate_global_response

    sidecar = json.loads(Path(args.sidecar).read_text(encoding="utf-8"))
    response = json.loads(Path(args.response).read_text(encoding="utf-8"))
    validated = validate_global_response(sidecar, response, workflow=args.workflow)
    output = Path(args.output_dir).resolve()
    output.mkdir(parents=True, exist_ok=True)
    _write_json(output / "validated-global-response.json", validated)
    boundary_request = boundary_agent_request(sidecar, validated, workflow=args.workflow)
    if boundary_request is not None:
        _write_json(output / "boundary-agent-request.json", boundary_request)
        _write_json(output / "boundary-output-schema.json", boundary_request["outputSchema"])
        (output / "boundary-system-prompt.txt").write_text(
            boundary_request["systemPrompt"],
            encoding="utf-8",
        )
        (output / "boundary-user-prompt.txt").write_text(
            boundary_request["userPrompt"],
            encoding="utf-8",
        )
    return 0


def _materialize_command(args: argparse.Namespace) -> int:
    from contracts import materialize_marker_plan, validate_global_response, write_materialized_outputs

    sidecar = json.loads(Path(args.sidecar).read_text(encoding="utf-8"))
    coverage = json.loads(Path(args.coverage).read_text(encoding="utf-8"))
    response = json.loads(Path(args.response).read_text(encoding="utf-8"))
    validated = validate_global_response(sidecar, response, workflow=args.workflow)
    boundary_response = (
        json.loads(Path(args.boundary_response).read_text(encoding="utf-8"))
        if args.boundary_response
        else None
    )
    plan = materialize_marker_plan(
        sidecar,
        coverage,
        validated,
        workflow=args.workflow,
        run_id=args.run_id,
        boundary_response=boundary_response,
    )
    write_materialized_outputs(Path(args.output_dir).resolve(), plan)
    return 0


def _snapshot_markers_command(args: argparse.Namespace) -> int:
    from contracts import snapshot_marker_inventory
    output = Path(args.output).resolve()
    output.parent.mkdir(parents=True, exist_ok=True)
    output.write_text(
        json.dumps(snapshot_marker_inventory(Path(args.inventory), workflow=args.workflow), ensure_ascii=False, indent=2) + "\n",
        encoding="utf-8",
    )
    return 0


def _verify_created_markers_command(args: argparse.Namespace) -> int:
    from contracts import verify_created_marker_inventory
    baseline = json.loads(Path(args.baseline).read_text(encoding="utf-8"))
    plan = json.loads(Path(args.plan).read_text(encoding="utf-8"))
    result = verify_created_marker_inventory(
        baseline, Path(args.inventory), plan, output_dir=Path(args.output_dir).resolve()
    )
    (Path(args.output_dir).resolve() / "created-marker-audit.json").write_text(
        json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
    )
    return 0


def _verify_final_markers_command(args: argparse.Namespace) -> int:
    from contracts import verify_final_marker_inventory
    baseline = json.loads(Path(args.baseline).read_text(encoding="utf-8"))
    plan = json.loads(Path(args.plan).read_text(encoding="utf-8"))
    result = verify_final_marker_inventory(baseline, Path(args.inventory), plan)
    output = Path(args.output).resolve()
    output.write_text(
        json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
    )
    remaining = int(result["priorOwnedMarkersRemaining"])
    if remaining:
        warning = (
            f"The new marker run is intact, but {remaining} prior owned marker(s) "
            "could not be retired. Both tagged runs remain for editor review."
        )
        output.with_name("retirement-warning.txt").write_text(warning + "\n", encoding="utf-8")
        sys.stdout.write(f"warning-prior-owned-markers-remain={remaining}")
    else:
        sys.stdout.write("ok")
    return 0


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(description="Prepare transcript-only semantic sequence context.")
    subparsers = parser.add_subparsers(dest="command", required=True)
    prepare = subparsers.add_parser("prepare")
    prepare.add_argument("--manifest", required=True)
    prepare.add_argument("--output-dir", required=True)
    prepare.add_argument("--workflow", choices=("chapters", "highlights"), required=True)
    prepare.add_argument("--editor-context", default="")
    prepare.add_argument("--editor-context-file")
    prepare.add_argument("--srt-toolkit-dir")
    prepare.add_argument("--zero-point-seconds", type=float)
    prepare.set_defaults(handler=_prepare_command)
    agent_request = subparsers.add_parser("agent-request")
    agent_request.add_argument("--request", required=True)
    agent_request.add_argument("--output-dir", required=True)
    agent_request.add_argument("--workflow", choices=("chapters", "highlights"), required=True)
    agent_request.add_argument("--density", choices=("Automatic", "Fewer", "More"), default="Automatic")
    agent_request.add_argument(
        "--emphasis",
        choices=("Balanced", "Teasers", "Highlights", "Social cutdowns"),
        default="Balanced",
    )
    agent_request.add_argument(
        "--candidate-count",
        choices=("Automatic", "Fewer", "More"),
        default="Automatic",
    )
    agent_request.set_defaults(handler=_agent_request_command)
    validate = subparsers.add_parser("validate")
    validate.add_argument("--sidecar", required=True)
    validate.add_argument("--response", required=True)
    validate.add_argument("--output-dir", required=True)
    validate.add_argument("--workflow", choices=("chapters", "highlights"), required=True)
    validate.set_defaults(handler=_validate_command)
    materialize = subparsers.add_parser("materialize")
    materialize.add_argument("--sidecar", required=True)
    materialize.add_argument("--coverage", required=True)
    materialize.add_argument("--response", required=True)
    materialize.add_argument("--boundary-response")
    materialize.add_argument("--output-dir", required=True)
    materialize.add_argument("--workflow", choices=("chapters", "highlights"), required=True)
    materialize.add_argument("--run-id", required=True)
    materialize.set_defaults(handler=_materialize_command)
    snapshot = subparsers.add_parser("snapshot-markers")
    snapshot.add_argument("--inventory", required=True)
    snapshot.add_argument("--output", required=True)
    snapshot.add_argument("--workflow", choices=("chapters", "highlights"), required=True)
    snapshot.set_defaults(handler=_snapshot_markers_command)
    verify_created = subparsers.add_parser("verify-created-markers")
    verify_created.add_argument("--baseline", required=True)
    verify_created.add_argument("--inventory", required=True)
    verify_created.add_argument("--plan", required=True)
    verify_created.add_argument("--output-dir", required=True)
    verify_created.set_defaults(handler=_verify_created_markers_command)
    verify_final = subparsers.add_parser("verify-final-markers")
    verify_final.add_argument("--baseline", required=True)
    verify_final.add_argument("--inventory", required=True)
    verify_final.add_argument("--plan", required=True)
    verify_final.add_argument("--output", required=True)
    verify_final.set_defaults(handler=_verify_final_markers_command)
    return parser


def main(argv: list[str] | None = None) -> int:
    args = build_parser().parse_args(argv)
    try:
        return int(args.handler(args))
    except Exception as exc:  # noqa: BLE001 - CLI must return one concise failure.
        print(f"ERROR: {exc}", file=sys.stderr)
        return 1


if __name__ == "__main__":
    raise SystemExit(main())
