"""Codec / audio / container profile tables and helpers.

Data-driven configuration that replaces the v1 if/else codec chains.
Pure data + pure functions — no PySide6, no I/O, no internal package
dependencies. Safe to import from any context (incl. unit tests and
the CLI --version path).

Extracted from ``open-transcode.v3.py`` (QA item v4-03 — package split).
This is a pure code-organization refactor; behavior is identical to v3.
"""

from collections.abc import Callable
from dataclasses import dataclass

# ──────────────────────────────────────────────
#  CONFIG-DRIVEN PROFILES (replaces all if/else chains)
# ──────────────────────────────────────────────

@dataclass
class VideoCodecProfile:
    label: str                   # Display name in combo box
    av1an_encoder: str           # Encoder name passed to --encoder
    ffmpeg_encoder: str          # Encoder name for pure-ffmpeg fallback (e.g. "libsvtav1")
    container: str               # Default container extension (mkv or webm)
    crf_range: tuple[int, int]   # (min, max) valid CRF values
    default_crf: int
    params_fn: Callable[[int, int], str]          # (crf, preset) -> av1an video-params string
    ffmpeg_vargs_fn: Callable[[int, int], list[str]]  # (crf, preset) -> ffmpeg -c:v args
    presets: list[str]           # Human-readable preset labels
    preset_map: dict[str, int]   # label -> internal preset value

@dataclass
class AudioProfile:
    label: str
    params: list[str]            # Tokens passed to --audio-params (joined with space)
    # v3 (OTC-012, SEI CERT STR09-C): the ffmpeg audio encoder name this
    # profile depends on, e.g. "libopus", "libvorbis", "flac", "libiamf".
    # Used by _check_combo_compatibility and _disable_unavailable_codecs
    # to look up the encoder directly in EnvProbe.ffmpeg_libs — replacing
    # the v2 substring match (`"libiamf" in ap.params`) which would
    # falsely match a hypothetical `-libiamf-mode` argument.
    # Empty string means "no ffmpeg encoder dependency" (rare; only used
    # by passthrough profiles that don't transcode audio).
    ffmpeg_encoder_name: str = ""

@dataclass
class ContainerProfile:
    label: str
    ext: str                     # e.g. "mkv", "webm"


def _av1_params(crf: int, preset: int) -> str:
    """SVT-AV1 encoder params for av1an's --video-params.

    av1an splits the --video-params value by whitespace (``split_whitespace()``)
    and passes each resulting token as a separate argument to SvtAv1EncApp.
    Therefore the string must contain space-separated ``--flag value`` pairs
    that SvtAv1EncApp can parse natively.

    Colon-separated ``key=value:key=value`` does NOT work because there are
    no whitespace boundaries for av1an to split on — the entire string reaches
    SvtAv1EncApp as one opaque argument, producing:
    ``Maybe missing spacing between tokens``.
    """
    return f"--preset {preset} --crf {crf} --keyint 240"


def _vp9_params(crf: int, preset: int) -> str:
    """VP9 encoder params for av1an's --video-params.

    av1an splits by whitespace, so we use space-separated --flag=value tokens
    that vpxenc parses natively.
    """
    cpu_used = max(0, 8 - preset)
    return f"--end-usage=q --cq-level={crf} --cpu-used={cpu_used}"


def _x265_params(crf: int, preset: int) -> str:
    """x265 encoder params for av1an's --video-params.

    av1an splits by whitespace, so we use space-separated --flag value tokens
    that x265 parses natively.
    """
    return f"--crf {crf} --preset {preset}"

def _svtav1_ffmpeg_args(crf: int, preset: int) -> list[str]:
    """FFmpeg args for SVT-AV1 (maps av1an preset=0..8 → svtav1 -preset 0..13)."""
    # av1an preset range 0-8 maps to SVT-AV1 preset range 0-13
    # Scale roughly: 8→0, 6→4, 4→7, 2→10
    svt_preset = max(0, min(13, round((8 - preset) * 13 / 8)))
    return ["-c:v", "libsvtav1", "-preset", str(svt_preset), "-crf", str(crf),
            "-pix_fmt", "yuv420p10le", "-g", "240"]

def _vp9_ffmpeg_args(crf: int, preset: int) -> list[str]:
    """FFmpeg args for VP9 (maps av1an cpu-used 0..8 → -cpu-used 0..8)."""
    cpu_used = max(0, min(8, preset))
    return ["-c:v", "libvpx-vp9", "-crf", str(crf), "-b:v", "0",
            "-cpu-used", str(cpu_used), "-pix_fmt", "yuv420p", "-g", "240",
            "-row-mt", "1", "-tiles", "2x2"]

