from __future__ import annotations

from typing import TYPE_CHECKING, Optional, Union

from ._asset import AudioAsset, ImageAsset, TextAsset, VideoAsset
from ._transition import Transition
from ._types import CropGravity, Fit, Position

if TYPE_CHECKING:
    Asset = Union[VideoAsset, AudioAsset, ImageAsset, TextAsset]


class Clip:
    """Wraps an Asset and defines how it is presented on screen.

    This class mirrors VideoDB's ``Clip`` — an asset plus presentation
    parameters (duration, fit, position, scale, opacity, volume, etc.).

    Args:
        asset: The content to display.
        duration: Clip length in seconds. For ``VideoAsset`` this is optional
            (auto-detected via ffprobe); for ``ImageAsset`` / ``TextAsset`` it
            is required.
        fit: Scaling behaviour.  Default :attr:`Fit.COVER`.
        position: 9-zone anchor for overlay / text placement.
        scale: Size multiplier applied *after* fit.  ``0.3`` = 30 % of canvas.
        opacity: ``0.0`` (invisible) to ``1.0`` (opaque).
        volume: Audio volume multiplier for video/audio assets.
        crop_gravity: Which region to keep when ``fit=COVER`` crops.
        speed: Playback speed multiplier.  ``2.0`` = 2x fast.
        transition: Clip-level fade in/out (VideoDB style).
    """

    asset: Asset
    duration: float | None
    fit: Fit
    position: Position | None
    scale: float
    opacity: float
    volume: float
    crop_gravity: CropGravity
    speed: float
    transition: Transition | None
    _muted: bool

    def __init__(
        self,
        asset: Asset,
        *,
        duration: Optional[float] = None,
        fit: Fit = Fit.COVER,
        position: Optional[Position] = None,
        scale: float = 1.0,
        opacity: float = 1.0,
        volume: float = 1.0,
        crop_gravity: CropGravity = CropGravity.CENTER,
        speed: float = 1.0,
        transition: Optional[Transition] = None,
    ) -> None:
        if duration is not None and duration <= 0:
            raise ValueError(f"duration must be > 0, got {duration}")
        if not (0.0 <= opacity <= 1.0):
            raise ValueError(f"opacity must be 0.0–1.0, got {opacity}")
        if scale <= 0:
            raise ValueError(f"scale must be > 0, got {scale}")
        if volume < 0:
            raise ValueError(f"volume must be >= 0, got {volume}")
        if speed <= 0:
            raise ValueError(f"speed must be > 0, got {speed}")

        if isinstance(asset, (ImageAsset, TextAsset)) and duration is None:
            raise ValueError(
                f"{type(asset).__name__} requires an explicit duration in Clip()"
            )

        self.asset = asset
        self.duration = duration
        self.fit = fit
        self.position = position
        self.scale = scale
        self.opacity = opacity
        self.volume = volume
        self.crop_gravity = crop_gravity
        self.speed = speed
        self.transition = transition
        self._muted = False

    def mute(self) -> Clip:
        """Mute the clip's audio track.  Returns *self* for chaining."""
        self._muted = True
        return self

    @property
    def is_muted(self) -> bool:
        return self._muted

    def __repr__(self) -> str:
        parts = [repr(self.asset)]
        if self.duration is not None:
            parts.append(f"duration={self.duration}")
        parts.append(f"fit={self.fit.value}")
        return f"Clip({', '.join(parts)})"
