#!/usr/bin/env bash
export LC_NUMERIC=C
exec python3 - << 'PYEOF'
import sys
import os
import json
import subprocess
import hashlib
import locale

try:
    locale.setlocale(locale.LC_NUMERIC, 'C')
except Exception:
    pass

from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
    QListWidget, QListWidgetItem, QPushButton, QSlider, QLabel,
    QFileDialog, QInputDialog, QLineEdit, QMessageBox, QStackedWidget,
    QMenu, QSizePolicy, QFrame
)
from PyQt5.QtCore import Qt, QSize, QTime, QThread, pyqtSignal, QTimer, QEvent
from PyQt5.QtGui import QIcon, QPixmap, QColor, QPainter

import mpv

LOCK_FILE = os.path.expanduser("~/.config/mint_vidplayer_locks.json")
CACHE_DIR = os.path.expanduser("~/.cache/mint_vidplayer_thumbs")
os.makedirs(CACHE_DIR, exist_ok=True)

DARK_STYLESHEET = """
QMainWindow, QWidget {
    background-color: #0d0d12;
    color: #e1e1e6;
    font-family: 'Segoe UI', Ubuntu, sans-serif;
}

QListWidget {
    background-color: #0d0d12;
    border: none;
    outline: none;
    padding: 10px;
}

QListWidget::item {
    background-color: #161621;
    border: 1px solid #232333;
    border-radius: 8px;
    padding: 8px;
    margin: 6px;
    color: #ffffff;
}

QListWidget::item:hover {
    background-color: #212130;
    border: 1px solid #e2e83b;
}

QListWidget::item:selected {
    background-color: #28283d;
    border: 1px solid #e2e83b;
}

QPushButton {
    background-color: #1a1a26;
    color: #ffffff;
    border: 1px solid #2d2d42;
    border-radius: 6px;
    padding: 6px 14px;
    font-weight: bold;
    min-height: 24px;
}

QPushButton:hover {
    background-color: #2a2a3d;
    border-color: #e2e83b;
}

QPushButton#accentButton {
    background-color: #e2e83b;
    color: #0d0d12;
    border: none;
}

QPushButton#accentButton:hover {
    background-color: #f2f84b;
}

QLabel {
    color: #a0a0b0;
}

QSlider::groove:horizontal {
    height: 6px;
    background: #232333;
    border-radius: 3px;
}

QSlider::sub-page:horizontal {
    background: #e2e83b;
    border-radius: 3px;
}

QSlider::handle:horizontal {
    background: #ffffff;
    width: 14px;
    margin-top: -4px;
    margin-bottom: -4px;
    border-radius: 7px;
}

QFrame#controlBar {
    background-color: rgba(18, 18, 26, 0.85);
    border: 1px solid #232333;
    border-radius: 8px;
    padding: 4px;
}
"""

class MediaItem(QListWidgetItem):
    def __init__(self, icon, text, item_path, item_type):
        super().__init__(icon, text)
        self.item_path = str(item_path)
        self.item_type = str(item_type)

def create_placeholder_pixmap(width=180, height=110, is_folder=False):
    pix = QPixmap(width, height)
    pix.fill(QColor("#1f1f2e"))
    painter = QPainter(pix)
    painter.setPen(QColor("#4e4e68"))
    text = "📁 Folder" if is_folder else "🎬 Video"
    painter.drawText(pix.rect(), Qt.AlignCenter, text)
    painter.end()
    return pix

def get_video_in_folder(folder_path):
    try:
        for root, _, files in os.walk(folder_path):
            for f in files:
                if f.lower().endswith(('.mp4', '.mkv', '.avi', '.mov', '.webm', '.flv')):
                    return os.path.normpath(os.path.join(root, f))
    except Exception:
        pass
    return None

def count_videos_in_folder(folder_path):
    count = 0
    try:
        for f in os.listdir(folder_path):
            if f.lower().endswith(('.mp4', '.mkv', '.avi', '.mov', '.webm', '.flv')):
                count += 1
    except Exception:
        pass
    return count

