from __future__ import annotations

import argparse
from concurrent.futures import ThreadPoolExecutor, as_completed
import json
from dataclasses import dataclass
from pathlib import Path
import re
from typing import Any, Callable, Optional

try:
    from .get_clip_by_eventid import get_associated_clip_ids
    from .pdcl_clip_export_utils import (
        build_clip_tasks_from_clip_ids,
        run_clip_tasks_inference_exported,
    )
except ImportError:
    from get_clip_by_eventid import get_associated_clip_ids
    from pdcl_clip_export_utils import (
        build_clip_tasks_from_clip_ids,
        run_clip_tasks_inference_exported,
    )

DEFAULT_EVENT_CACHE_FILE = Path(__file__).resolve().parents[1] / ".cache" / "event_clip_cache.json"


@dataclass(frozen=True)
class ResolvedEventRecord:
    scene: str
    record_index: int
    event_id: str
    event_id_field_used: str
    source_record: dict[str, Any]
    clip_ids: list[str]
    clip_source: str


@dataclass(frozen=True)
class EventResolutionStats:
    total_events: int
    cache_hits: int
    cache_misses: int
    request_workers: int
    cache_file: str
    direct_clip_records: int = 0
    event_lookup_records: int = 0


def _dedupe_preserve_order(values: list[str]) -> list[str]:
    ordered: list[str] = []
    seen: set[str] = set()
    for value in values:
        token = str(value).strip()
        if not token or token in seen:
            continue
        seen.add(token)
        ordered.append(token)
    return ordered


def _sanitize_identifier_for_path(identifier: str, prefix: str = "event_id") -> str:
    token = re.sub(r'[\\/:*?"<>|\s]+', "_", str(identifier or "").strip())
    token = token.strip("._")
    token = token or "unknown_id"
    normalized_prefix = re.sub(r"[^0-9A-Za-z]+", "_", str(prefix or "").strip())
    normalized_prefix = normalized_prefix.strip("._") or "id"
    return f"{normalized_prefix}_{token}"


def _build_resolution_stats_payload(resolution_stats: "EventResolutionStats") -> dict[str, Any]:
    return {
        "total_events": resolution_stats.total_events,
        "cache_hits": resolution_stats.cache_hits,
        "cache_misses": resolution_stats.cache_misses,
        "request_workers": resolution_stats.request_workers,
        "cache_file": resolution_stats.cache_file,
        "direct_clip_records": resolution_stats.direct_clip_records,
        "event_lookup_records": resolution_stats.event_lookup_records,
    }


def _record_key(record: "ResolvedEventRecord") -> tuple[str, int, str]:
    return (record.scene, int(record.record_index), str(record.event_id))


def _normalize_direct_clip_ids(raw_value: Any) -> list[str]:
    if isinstance(raw_value, list):
        return _dedupe_preserve_order([str(item).strip() for item in raw_value if str(item).strip()])

    if isinstance(raw_value, str):
        text = raw_value.strip()
        if not text:
            return []
        if text.startswith("["):
            try:
                parsed = json.loads(text)
            except Exception:
                parsed = None
            if isinstance(parsed, list):
                return _dedupe_preserve_order([str(item).strip() for item in parsed if str(item).strip()])
        return _dedupe_preserve_order([token for token in re.split(r"[\s,]+", text) if token])

    if raw_value is None:
        return []

    token = str(raw_value).strip()
    if not token:
        return []
    return [token]


def _extract_direct_clip_ids(record: dict[str, Any], clip_ids_field: str) -> tuple[bool, list[str]]:
    field_name = str(clip_ids_field or "").strip()
    if not field_name or field_name not in record:
        return False, []
    return True, _normalize_direct_clip_ids(record.get(field_name))


def _resolve_record_identifier(
    record: dict[str, Any],
    preferred_field: str,
    *,
    allow_direct_clip_fallback: bool = False,
) -> tuple[str, str]:
    candidate_fields: list[str] = []
    preferred_token = str(preferred_field or "").strip()
    if preferred_token:
        candidate_fields.append(preferred_token)

    if allow_direct_clip_fallback:
        for field_name in ("rawid", "event_id", "data_path"):
            if field_name not in candidate_fields:
                candidate_fields.append(field_name)

    for field_name in candidate_fields:
        identifier = str(record.get(field_name, "")).strip()
        if identifier:
            return identifier, field_name

    return "", preferred_token


