from __future__ import annotations

import os
import sys
from enum import Enum, auto
from pathlib import Path
from typing import Optional, overload

from gi.repository import Gio, GObject

from .thumbnailer import Thumbnailer


class FileType(Enum):
    DIRECTORY = auto()
    VIDEO = auto()


class FileItem(GObject.Object):
    file_type: FileType
    full_path: Path
    thumbnail: bytes
    attempted_thumbnail: bool

    _has_thumbnail: bool

    __gtype_name__ = "FileItem"

    def __init__(self, name: str, file_type: FileType, full_path: Path):
        super().__init__()
        self.name = name
        self.file_type = file_type
        self.full_path = full_path
        self.thumbnail = b""
        self.attempted_thumbnail = False
        self._has_thumbnail = False

    @GObject.Property(type=GObject.TYPE_UINT64)
    def saved_position(self) -> int:
        return self._load_attribute("position", 0)

    @saved_position.setter
    def set_saved_position(self, value: int):
        self._save_attribute("position", value if value else None)
        self.notify("saved-position")

    @GObject.Property(type=GObject.TYPE_UINT64)
    def saved_duration(self):
        return self._load_attribute("duration", 1) or 1

    @saved_duration.setter
    def set_saved_duration(self, value: int):
        self._save_attribute("duration", value if value > 0 else None)
        self.notify("saved-duration")

    @GObject.Property(type=int)
    def saved_subtitle_track(self):
        return self._load_attribute("subtitle_track", -2)

    @saved_subtitle_track.setter
    def set_saved_subtitle_track(self, value: int):
        self._save_attribute("subtitle_track", value if value >= -1 else None)
        self.notify("saved-subtitle-track")

    @GObject.Property(type=int)
    def saved_audio_track(self):
        return self._load_attribute("audio_track", 0)

    @saved_audio_track.setter
    def set_saved_audio_track(self, value: int):
        self._save_attribute("audio_track", value if value > 0 else None)
        self.notify("saved-audio-track")

    @GObject.Property(type=bool, default=False)
    def has_thumbnail(self):
        return self._has_thumbnail

    @has_thumbnail.setter
    def set_has_thumbnail(self, value: bool):
        self._has_thumbnail = value
        self.notify("has-thumbnail")

    def ensure_thumbnail(self, thumbnailer: Thumbnailer):
        if self.thumbnail or self.attempted_thumbnail:
            return

        if not self.attempted_thumbnail:
            thumbnailer.generate_thumbnail(self)

    @overload
    def _load_attribute(self, name: str, dfl: str) -> str: ...

    @overload
    def _load_attribute(self, name: str, dfl: bytes) -> bytes: ...

    @overload
    def _load_attribute(self, name: str, dfl: int) -> int: ...

    def _load_attribute(self, name: str, dfl: str | bytes | int) -> str | bytes | int:
        try:
            strval = os.getxattr(self.full_path, f"user.lazy_player.{name}")
            return type(dfl)(strval)
        except OSError:
            return dfl

    def _save_attribute(self, name: str, value: str | bytes | float | int | None) -> None:
        try:
            if value is None:
                os.removexattr(self.full_path, f"user.lazy_player.{name}")
            else:
                if isinstance(value, bytes):
                    os.setxattr(self.full_path, f"user.lazy_player.{name}", value)
                else:
                    os.setxattr(
                        self.full_path, f"user.lazy_player.{name}", str(value).encode("utf8")
                    )
        except OSError as err:
            print(err, file=sys.stderr)


class FileListModel(GObject.Object, Gio.ListModel):
    """A ListModel implementation for FileItems"""

    __gtype_name__ = "FileListModel"

    items: list[FileItem]

    def __init__(self):
        super().__init__()
        self.items = []

    def do_get_item_type(self) -> GObject.GType:
        return GObject.type_from_name("FileItem")

    def do_get_n_items(self) -> int:
        return len(self.items)

    def do_get_item(self, position: int) -> Optional[FileItem]:
        if 0 <= position < len(self.items):
            return self.items[position]
        return None

    def remove_all(self) -> None:
        removed = len(self.items)
        self.items = []
        self.items_changed(0, removed, 0)

    def set_items(self, items: list[FileItem]):
        removed = len(self.items)
        self.items = list(items)
        self.items_changed(0, removed, len(self.items))

    def append(self, item: FileItem) -> None:
        pos = len(self.items)
        self.items.append(item)
        self.items_changed(pos, 0, 1)

    def remove(self, position: int) -> None:
        if 0 <= position < len(self.items):
            self.items.pop(position)
            self.items_changed(position, 1, 0)