from __future__ import annotations

from pathlib import Path

from gi.repository import GObject, Gst, Gtk


class VideoPlayer(GObject.Object):
    pipeline: Gst.Pipeline
    playbin: Gst.Element

    __gtype_name__ = "VideoPlayer"

    is_playing = GObject.Property(type=bool, default=False)
    is_paused = GObject.Property(type=bool, default=True)

    def __init__(self, picture: Gtk.Picture):
        super().__init__()

        self.pipeline = Gst.Pipeline.new("video-player")

        playbin = Gst.ElementFactory.make("playbin", "playbin")
        if not playbin:
            raise RuntimeError("Failed to create playbin element")

        self.playbin = playbin

        video_sink = Gst.ElementFactory.make("gtk4paintablesink", "gtk4paintablesink")
        if not video_sink:
            raise RuntimeError("Failed to create gtk4paintablesink element")

        self.playbin.set_property("video-sink", video_sink)
        self.pipeline.add(self.playbin)

        # Link picture to sink
        paintable = video_sink.get_property("paintable")
        picture.set_paintable(paintable)

    def play(
        self,
        file_path: Path | str,
        position: int = 0,
        subtitle_track: int = -2,
        audio_track: int = 0,
    ) -> None:
        """Start playing a video file"""

        self.playbin.set_property("uri", Gst.filename_to_uri(str(file_path)))

        if subtitle_track >= 0:
            flags = self.playbin.get_property("flags")
            flags |= 0x00000004  # TEXT flag
            self.playbin.set_property("flags", flags)
            self.playbin.set_property("current-text", subtitle_track)
        elif subtitle_track == -1:
            flags = self.playbin.get_property("flags")
            flags &= ~0x00000004  # TEXT flag
            self.playbin.set_property("flags", flags)

        self.playbin.set_property("current-audio", audio_track)

        # Pause and wait for it to complete
        self.pipeline.set_state(Gst.State.PAUSED)
        self.pipeline.get_state(Gst.CLOCK_TIME_NONE)

        if position:
            # Seek to saved position
            self.pipeline.seek_simple(
                Gst.Format.TIME,
                Gst.SeekFlags.FLUSH | Gst.SeekFlags.ACCURATE,
                position,
            )

        # Start playing
        self.pipeline.set_state(Gst.State.PLAYING)
        self.set_property("is-playing", True)
        self.set_property("is-paused", False)

    def stop(self) -> None:
        """Stop playback and release resources"""
        self.pipeline.set_state(Gst.State.NULL)
        self.set_property("is-paused", True)
        self.set_property("is-playing", False)

    def toggle_play_pause(self) -> None:
        """Toggle between play and pause states"""
        _, state, _ = self.pipeline.get_state(0)
        if state == Gst.State.PLAYING:
            self.pipeline.set_state(Gst.State.PAUSED)
            self.set_property("is-paused", True)
        else:
            self.pipeline.set_state(Gst.State.PLAYING)
            self.set_property("is-paused", False)

    def seek_relative(self, offset: float) -> None:
        """Seek relative to current position by offset seconds"""
        success, current = self.pipeline.query_position(Gst.Format.TIME)
        if not success:
            return

        new_pos = current + int(offset * Gst.SECOND)
        if new_pos < 0:
            new_pos = 0

        self.pipeline.seek_simple(
            Gst.Format.TIME,
            Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT,
            new_pos,
        )

    def seek_start(self):
        """Seek to the start of the video."""
        self.pipeline.seek_simple(
            Gst.Format.TIME,
            Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT,
            0,
        )

    def seek_end(self):
        """Seek to the end of the video."""
        duration = self.get_duration()
        if duration:
            self.pipeline.seek_simple(
                Gst.Format.TIME,
                Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT,
                duration,
            )

    def get_position(self) -> int | None:
        """Get current playback position in nanoseconds"""
        success, position = self.pipeline.query_position(Gst.Format.TIME)
        return position if success else None

    def get_duration(self) -> int | None:
        """Get total duration in nanoseconds"""
        success, duration = self.pipeline.query_duration(Gst.Format.TIME)
        return duration if success else None

    def cycle_subtitles(self) -> tuple[bool, int, str]:
        """Cycle through available subtitle tracks, including off state"""

        flags = self.playbin.get_property("flags")
        current = self.playbin.get_property("current-text")
        n_text = self.playbin.get_property("n-text")

        if n_text == 0:
            return False, 0, ""

        if not (flags & 0x00000004):  # TEXT flag
            flags |= 0x00000004
            self.playbin.set_property("flags", flags)
            self.playbin.set_property("current-text", 0)
            return True, 1, self._get_track_lang("text", 0)

        if current >= n_text - 1:
            flags &= ~0x00000004  # TEXT flag
            self.playbin.set_property("flags", flags)
            return True, 0, ""

        next_track = current + 1
        self.playbin.set_property("current-text", next_track)
        return True, next_track + 1, self._get_track_lang("text", next_track)

    def cycle_audio(self) -> tuple[bool, int, str]:
        """Cycle through available audio tracks"""

        current = self.playbin.get_property("current-audio")
        n_audio = self.playbin.get_property("n-audio")

        if n_audio == 0:
            return False, 0, ""

        next_track = (current + 1) % n_audio
        self.playbin.set_property("current-audio", next_track)
        return True, next_track + 1, self._get_track_lang("audio", next_track)

    def _get_track_lang(self, track_type: str, track_index: int) -> str:
        caps: Gst.TagList | None = self.playbin.emit(f"get-{track_type}-tags", track_index)
        if not caps:
            return str(track_index)

        found, lang = caps.get_string("language-code")
        return lang if found else "???"