def _extract_condition_values(source_record: dict[str, Any], condition_fields: list[str]) -> dict[str, str]:
    return {
        str(field): str(source_record.get(field, "")).strip()
        for field in condition_fields
    }


def _select_event_records_by_condition(
    records: list["ResolvedEventRecord"],
    *,
    condition_fields: list[str],
    max_records_per_condition: int,
    selection_strategy: str,
) -> tuple[list["ResolvedEventRecord"], dict[str, Any]]:
    normalized_fields = [str(field).strip() for field in condition_fields if str(field).strip()]
    limit = max(0, int(max_records_per_condition))
    selection_enabled = bool(normalized_fields and limit > 0)
    summary: dict[str, Any] = {
        "enabled": selection_enabled,
        "condition_fields": normalized_fields,
        "max_records_per_condition": limit,
        "selection_strategy": selection_strategy,
        "records_before_selection": len(records),
        "records_after_selection": len(records),
        "group_count": 0,
        "groups": [],
    }
    if not selection_enabled:
        return list(records), summary

    if selection_strategy != "first":
        raise ValueError(f"Unsupported condition selection strategy: {selection_strategy!r}")

    selected_records: list[ResolvedEventRecord] = []
    selected_counts: dict[tuple[str, tuple[str, ...]], int] = {}
    group_summaries: dict[tuple[str, tuple[str, ...]], dict[str, Any]] = {}

    for record in records:
        condition_values = _extract_condition_values(record.source_record, normalized_fields)
        condition_tuple = tuple(condition_values[field] for field in normalized_fields)
        group_key = (record.scene, condition_tuple)
        group_summary = group_summaries.setdefault(
            group_key,
            {
                "scene": record.scene,
                "condition_values": condition_values,
                "record_count": 0,
                "selected_count": 0,
                "skipped_count": 0,
                "selected_record_ids": [],
                "selected_record_indices": [],
                "skipped_record_ids": [],
                "skipped_record_indices": [],
            },
        )
        group_summary["record_count"] += 1
        current_selected_count = selected_counts.get(group_key, 0)

        if current_selected_count < limit:
            selected_records.append(record)
            selected_counts[group_key] = current_selected_count + 1
            group_summary["selected_count"] += 1
            group_summary["selected_record_ids"].append(record.event_id)
            group_summary["selected_record_indices"].append(record.record_index)
            continue

        group_summary["skipped_count"] += 1
        group_summary["skipped_record_ids"].append(record.event_id)
        group_summary["skipped_record_indices"].append(record.record_index)

    summary["records_after_selection"] = len(selected_records)
    summary["group_count"] = len(group_summaries)
    summary["groups"] = list(group_summaries.values())
    return selected_records, summary


def _filter_selection_summary_for_scene(selection_summary: dict[str, Any], scene: str) -> dict[str, Any]:
    if not selection_summary:
        return {}

    scene_groups = [
        dict(group)
        for group in selection_summary.get("groups", [])
        if str(group.get("scene", "")) == str(scene)
    ]
    filtered = dict(selection_summary)
    filtered["groups"] = scene_groups
    filtered["group_count"] = len(scene_groups)
    if scene_groups:
        filtered["records_before_selection"] = sum(int(group.get("record_count", 0)) for group in scene_groups)
        filtered["records_after_selection"] = sum(int(group.get("selected_count", 0)) for group in scene_groups)
    else:
        filtered["records_before_selection"] = 0
        filtered["records_after_selection"] = 0
    return filtered


def _format_condition_summary(source_record: dict[str, Any], condition_fields: list[str]) -> str:
    normalized_fields = [str(field).strip() for field in condition_fields if str(field).strip()]
    if not normalized_fields:
        return ""
    values = _extract_condition_values(source_record, normalized_fields)
    return ", ".join(f"{field}={values.get(field, '')}" for field in normalized_fields)


