import re
from typing import cast

from PySide6 import QtCore, QtGui, QtWidgets
from tidalapi import Album, Mix, Playlist, Track, UserPlaylist, Video
from tidalapi.artist import Artist
from tidalapi.media import Quality
from tidalapi.playlist import Folder

from tidal_dl_ng.constants import QualityVideo


def get_table_data(
    item: QtWidgets.QTreeWidgetItem, column: int
) -> Track | Video | Album | Artist | Mix | Playlist | UserPlaylist:
    result: Track | Video | Album | Artist = item.data(column, QtCore.Qt.ItemDataRole.UserRole)

    return result


def get_table_text(item: QtWidgets.QTreeWidgetItem, column: int) -> str:
    result: str = item.text(column)

    return result


def get_results_media_item(
    index: QtCore.QModelIndex, proxy: QtCore.QSortFilterProxyModel, model: QtGui.QStandardItemModel
) -> Track | Video | Album | Artist | Playlist | Mix:
    # Switch column to "obj" column and map proxy data to our model.
    item: QtGui.QStandardItem = model.itemFromIndex(proxy.mapToSource(index.siblingAtColumn(1)))
    result: Track | Video | Album | Artist = item.data(QtCore.Qt.ItemDataRole.UserRole)

    return result


def get_user_list_media_item(item: QtWidgets.QTreeWidgetItem) -> Mix | Playlist | UserPlaylist | Folder | str:
    result: Mix | Playlist | UserPlaylist | Folder | str = get_table_data(item, 1)

    return result


def get_queue_download_media(
    item: QtWidgets.QTreeWidgetItem,
) -> Mix | Playlist | UserPlaylist | Track | Video | Album | Artist:
    result: Mix | Playlist | UserPlaylist | Track | Video | Album | Artist = get_table_data(item, 1)

    return result


def get_queue_download_quality(
    item: QtWidgets.QTreeWidgetItem,
    column: int,
) -> str:
    result: str = get_table_text(item, column)

    return result


def get_queue_download_quality_audio(
    item: QtWidgets.QTreeWidgetItem,
) -> Quality:
    result: Quality = cast(Quality, get_queue_download_quality(item, 4))

    return result


def get_queue_download_quality_video(
    item: QtWidgets.QTreeWidgetItem,
) -> QualityVideo:
    result: QualityVideo = cast(QualityVideo, get_queue_download_quality(item, 5))

    return result


def set_table_data(
    item: QtWidgets.QTreeWidgetItem,
    data: Track | Video | Album | Artist | Mix | Playlist | UserPlaylist | Folder | str,
    column: int,
):
    item.setData(column, QtCore.Qt.ItemDataRole.UserRole, data)


def set_results_media(item: QtWidgets.QTreeWidgetItem, media: Track | Video | Album | Artist):
    set_table_data(item, media, 1)


def set_user_list_media(
    item: QtWidgets.QTreeWidgetItem,
    media: Track | Video | Album | Artist | Mix | Playlist | UserPlaylist | Folder | str,
):
    set_table_data(item, media, 1)


def set_queue_download_media(
    item: QtWidgets.QTreeWidgetItem, media: Mix | Playlist | UserPlaylist | Track | Video | Album | Artist
):
    set_table_data(item, media, 1)


class FilterHeader(QtWidgets.QHeaderView):
    filter_activated = QtCore.Signal()

    def __init__(self, parent):
        super().__init__(QtCore.Qt.Horizontal, parent)
        self._editors = []
        self._padding = 4
        self.setCascadingSectionResizes(True)
        self.setSectionResizeMode(QtWidgets.QHeaderView.Interactive)
        self.setStretchLastSection(True)
        self.setDefaultAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
        self.setSortIndicatorShown(False)
        self.setSectionsMovable(True)
        self.sectionResized.connect(self.adjust_positions)
        parent.horizontalScrollBar().valueChanged.connect(self.adjust_positions)

    def set_filter_boxes(self, count):
        while self._editors:
            editor = self._editors.pop()
            editor.deleteLater()

        for _ in range(count):
            editor = QtWidgets.QLineEdit(self.parent())
            editor.setPlaceholderText("Filter")
            editor.setClearButtonEnabled(True)
            editor.returnPressed.connect(self.filter_activated.emit)
            self._editors.append(editor)

        self.adjust_positions()

    def sizeHint(self):
        size = super().sizeHint()

        if self._editors:
            height = self._editors[0].sizeHint().height()

            size.setHeight(size.height() + height + self._padding)

        return size

    def updateGeometries(self):
        if self._editors:
            height = self._editors[0].sizeHint().height()

            self.setViewportMargins(0, 0, 0, height + self._padding)
        else:
            self.setViewportMargins(0, 0, 0, 0)

        super().updateGeometries()
        self.adjust_positions()

    def adjust_positions(self):
        for index, editor in enumerate(self._editors):
            height = editor.sizeHint().height()

            editor.move(self.sectionPosition(index) - self.offset() + 2, height + (self._padding // 2))
            editor.resize(self.sectionSize(index), height)

    def filter_text(self, index) -> str:
        if 0 <= index < len(self._editors):
            return self._editors[index].text()

        return ""

    def set_filter_text(self, index, text):
        if 0 <= index < len(self._editors):
            self._editors[index].setText(text)

    def clear_filters(self):
        for editor in self._editors:
            editor.clear()


class HumanProxyModel(QtCore.QSortFilterProxyModel):
    def _human_key(self, key):
        parts = re.split(r"(\d*\.\d+|\d+)", key)

        return tuple((e.swapcase() if i % 2 == 0 else float(e)) for i, e in enumerate(parts))

    def lessThan(self, source_left, source_right):
        data_left = source_left.data()
        data_right = source_right.data()

        if isinstance(data_left, str) and isinstance(data_right, str):
            return self._human_key(data_left) < self._human_key(data_right)

        return super().lessThan(source_left, source_right)

    @property
    def filters(self):
        if not hasattr(self, "_filters"):
            self._filters = []

        return self._filters

    @filters.setter
    def filters(self, filters):
        self._filters = filters

        self.invalidateFilter()

    def filterAcceptsRow(self, source_row: int, source_parent: QtCore.QModelIndex) -> bool:
        model = self.sourceModel()
        source_index = model.index(source_row, 0, source_parent)
        result: [bool] = []

        # Show top level children
        for child_row in range(model.rowCount(source_index)):
            if self.filterAcceptsRow(child_row, source_index):
                return True

        # Filter for actual needle
        for i, text in self.filters:
            if 0 <= i < self.columnCount():
                ix = self.sourceModel().index(source_row, i, source_parent)
                data = ix.data()

                # Append results to list to enable an AND operator for filtering.
                result.append(bool(re.search(rf"{text}", data, re.MULTILINE | re.IGNORECASE)) if data else False)

        # If no filter set, just set the result to True.
        if not result:
            result.append(True)

        return all(result)