def _x265_ffmpeg_args(crf: int, preset: int) -> list[str]:
    """FFmpeg args for x265 (maps av1an preset 5..10 → x265 -preset)."""
    # av1an x265 preset range 5-10 maps to x265 preset names
    preset_names = {5: "slow", 7: "medium", 9: "fast", 10: "faster"}
    p = preset_names.get(preset, "medium")
    return ["-c:v", "libx265", "-preset", p, "-crf", str(crf),
            "-pix_fmt", "yuv420p10le", "-g", "240"]


VIDEO_CODECS: list[VideoCodecProfile] = [
    VideoCodecProfile(
        label="AV1 (SVT-AV1)",
        av1an_encoder="svt_av1",
        ffmpeg_encoder="libsvtav1",
        container="mkv",
        crf_range=(18, 52),
        default_crf=32,
        params_fn=_av1_params,
        ffmpeg_vargs_fn=_svtav1_ffmpeg_args,
        presets=["Slow (8)", "Medium (6)", "Fast (4)", "Faster (2)"],
        preset_map={"Slow (8)": 8, "Medium (6)": 6, "Fast (4)": 4, "Faster (2)": 2},
    ),
    VideoCodecProfile(
        label="VP9",
        av1an_encoder="vpx",
        ffmpeg_encoder="libvpx-vp9",
        container="webm",
        crf_range=(18, 52),
        default_crf=32,
        params_fn=_vp9_params,
        ffmpeg_vargs_fn=_vp9_ffmpeg_args,
        presets=["Slow (0)", "Medium (2)", "Fast (4)", "Faster (6)"],
        preset_map={"Slow (0)": 0, "Medium (2)": 2, "Fast (4)": 4, "Faster (6)": 6},
    ),
    VideoCodecProfile(
        label="x265 (HEVC)",
        av1an_encoder="x265",
        ffmpeg_encoder="libx265",
        container="mkv",
        crf_range=(18, 40),
        default_crf=28,
        params_fn=_x265_params,
        ffmpeg_vargs_fn=_x265_ffmpeg_args,
        presets=["Slow (5)", "Medium (7)", "Fast (9)", "Faster (10)"],
        preset_map={"Slow (5)": 5, "Medium (7)": 7, "Fast (9)": 9, "Faster (10)": 10},
    ),
]

AUDIO_PROFILES: list[AudioProfile] = [
    AudioProfile(label="Opus (96k)",  params=["-c:a", "libopus", "-b:a", "96k"],
                 ffmpeg_encoder_name="libopus"),
    AudioProfile(label="Opus (128k)", params=["-c:a", "libopus", "-b:a", "128k"],
                 ffmpeg_encoder_name="libopus"),
    AudioProfile(label="Opus (64k)",  params=["-c:a", "libopus", "-b:a", "64k"],
                 ffmpeg_encoder_name="libopus"),
    AudioProfile(label="Vorbis (128k)", params=["-c:a", "libvorbis", "-b:a", "128k"],
                 ffmpeg_encoder_name="libvorbis"),
    AudioProfile(label="Vorbis (192k)", params=["-c:a", "libvorbis", "-b:a", "192k"],
                 ffmpeg_encoder_name="libvorbis"),
    AudioProfile(label="FLAC (lossless)", params=["-c:a", "flac"],
                 ffmpeg_encoder_name="flac"),
    # IAMF — AOMedia Immersive Audio Model and Formats (RFC 9454 family).
    # Built on Opus internally; requires ffmpeg compiled with --enable-libiamf.
    # CANNOT be muxed into MKV/WebM — must use the MP4 container (see below).
    # The -strict experimental flag is harmless on ffmpeg builds where libiamf
    # is already stable, and required on builds where it's still flagged
    # experimental, so we always pass it for forward compatibility.
    AudioProfile(
        label="IAMF (128k)",
        params=["-c:a", "libiamf", "-b:a", "128k", "-strict", "experimental"],
        ffmpeg_encoder_name="libiamf",
    ),
]

CONTAINER_PROFILES: list[ContainerProfile] = [
    ContainerProfile(label="MKV (Matroska)", ext="mkv"),
    ContainerProfile(label="WebM",           ext="webm"),
    # MP4 is required for IAMF audio (MKV/WebM cannot mux the IAMF codec).
    # Also useful as a more universally compatible output container.
    ContainerProfile(label="MP4",            ext="mp4"),
]