def _print_event_json_summary(
    *,
    args: argparse.Namespace,
    manifest_path: Path,
    resolved_records: list["ResolvedEventRecord"],
    selected_records: list["ResolvedEventRecord"],
    selection_summary: dict[str, Any],
    scene_results: dict[str, dict[str, Any]],
) -> None:
    print(f"Event JSON file: {args.event_json_file}")
    if args.scene:
        print(f"Scene filter: {args.scene}")
    print(
        "Resolved records: "
        f"{len(resolved_records)}, selected records: {len(selected_records)}"
    )
    print(f"Manifest: {manifest_path}")

    if not selected_records:
        print("No records selected.")
        return

    condition_fields = [str(field).strip() for field in getattr(args, "condition_fields", []) if str(field).strip()]
    if args.selection_only:
        print("Selection-only mode: no clip export or inference was run.")
    elif selection_summary.get("enabled"):
        print(
            "Condition selection: "
            f"{selection_summary.get('group_count', 0)} groups, "
            f"strategy={selection_summary.get('selection_strategy', '')}, "
            f"max_records_per_condition={selection_summary.get('max_records_per_condition', 0)}"
        )

    for scene in sorted(scene_results):
        scene_result = scene_results[scene]
        print(
            f"Scene {scene}: selected_record_count="
            f"{scene_result.get('selected_record_count', len([record for record in selected_records if record.scene == scene]))}"
        )
        scene_manifest_path = scene_result.get("scene_manifest_path")
        if scene_manifest_path:
            print(f"  Scene manifest: {scene_manifest_path}")
        if scene_result.get("scene_output_dir"):
            print(f"  Output dir: {scene_result['scene_output_dir']}")

        scene_records = [record for record in selected_records if record.scene == scene]
        for record in scene_records:
            condition_summary = _format_condition_summary(record.source_record, condition_fields)
            summary_parts = []
            if condition_summary:
                summary_parts.append(condition_summary)
            summary_parts.append(f"record_id={record.event_id}")
            summary_parts.append(f"clips={len(record.clip_ids)}")
            print(f"  - {'; '.join(summary_parts)}")