class SequentialThumbnailWorker(QThread):
    thumbnail_ready = pyqtSignal(str, str)

    def __init__(self):
        super().__init__()
        self.queue = []
        self._is_running = True

    def add_job(self, target_path, is_folder):
        self.queue.append((os.path.normpath(target_path), is_folder))

    def stop(self):
        self._is_running = False
        self.queue.clear()

    def run(self):
        has_ffmpegthumbnailer = subprocess.call(
            ['which', 'ffmpegthumbnailer'],
            stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
        ) == 0

        while self._is_running and self.queue:
            target_path, is_folder = self.queue.pop(0)

            video_source = target_path
            if is_folder:
                video_source = get_video_in_folder(target_path)

            if not video_source or not os.path.exists(video_source):
                continue

            path_hash = hashlib.md5(video_source.encode('utf-8', errors='ignore')).hexdigest()
            thumb_path = os.path.join(CACHE_DIR, f"{path_hash}.jpg")

            if os.path.exists(thumb_path) and os.path.getsize(thumb_path) > 0:
                self.thumbnail_ready.emit(target_path, thumb_path)
                continue

            if has_ffmpegthumbnailer:
                cmd = ['nice', '-n', '19', 'ffmpegthumbnailer', '-i', video_source, '-o', thumb_path, '-s', '160', '-q', '5']
            else:
                cmd = [
                    'nice', '-n', '19', 'ffmpeg', '-ss', '00:00:02', '-noaccurate_seek',
                    '-i', video_source, '-vframes', '1', '-vf', 'scale=160:-1',
                    '-q:v', '5', thumb_path, '-y'
                ]

            try:
                subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=3)
                if os.path.exists(thumb_path) and os.path.getsize(thumb_path) > 0:
                    self.thumbnail_ready.emit(target_path, thumb_path)
            except Exception:
                pass