# ──────────────────────────────────────────────────────────────────────────────
# FFMPEG_LIB_KEY_MAP — single source of truth (OTC-007, SEI CERT MSC04-C).
#
# Maps the `ffmpeg_encoder` field of a VideoCodecProfile (e.g. "libsvtav1",
# "libvpx-vp9") to the corresponding key in EnvProbe.ffmpeg_libs (which is
# populated by _probe_ffmpeg_libs()).
#
# v2 had this map duplicated in three call sites:
#   - _ffmpeg_fallback_encode (around line 2075)
#   - _probe_and_init status bar (around line 4309)
#   - _handle_vs_incompat fallback check (around line 4532)
# Adding a new codec required updating all three in sync — a classic
# MSC04-C violation. v3 hoists it to one module-level constant.
# ──────────────────────────────────────────────────────────────────────────────
FFMPEG_LIB_KEY_MAP: dict[str, str] = {
    "libsvtav1":  "libsvtav1",
    "libaom-av1": "libaom",
    "libvpx-vp9": "libvpx",
    "libx265":    "libx265",
}


def ffmpeg_lib_key_for(ffmpeg_encoder: str) -> str:
    """Look up the ffmpeg_libs key for a given ffmpeg encoder name.

    Returns the encoder name itself if no mapping is known — this preserves
    forward compatibility with encoders added after this map was last
    updated (the caller's .get() will then return False, which is the
    safe default for an unknown encoder).
    """
    return FFMPEG_LIB_KEY_MAP.get(ffmpeg_encoder, ffmpeg_encoder)


# ── Resolution presets ──
# Aspect ratios:
#   Standard 16:9  -> w/h = 1.778
#   Wide 21:9      -> w/h = 2.333
#   Ultrawide 32:9 -> w/h = 3.556

@dataclass
class ResolutionProfile:
    label: str              # Display label in dropdown, e.g. "1080p Wide (2560x1080)"
    category: str           # Grouping key: "standard", "wide", "ultrawide", "original"
    width: int | None    # None for "original" (no scaling)
    height: int | None   # None for "original"
    aspect_label: str       # "16:9", "21:9", "32:9", "Source"


RESOLUTION_PRESETS: list[ResolutionProfile] = [
    # ── Original (no scaling) ──
    ResolutionProfile("Original (No Scaling)", "original", None, None, "Source"),

    # ── Standard 16:9 ──
    ResolutionProfile("480p  ( 854x 480)",      "standard",   854,  480, "16:9"),
    ResolutionProfile("720p  (1280x 720)",      "standard",  1280,  720, "16:9"),
    ResolutionProfile("1080p (1920x1080)",      "standard",  1920, 1080, "16:9"),
    ResolutionProfile("2K    (2560x1440)",      "standard",  2560, 1440, "16:9"),
    ResolutionProfile("4K    (3840x2160)",      "standard",  3840, 2160, "16:9"),

    # ── Wide 21:9 ──
    ResolutionProfile("480p  Wide  ( 854x 366)",  "wide",   854,  366, "21:9"),
    ResolutionProfile("720p  Wide  (1280x 549)",  "wide",  1280,  549, "21:9"),
    ResolutionProfile("1080p Wide  (2560x1080)", "wide",  2560, 1080, "21:9"),
    ResolutionProfile("2K    Wide  (3440x1440)", "wide",  3440, 1440, "21:9"),
    ResolutionProfile("4K    Wide  (5120x2160)", "wide",  5120, 2160, "21:9"),

    # ── Ultrawide 32:9 ──
    ResolutionProfile("480p  UW    (1706x 480)",  "ultrawide", 1706,  480, "32:9"),
    ResolutionProfile("1080p UW    (3840x1080)", "ultrawide", 3840, 1080, "32:9"),
    ResolutionProfile("2K    UW    (5120x1440)", "ultrawide", 5120, 1440, "32:9"),
    ResolutionProfile("4K    UW    (7680x2160)", "ultrawide", 7680, 2160, "32:9"),
]


SUBTITLE_OPTIONS = [
    ("None",    None),
    ("English", "eng"),
]


DEFAULT_INPUT_EXTENSIONS = {".mp4", ".mkv", ".avi", ".mov", ".ts", ".m4v", ".flv", ".wmv", ".webm", ".mpg", ".mpeg"}
