"""Side-effect-free FFmpeg concat planning for local preview frames."""

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

from autoshorts.ideas.candidate import ValidationResult
from autoshorts.rendering.local_frame_writer import LocalFrameWriteResult
from autoshorts.rendering.local_preview import LocalPreviewPlan, validate_local_preview_plan


@dataclass(frozen=True)
class FFmpegConcatEntry:
    image_path: str
    duration_seconds: float


@dataclass(frozen=True)
class FFmpegConcatPlan:
    script_id: str
    output_path: str
    concat_file_path: str
    width: int
    height: int
    fps: int
    entries: tuple[FFmpegConcatEntry, ...]
    concat_file_content: str
    side_effects: tuple[str, ...] = ()
    external_calls: tuple[str, ...] = ()


def _is_blank(value: str) -> bool:
    return not (value or "").strip()


def _quote_concat_path(path: str) -> str:
    return path.replace("'", "'\\''")


def _concat_content(entries: tuple[FFmpegConcatEntry, ...]) -> str:
    lines: list[str] = []
    for entry in entries:
        lines.append(f"file '{_quote_concat_path(entry.image_path)}'")
        lines.append(f"duration {entry.duration_seconds:.3f}")
    if entries:
        lines.append(f"file '{_quote_concat_path(entries[-1].image_path)}'")
    return "\n".join(lines) + "\n"


def build_ffmpeg_concat_plan(
    preview_plan: LocalPreviewPlan,
    write_result: LocalFrameWriteResult,
    *,
    output_path: str | Path,
) -> FFmpegConcatPlan:
    """Build an FFmpeg concat plan without writing files or calling FFmpeg."""

    preview_validation = validate_local_preview_plan(preview_plan)
    if not preview_validation.is_valid:
        raise ValueError("; ".join(preview_validation.errors))
    if write_result.script_id != preview_plan.script_id:
        raise ValueError("write result script_id must match preview plan")
    if len(write_result.frame_paths) != len(preview_plan.frames):
        raise ValueError("frame count must match preview plan")
    if any(not path.endswith(".png") for path in write_result.frame_paths):
        raise ValueError("frame paths must be png files")
    if write_result.external_calls:
        raise ValueError("frame write result must not include external calls")

    entries = tuple(
        FFmpegConcatEntry(
            image_path=image_path,
            duration_seconds=frame.duration_ms / 1000,
        )
        for frame, image_path in zip(preview_plan.frames, write_result.frame_paths, strict=True)
    )
    out_path = Path(output_path)
    concat_path = out_path.with_suffix(".concat.txt")
    plan = FFmpegConcatPlan(
        script_id=preview_plan.script_id,
        output_path=str(out_path),
        concat_file_path=str(concat_path),
        width=preview_plan.width,
        height=preview_plan.height,
        fps=preview_plan.fps,
        entries=entries,
        concat_file_content=_concat_content(entries),
        side_effects=(),
        external_calls=(),
    )
    validation = validate_ffmpeg_concat_plan(plan)
    if not validation.is_valid:
        raise ValueError("; ".join(validation.errors))
    return plan


def validate_ffmpeg_concat_plan(plan: FFmpegConcatPlan) -> ValidationResult:
    errors: list[str] = []
    if _is_blank(plan.script_id):
        errors.append("script_id is required")
    if _is_blank(plan.output_path):
        errors.append("output_path is required")
    elif not plan.output_path.endswith(".mp4"):
        errors.append("output_path must end with .mp4")
    if _is_blank(plan.concat_file_path):
        errors.append("concat_file_path is required")
    elif not plan.concat_file_path.endswith(".concat.txt"):
        errors.append("concat_file_path must end with .concat.txt")
    if plan.width <= 0 or plan.height <= 0:
        errors.append("width and height must be positive")
    if plan.fps <= 0:
        errors.append("fps must be positive")
    if plan.side_effects:
        errors.append("ffmpeg concat plans must not have side effects")
    if plan.external_calls:
        errors.append("ffmpeg concat plans must not call external tools")
    if not plan.entries:
        errors.append("at least one concat entry is required")
    if _is_blank(plan.concat_file_content):
        errors.append("concat_file_content is required")

    for index, entry in enumerate(plan.entries, start=1):
        if _is_blank(entry.image_path):
            errors.append(f"entry {index} image_path is required")
        elif not entry.image_path.endswith(".png"):
            errors.append("frame paths must be png files")
        if entry.duration_seconds <= 0:
            errors.append(f"entry {index} duration_seconds must be positive")

    return ValidationResult(is_valid=not errors, errors=tuple(errors))