def _build_scene_manifest_payload(
    *,
    args: argparse.Namespace,
    scene: str,
    scene_records: list["ResolvedEventRecord"],
    resolution_stats: "EventResolutionStats",
    selection_summary: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    payload = {
        "event_json_file": args.event_json_file,
        "scene": scene,
        "event_id_field": args.event_id_field,
        "event_clip_ids_field": args.event_clip_ids_field,
        "resolution_stats": _build_resolution_stats_payload(resolution_stats),
        "event_record_count": len(scene_records),
        "records": [
            {
                "record_index": record.record_index,
                "event_id": record.event_id,
                "event_id_field_used": record.event_id_field_used,
                "clip_ids": record.clip_ids,
                "clip_source": record.clip_source,
                "source_record": record.source_record,
            }
            for record in scene_records
        ],
    }
    if selection_summary is not None:
        payload["selection"] = selection_summary
    return payload


def _build_event_manifest_payload(
    *,
    args: argparse.Namespace,
    scene: str,
    event_id: str,
    event_records: list["ResolvedEventRecord"],
    clip_ids: list[str],
    resolution_stats: "EventResolutionStats",
) -> dict[str, Any]:
    condition_fields = [str(field).strip() for field in getattr(args, "condition_fields", []) if str(field).strip()]
    payload = {
        "event_json_file": args.event_json_file,
        "scene": scene,
        "event_id_field": args.event_id_field,
        "event_clip_ids_field": args.event_clip_ids_field,
        "event_id": event_id,
        "resolution_stats": _build_resolution_stats_payload(resolution_stats),
        "event_record_count": len(event_records),
        "clip_ids": clip_ids,
        "clip_count": len(clip_ids),
        "records": [
            {
                "record_index": record.record_index,
                "event_id": record.event_id,
                "event_id_field_used": record.event_id_field_used,
                "clip_ids": record.clip_ids,
                "clip_source": record.clip_source,
                "source_record": record.source_record,
            }
            for record in event_records
        ],
    }
    if event_records:
        payload["event_id_field_used"] = event_records[0].event_id_field_used
        payload["clip_source"] = sorted({record.clip_source for record in event_records})
    if condition_fields and event_records:
        payload["condition_values"] = _extract_condition_values(event_records[0].source_record, condition_fields)
    return payload


def load_event_scene_json(json_file: str) -> dict[str, list[dict[str, Any]]]:
    path = Path(json_file)
    with path.open("r", encoding="utf-8") as file:
        payload = json.load(file)
    if not isinstance(payload, dict):
        raise ValueError(f"Expected top-level dict in {path}, got {type(payload).__name__}")

    normalized: dict[str, list[dict[str, Any]]] = {}
    for scene, records in payload.items():
        if not isinstance(records, list):
            raise ValueError(f"Scene {scene!r} should map to a list, got {type(records).__name__}")
        normalized[str(scene)] = [dict(item) if isinstance(item, dict) else {"value": item} for item in records]
    return normalized


def load_event_clip_cache(cache_file: str | Path) -> dict[str, list[str]]:
    cache_path = Path(cache_file)
    if not cache_path.exists():
        return {}

    try:
        payload = json.loads(cache_path.read_text(encoding="utf-8"))
    except Exception:
        return {}

    if not isinstance(payload, dict):
        return {}

    cache: dict[str, list[str]] = {}
    for event_id, clip_ids in payload.items():
        if isinstance(clip_ids, list):
            cache[str(event_id)] = [str(item).strip() for item in clip_ids if str(item).strip()]
    return cache


def save_event_clip_cache(cache_file: str | Path, cache_payload: dict[str, list[str]]) -> Path:
    cache_path = Path(cache_file)
    cache_path.parent.mkdir(parents=True, exist_ok=True)
    normalized = {
        str(event_id): [str(item).strip() for item in clip_ids if str(item).strip()]
        for event_id, clip_ids in cache_payload.items()
    }
    with cache_path.open("w", encoding="utf-8") as file:
        json.dump(normalized, file, indent=2, ensure_ascii=False)
    return cache_path


def resolve_event_clip_ids(
    event_ids: list[str],
    *,
    timeout: float = 60.0,
    cache_file: str | Path = DEFAULT_EVENT_CACHE_FILE,
    workers: int = 4,
    max_retries: int = 3,
    retry_backoff_sec: float = 2.0,
) -> tuple[dict[str, list[str]], EventResolutionStats]:
    ordered_event_ids: list[str] = []
    seen: set[str] = set()
    for event_id in event_ids:
        normalized = str(event_id).strip()
        if not normalized or normalized in seen:
            continue
        seen.add(normalized)
        ordered_event_ids.append(normalized)

    cache_payload = load_event_clip_cache(cache_file)
    resolved: dict[str, list[str]] = {}
    unresolved_event_ids: list[str] = []
    cache_hits = 0
    cache_misses = 0

    for event_id in ordered_event_ids:
        if event_id in cache_payload:
            resolved[event_id] = list(cache_payload[event_id])
            cache_hits += 1
        else:
            unresolved_event_ids.append(event_id)
            cache_misses += 1

    if unresolved_event_ids:
        max_workers = max(1, min(int(workers), len(unresolved_event_ids)))
        if max_workers == 1:
            for event_id in unresolved_event_ids:
                clip_ids = get_associated_clip_ids(
                    event_id,
                    timeout=timeout,
                    max_retries=max_retries,
                    retry_backoff_sec=retry_backoff_sec,
                )
                resolved[event_id] = clip_ids
                cache_payload[event_id] = clip_ids
        else:
            with ThreadPoolExecutor(max_workers=max_workers) as executor:
                future_to_event_id = {
                    executor.submit(
                        get_associated_clip_ids,
                        event_id,
                        timeout,
                        max_retries,
                        retry_backoff_sec,
                    ): event_id
                    for event_id in unresolved_event_ids
                }
                for future in as_completed(future_to_event_id):
                    event_id = future_to_event_id[future]
                    clip_ids = future.result()
                    resolved[event_id] = clip_ids
                    cache_payload[event_id] = clip_ids
        save_event_clip_cache(cache_file, cache_payload)
        worker_count = max(1, min(int(workers), len(unresolved_event_ids)))
    else:
        worker_count = 0

    stats = EventResolutionStats(
        total_events=len(ordered_event_ids),
        cache_hits=cache_hits,
        cache_misses=cache_misses,
        request_workers=worker_count,
        cache_file=str(Path(cache_file).resolve()),
    )
    return resolved, stats


def resolve_event_records(
    json_file: str,
    scene_filter: Optional[str] = None,
    event_id_field: str = "data_path",
    clip_ids_field: str = "clips",
    max_events: int = 0,
    timeout: float = 60.0,
    cache_file: str | Path = DEFAULT_EVENT_CACHE_FILE,
    workers: int = 4,
    max_retries: int = 3,
    retry_backoff_sec: float = 2.0,
) -> tuple[list[ResolvedEventRecord], EventResolutionStats]:
    scene_payload = load_event_scene_json(json_file)
    scene_names = [scene_filter] if scene_filter else list(scene_payload)
    pending_records: list[tuple[str, int, dict[str, Any], str, str, list[str], str]] = []
    lookup_event_ids: list[str] = []
    processed = 0
    direct_clip_records = 0
    event_lookup_records = 0

    for scene in scene_names:
        records = scene_payload.get(scene)
        if records is None:
            raise ValueError(f"Scene {scene!r} not found in {json_file}")

        for index, record in enumerate(records):
            has_direct_clip_ids, direct_clip_ids = _extract_direct_clip_ids(record, clip_ids_field)
            event_id, resolved_id_field = _resolve_record_identifier(
                record,
                event_id_field,
                allow_direct_clip_fallback=has_direct_clip_ids,
            )
            if not event_id:
                continue

            clip_source = "direct_clip_ids_field" if has_direct_clip_ids else "event_lookup"
            if has_direct_clip_ids:
                direct_clip_records += 1
            else:
                event_lookup_records += 1
                lookup_event_ids.append(event_id)

            pending_records.append(
                (scene, index, record, event_id, resolved_id_field, direct_clip_ids, clip_source)
            )
            processed += 1
            if max_events > 0 and processed >= max_events:
                break
        if max_events > 0 and processed >= max_events:
            break

    resolved_clip_map: dict[str, list[str]] = {}
    lookup_stats = EventResolutionStats(
        total_events=0,
        cache_hits=0,
        cache_misses=0,
        request_workers=0,
        cache_file=str(Path(cache_file).resolve()),
    )
    if lookup_event_ids:
        resolved_clip_map, lookup_stats = resolve_event_clip_ids(
            lookup_event_ids,
            timeout=timeout,
            cache_file=cache_file,
            workers=workers,
            max_retries=max_retries,
            retry_backoff_sec=retry_backoff_sec,
        )

    resolved = [
        ResolvedEventRecord(
            scene=scene,
            record_index=index,
            event_id=event_id,
            event_id_field_used=resolved_id_field,
            source_record=record,
            clip_ids=direct_clip_ids if clip_source == "direct_clip_ids_field" else resolved_clip_map.get(event_id, []),
            clip_source=clip_source,
        )
        for scene, index, record, event_id, resolved_id_field, direct_clip_ids, clip_source in pending_records
    ]
    stats = EventResolutionStats(
        total_events=len(pending_records),
        cache_hits=lookup_stats.cache_hits,
        cache_misses=lookup_stats.cache_misses,
        request_workers=lookup_stats.request_workers,
        cache_file=str(Path(cache_file).resolve()),
        direct_clip_records=direct_clip_records,
        event_lookup_records=event_lookup_records,
    )
    return resolved, stats


def build_event_resolution_payload(
    json_file: str,
    resolved_records: list[ResolvedEventRecord],
    event_id_field: str,
    clip_ids_field: str,
    stats: EventResolutionStats,
    selection_summary: Optional[dict[str, Any]] = None,
    selected_record_keys: Optional[set[tuple[str, int, str]]] = None,
) -> dict[str, Any]:
    selection_enabled = bool(selection_summary and selection_summary.get("enabled"))
    selected_keys = selected_record_keys or set()
    scenes: dict[str, list[dict[str, Any]]] = {}
    for record in resolved_records:
        scenes.setdefault(record.scene, []).append(
            {
                "record_index": record.record_index,
                "event_id_field": event_id_field,
                "event_id_field_used": record.event_id_field_used,
                "event_id": record.event_id,
                "event_clip_ids_field": clip_ids_field,
                "clip_ids": record.clip_ids,
                "clip_count": len(record.clip_ids),
                "clip_source": record.clip_source,
                "selected_for_inference": True if not selection_enabled else _record_key(record) in selected_keys,
                "source_record": record.source_record,
            }
        )

    payload = {
        "event_json_file": json_file,
        "event_id_field": event_id_field,
        "event_clip_ids_field": clip_ids_field,
        "scene_count": len(scenes),
        "record_count": len(resolved_records),
        "selected_record_count": len(selected_keys) if selection_enabled else len(resolved_records),
        "resolution_stats": _build_resolution_stats_payload(stats),
        "scenes": scenes,
    }
    if selection_summary is not None:
        payload["selection"] = selection_summary
    return payload


def save_event_resolution_manifest(
    output_root: Path,
    json_file: str,
    resolved_records: list[ResolvedEventRecord],
    event_id_field: str,
    clip_ids_field: str,
    stats: EventResolutionStats,
    selection_summary: Optional[dict[str, Any]] = None,
    selected_record_keys: Optional[set[tuple[str, int, str]]] = None,
) -> Path:
    manifest_path = output_root / "_status" / "event_scene_manifest.json"
    manifest_path.parent.mkdir(parents=True, exist_ok=True)
    payload = build_event_resolution_payload(
        json_file,
        resolved_records,
        event_id_field,
        clip_ids_field,
        stats,
        selection_summary=selection_summary,
        selected_record_keys=selected_record_keys,
    )
    with manifest_path.open("w", encoding="utf-8") as file:
        json.dump(payload, file, indent=2, ensure_ascii=False)
    return manifest_path


def run_event_json_inference_exported(
    *,
    context: Any,
    args: argparse.Namespace,
    load_env: Callable[[], Any],
    run_case_inference: Callable[..., dict[str, Any]],
) -> dict[str, Any]:
    resolved_records, resolution_stats = resolve_event_records(
        json_file=args.event_json_file,
        scene_filter=args.scene,
        event_id_field=args.event_id_field,
        clip_ids_field=args.event_clip_ids_field,
        max_events=args.max_events,
        timeout=args.event_request_timeout,
        cache_file=args.event_cache_file,
        workers=args.event_resolve_workers,
        max_retries=args.event_request_retries,
        retry_backoff_sec=args.event_request_retry_backoff_sec,
    )
    selected_records, selection_summary = _select_event_records_by_condition(
        resolved_records,
        condition_fields=[str(field).strip() for field in getattr(args, "condition_fields", []) if str(field).strip()],
        max_records_per_condition=int(getattr(args, "max_records_per_condition", 0)),
        selection_strategy=str(getattr(args, "condition_select_strategy", "first")),
    )
    selected_record_keys = {_record_key(record) for record in selected_records}
    output_root = Path(args.output_dir).resolve()
    output_root.mkdir(parents=True, exist_ok=True)
    manifest_path = save_event_resolution_manifest(
        output_root=output_root,
        json_file=args.event_json_file,
        resolved_records=resolved_records,
        event_id_field=args.event_id_field,
        clip_ids_field=args.event_clip_ids_field,
        stats=resolution_stats,
        selection_summary=selection_summary,
        selected_record_keys=selected_record_keys,
    )

    summary_by_scene: dict[str, dict[str, Any]] = {}
    for scene in sorted({record.scene for record in selected_records}):
        scene_records = [record for record in selected_records if record.scene == scene]
        scene_selection_summary = _filter_selection_summary_for_scene(selection_summary, scene)
        scene_export_root = Path(args.export_root).resolve() / scene
        scene_output_root = output_root / scene
        scene_output_root.mkdir(parents=True, exist_ok=True)
        scene_manifest_path = scene_output_root / "_status" / "scene_event_manifest.json"
        scene_manifest_path.parent.mkdir(parents=True, exist_ok=True)
        with scene_manifest_path.open("w", encoding="utf-8") as file:
            json.dump(
                _build_scene_manifest_payload(
                    args=args,
                    scene=scene,
                    scene_records=scene_records,
                    resolution_stats=resolution_stats,
                    selection_summary=scene_selection_summary,
                ),
                file,
                indent=2,
                ensure_ascii=False,
            )

        if args.selection_only:
            summary_by_scene[scene] = {
                "scene_output_dir": str(scene_output_root),
                "scene_manifest_path": str(scene_manifest_path),
                "selected_record_count": len(scene_records),
                "selection_only": True,
            }
            continue

        event_records_by_id: dict[str, list[ResolvedEventRecord]] = {}
        event_order: list[str] = []
        for record in scene_records:
            if record.event_id not in event_records_by_id:
                event_order.append(record.event_id)
            event_records_by_id.setdefault(record.event_id, []).append(record)

        selected_scene_clip_ids: set[str] = set()
        event_results: dict[str, dict[str, Any]] = {}
        event_dirs: dict[str, str] = {}
        event_export_dirs: dict[str, str] = {}
        for event_id in event_order:
            event_records = event_records_by_id[event_id]
            event_clip_ids = _dedupe_preserve_order(
                [clip_id for record in event_records for clip_id in record.clip_ids]
            )
            if args.limit_clips > 0:
                limited_clip_ids: list[str] = []
                for clip_id in event_clip_ids:
                    if clip_id in selected_scene_clip_ids:
                        limited_clip_ids.append(clip_id)
                        continue
                    if len(selected_scene_clip_ids) >= args.limit_clips:
                        continue
                    selected_scene_clip_ids.add(clip_id)
                    limited_clip_ids.append(clip_id)
                event_clip_ids = limited_clip_ids

            clip_tasks = build_clip_tasks_from_clip_ids(event_clip_ids)
            dir_prefix = "event_id"
            if any(record.clip_source == "direct_clip_ids_field" for record in event_records):
                dir_prefix = event_records[0].event_id_field_used or args.event_id_field or "record_id"
            event_dir_name = _sanitize_identifier_for_path(event_id, prefix=dir_prefix)
            event_export_root = scene_export_root / event_dir_name
            event_output_root = scene_output_root / event_dir_name
            event_export_dirs[event_id] = str(event_export_root)
            event_dirs[event_id] = str(event_output_root)

            def on_before_run(export_root: Path, inference_root: Path, tasks: list[Any], *, _event_id: str = event_id, _event_records: list[ResolvedEventRecord] = event_records, _event_clip_ids: list[str] = event_clip_ids) -> None:
                payload = _build_event_manifest_payload(
                    args=args,
                    scene=scene,
                    event_id=_event_id,
                    event_records=_event_records,
                    clip_ids=_event_clip_ids,
                    resolution_stats=resolution_stats,
                )
                manifest_path = inference_root / "_status" / "event_manifest.json"
                manifest_path.parent.mkdir(parents=True, exist_ok=True)
                with manifest_path.open("w", encoding="utf-8") as file:
                    json.dump(payload, file, indent=2, ensure_ascii=False)

            event_results[event_id] = run_clip_tasks_inference_exported(
                context=context,
                args=args,
                clip_tasks=clip_tasks,
                load_env=load_env,
                run_case_inference=run_case_inference,
                export_root=event_export_root,
                output_root=event_output_root,
                on_before_run=on_before_run,
            )

        summary_by_scene[scene] = {
            "scene_output_dir": str(scene_output_root),
            "scene_manifest_path": str(scene_manifest_path),
            "event_export_dirs": event_export_dirs,
            "event_dirs": event_dirs,
            "event_results": event_results,
        }

    result = {
        "event_json_file": args.event_json_file,
        "scene": args.scene or "",
        "event_id_field": args.event_id_field,
        "event_clip_ids_field": args.event_clip_ids_field,
        "manifest_path": str(manifest_path),
        "selection": selection_summary,
        "selected_record_count": len(selected_records),
        "resolution_stats": _build_resolution_stats_payload(resolution_stats),
        "scene_results": summary_by_scene,
    }
    _print_event_json_summary(
        args=args,
        manifest_path=manifest_path,
        resolved_records=resolved_records,
        selected_records=selected_records,
        selection_summary=selection_summary,
        scene_results=summary_by_scene,
    )
    return result