class PlayerContainer(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.video_frame = QWidget(self)
        self.video_frame.setStyleSheet("background-color: #000000; border-radius: 6px;")
        
        self.control_frame = QFrame(self)
        self.control_frame.setObjectName("controlBar")

    def resizeEvent(self, event):
        super().resizeEvent(event)
        self.video_frame.setGeometry(0, 0, self.width(), self.height())
        ctrl_h = self.control_frame.sizeHint().height()
        if ctrl_h < 40:
            ctrl_h = 45
        margin = 12
        self.control_frame.setGeometry(
            margin,
            self.height() - ctrl_h - margin,
            self.width() - (margin * 2),
            ctrl_h
        )
        self.control_frame.raise_()

class VideoPlayerApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("ZipyVid Browser & Player")
        self.setMinimumSize(400, 300)
        self.resize(1000, 680)
        self.setStyleSheet(DARK_STYLESHEET)

        self.default_dir = os.path.abspath(os.path.expanduser("~/Videos"))
        if not os.path.exists(self.default_dir):
            os.makedirs(self.default_dir)
        self.current_dir = self.default_dir

        self.locked_folders = self.load_locks()
        self.worker = None

        self.folder_placeholder = QIcon(create_placeholder_pixmap(is_folder=True))
        self.video_placeholder = QIcon(create_placeholder_pixmap(is_folder=False))

        self.fit_mode = False
        
        self.hide_timer = QTimer(self)
        self.hide_timer.setSingleShot(True)
        self.hide_timer.setInterval(2500)
        self.hide_timer.timeout.connect(self.hide_fit_controls)

        self.init_ui()

        self.setMouseTracking(True)
        self.centralWidget().setMouseTracking(True)
        self.player_container.setMouseTracking(True)
        self.player_container.video_frame.setMouseTracking(True)
        self.player_container.video_frame.installEventFilter(self)

        try:
            self.player = mpv.MPV(
                wid=str(int(self.player_container.video_frame.winId())),
                vo='gpu,x11',
                hwdec='auto',
                log_handler=None
            )
        except Exception:
            self.player = mpv.MPV(wid=str(int(self.player_container.video_frame.winId())), hwdec='auto')

    def init_ui(self):
        main_widget = QWidget()
        self.setCentralWidget(main_widget)
        main_layout = QVBoxLayout(main_widget)
        main_layout.setContentsMargins(8, 8, 8, 8)
        main_layout.setSpacing(6)

        # Top Navigation Bar
        self.top_bar = QWidget()
        top_layout = QHBoxLayout(self.top_bar)
        top_layout.setContentsMargins(0, 0, 0, 0)
        
        lbl_title = QLabel("⚡ ZipyVid")
        lbl_title.setStyleSheet("font-size: 16px; font-weight: bold; color: #e2e83b; margin-right: 10px;")
        top_layout.addWidget(lbl_title)

        self.btn_back_dir = QPushButton("⬆ Up")
        self.btn_back_dir.clicked.connect(self.navigate_up)
        top_layout.addWidget(self.btn_back_dir)

        self.lbl_path = QLabel(self.current_dir)
        self.lbl_path.setStyleSheet("font-size: 12px; color: #8a8a9e; background: #161621; padding: 4px 8px; border-radius: 4px;")
        top_layout.addWidget(self.lbl_path, stretch=1)
        
        self.btn_change_dir = QPushButton("Open Directory")
        self.btn_change_dir.setObjectName("accentButton")
        self.btn_change_dir.clicked.connect(self.select_base_folder)
        top_layout.addWidget(self.btn_change_dir)

        main_layout.addWidget(self.top_bar)

        self.stacked_widget = QStackedWidget()
        
        self.grid = QListWidget()
        self.grid.setViewMode(QListWidget.IconMode)
        self.grid.setIconSize(QSize(180, 110))
        self.grid.setResizeMode(QListWidget.Adjust)
        self.grid.setMovement(QListWidget.Static)
        self.grid.setWordWrap(True)
        self.grid.itemDoubleClicked.connect(self.on_item_double_clicked)
        
        self.grid.setContextMenuPolicy(Qt.CustomContextMenu)
        self.grid.customContextMenuRequested.connect(self.show_context_menu)

        self.stacked_widget.addWidget(self.grid)

        # Player Container (Holds video and overlaid control bar)
        self.player_container = PlayerContainer()
        
        controls_layout = QHBoxLayout(self.player_container.control_frame)
        controls_layout.setContentsMargins(8, 4, 8, 4)
        controls_layout.setSpacing(8)

        self.btn_back_to_grid = QPushButton("📂 Browser")
        self.btn_back_to_grid.clicked.connect(self.show_browser)
        controls_layout.addWidget(self.btn_back_to_grid)

        self.btn_play = QPushButton("▶")
        self.btn_play.setFixedWidth(40)
        self.btn_play.clicked.connect(self.toggle_play)
        controls_layout.addWidget(self.btn_play)

        self.lbl_time = QLabel("00:00 / 00:00")
        controls_layout.addWidget(self.lbl_time)

        self.slider_seek = QSlider(Qt.Horizontal)
        self.slider_seek.setRange(0, 1000)
        self.slider_seek.sliderMoved.connect(self.set_position)
        controls_layout.addWidget(self.slider_seek, stretch=1)

        self.lbl_vol = QLabel("🔊")
        controls_layout.addWidget(self.lbl_vol)

        self.slider_vol = QSlider(Qt.Horizontal)
        self.slider_vol.setRange(0, 100)
        self.slider_vol.setValue(70)
        self.slider_vol.setFixedWidth(80)
        self.slider_vol.sliderMoved.connect(self.set_volume)
        controls_layout.addWidget(self.slider_vol)

        self.btn_fit = QPushButton("📐 Fit")
        self.btn_fit.setToolTip("Maximized Aspect View (Floating Controls)")
        self.btn_fit.clicked.connect(self.toggle_fit_mode)
        controls_layout.addWidget(self.btn_fit)

        self.btn_fullscreen = QPushButton("⛶ Fullscreen")
        self.btn_fullscreen.setToolTip("Toggle Fullscreen")
        self.btn_fullscreen.clicked.connect(self.toggle_fullscreen)
        controls_layout.addWidget(self.btn_fullscreen)

        self.stacked_widget.addWidget(self.player_container)
        main_layout.addWidget(self.stacked_widget, stretch=1)

        self.timer = QTimer(self)
        self.timer.setInterval(500)
        self.timer.timeout.connect(self.update_ui_timer)
        self.timer.start()

        self.populate_grid()

    def eventFilter(self, source, event):
        if self.fit_mode and self.stacked_widget.currentIndex() == 1:
            if event.type() in (QEvent.MouseMove, QEvent.HoverMove):
                self.show_fit_controls()
        return super().eventFilter(source, event)

    def mouseMoveEvent(self, event):
        if self.fit_mode and self.stacked_widget.currentIndex() == 1:
            self.show_fit_controls()
        super().mouseMoveEvent(event)

    def show_fit_controls(self):
        if self.fit_mode:
            self.player_container.control_frame.show()
            self.unsetCursor()
            self.hide_timer.start()

    def hide_fit_controls(self):
        if self.fit_mode and self.stacked_widget.currentIndex() == 1:
            self.player_container.control_frame.hide()
            self.setCursor(Qt.BlankCursor)

    def toggle_fit_mode(self):
        self.fit_mode = not self.fit_mode
        if hasattr(self, 'player'):
            self.player.keepaspect = True

        if self.fit_mode:
            self.btn_fit.setText("📐 Exit Fit")
            self.top_bar.hide()
            self.hide_fit_controls()
        else:
            self.btn_fit.setText("📐 Fit")
            self.hide_timer.stop()
            self.top_bar.show()
            self.player_container.control_frame.show()
            self.unsetCursor()

    def stop_worker(self):
        if self.worker and self.worker.isRunning():
            self.worker.stop()
            self.worker.wait()
            self.worker = None

    def populate_grid(self):
        self.stop_worker()
        self.grid.clear()
        self.lbl_path.setText(self.current_dir)

        try:
            entries = sorted(os.listdir(self.current_dir))
        except PermissionError:
            QMessageBox.critical(self, "Error", "Permission Denied!")
            return

        self.worker = SequentialThumbnailWorker()

        for entry in entries:
            full_path = os.path.abspath(os.path.join(self.current_dir, entry))

            if os.path.isdir(full_path):
                is_locked = full_path in self.locked_folders
                v_count = count_videos_in_folder(full_path)
                
                label_text = f"🔒 {entry}" if is_locked else f"📁 {entry}"
                label_text += f"\n{v_count} video{'s' if v_count != 1 else ''}"

                item = MediaItem(self.folder_placeholder, label_text, full_path, "dir")
                self.grid.addItem(item)
                self.worker.add_job(full_path, is_folder=True)

            elif entry.lower().endswith(('.mp4', '.mkv', '.avi', '.mov', '.webm', '.flv')):
                item = MediaItem(self.video_placeholder, entry, full_path, "file")
                self.grid.addItem(item)
                self.worker.add_job(full_path, is_folder=False)

        self.worker.thumbnail_ready.connect(self.on_thumbnail_loaded)
        self.worker.start()

    def on_thumbnail_loaded(self, target_path, thumb_path):
        for i in range(self.grid.count()):
            item = self.grid.item(i)
            if isinstance(item, MediaItem) and item.item_path == target_path:
                item.setIcon(QIcon(thumb_path))
                break

    def on_item_double_clicked(self, item):
        if not isinstance(item, MediaItem):
            return

        path = item.item_path
        item_type = item.item_type

        if item_type == "dir":
            if path in self.locked_folders:
                pwd, ok = QInputDialog.getText(
                    self, "Locked Folder", f"Enter password for '{os.path.basename(path)}':",
                    QLineEdit.Password
                )
                if ok:
                    hashed = hashlib.sha256(pwd.encode()).hexdigest()
                    if hashed == self.locked_folders[path]:
                        self.current_dir = path
                        self.populate_grid()
                    else:
                        QMessageBox.warning(self, "Access Denied", "Incorrect password!")
            else:
                self.current_dir = path
                self.populate_grid()

        elif item_type == "file":
            self.play_video(path)

    def navigate_up(self):
        if self.current_dir != self.default_dir and os.path.dirname(self.current_dir) >= self.default_dir:
            self.current_dir = os.path.dirname(self.current_dir)
            self.populate_grid()

    def select_base_folder(self):
        folder = QFileDialog.getExistingDirectory(self, "Select Directory", self.default_dir)
        if folder:
            self.default_dir = os.path.abspath(folder)
            self.current_dir = self.default_dir
            self.populate_grid()

    def show_context_menu(self, pos):
        item = self.grid.itemAt(pos)
        if not isinstance(item, MediaItem):
            return

        path = item.item_path
        if item.item_type == "dir":
            menu = QMenu()

            if path in self.locked_folders:
                unlock_act = menu.addAction("🔓 Unlock Folder")
                action = menu.exec_(self.grid.mapToGlobal(pos))
                if action == unlock_act:
                    pwd, ok = QInputDialog.getText(self, "Unlock Folder", "Enter password:", QLineEdit.Password)
                    if ok and hashlib.sha256(pwd.encode()).hexdigest() == self.locked_folders[path]:
                        del self.locked_folders[path]
                        self.save_locks()
                        self.populate_grid()
                    elif ok:
                        QMessageBox.warning(self, "Error", "Wrong password!")
            else:
                lock_act = menu.addAction("🔒 Lock Folder")
                action = menu.exec_(self.grid.mapToGlobal(pos))
                if action == lock_act:
                    pwd, ok = QInputDialog.getText(self, "Lock Folder", "Set password:", QLineEdit.Password)
                    if ok and pwd:
                        hashed = hashlib.sha256(pwd.encode()).hexdigest()
                        self.locked_folders[path] = hashed
                        self.save_locks()
                        self.populate_grid()

    def load_locks(self):
        if os.path.exists(LOCK_FILE):
            try:
                with open(LOCK_FILE, "r") as f:
                    return json.load(f)
            except Exception:
                return {}
        return {}

    def save_locks(self):
        with open(LOCK_FILE, "w") as f:
            json.dump(self.locked_folders, f)

    def play_video(self, file_path):
        clean_path = os.path.abspath(os.path.normpath(str(file_path).strip()))

        if not os.path.exists(clean_path):
            QMessageBox.warning(self, "File Not Found", f"Cannot open:\n{clean_path}")
            return

        self.stacked_widget.setCurrentIndex(1)
        QApplication.processEvents()

        self.player.play(clean_path)
        self.btn_play.setText("⏸")

    def toggle_play(self):
        if hasattr(self, 'player'):
            try:
                self.player.pause = not self.player.pause
                self.btn_play.setText("▶" if self.player.pause else "⏸")
            except Exception:
                pass

    def toggle_fullscreen(self):
        if self.isFullScreen():
            self.showNormal()
            self.btn_fullscreen.setText("⛶ Fullscreen")
            if hasattr(self, 'player'):
                try:
                    self.player.fullscreen = False
                except Exception:
                    pass
        else:
            self.showFullScreen()
            self.btn_fullscreen.setText("🗗 Exit Full")
            if hasattr(self, 'player'):
                try:
                    self.player.fullscreen = True
                except Exception:
                    pass

    def show_browser(self):
        if self.fit_mode:
            self.toggle_fit_mode()
        if self.isFullScreen():
            self.toggle_fullscreen()
        if hasattr(self, 'player'):
            try:
                self.player.stop()
            except Exception:
                pass
        self.stacked_widget.setCurrentIndex(0)

    def set_position(self, value):
        if hasattr(self, 'player') and self.player.duration:
            try:
                target_sec = (value / 1000.0) * self.player.duration
                self.player.seek(target_sec, reference="absolute")
            except Exception:
                pass

    def set_volume(self, value):
        if hasattr(self, 'player'):
            try:
                self.player.volume = value
            except Exception:
                pass

    def update_ui_timer(self):
        if hasattr(self, 'player') and self.stacked_widget.currentIndex() == 1:
            try:
                pos_sec = self.player.time_pos or 0
                dur_sec = self.player.duration or 0

                if dur_sec > 0:
                    percent = int((pos_sec / dur_sec) * 1000)
                    self.slider_seek.setValue(percent)

                t_str = QTime(0, 0, 0).addSecs(int(pos_sec)).toString("hh:mm:ss")
                l_str = QTime(0, 0, 0).addSecs(int(dur_sec)).toString("hh:mm:ss")
                self.lbl_time.setText(f"{t_str} / {l_str}")
            except Exception:
                pass

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Escape:
            if self.fit_mode:
                self.toggle_fit_mode()
            elif self.isFullScreen():
                self.toggle_fullscreen()
        else:
            super().keyPressEvent(event)

    def closeEvent(self, event):
        self.stop_worker()
        if hasattr(self, 'player'):
            try:
                self.player.terminate()
            except Exception:
                pass
        event.accept()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = VideoPlayerApp()
    window.show()
    sys.exit(app.exec_())
PYEOF
