import sys
import os
import subprocess
import time
import json

from PySide6.QtWidgets import (QApplication, QWidget, QPushButton, QGridLayout,
                               QVBoxLayout, QLabel, QSystemTrayIcon, QMenu)
from PySide6.QtCore import Qt, QPoint, Signal, Slot, QMimeData
from PySide6.QtGui import QCursor, QMouseEvent, QIcon, QAction, QDragEnterEvent, QDropEvent, QDrag

from pynput import mouse

if sys.platform == "win32":
    import ctypes
    from ctypes import wintypes
    user32 = ctypes.windll.user32

CONFIG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "launcher_config.json")


# ====== 🔄 並び替えと右クリックに対応したカスタムボタン ======
class LauncherButton(QPushButton):
    def __init__(self, name, command, index, parent_launcher):
        super().__init__(name)
        self.command = command
        self.index = index
        self.launcher = parent_launcher

        self.setFixedSize(110, 40)
        self.setToolTip(command)

        # 通常のクリックでアプリ起動
        self.clicked.connect(lambda: self.launcher.launch_app(self.command))

        # ドラッグ開始の起点座標
        self.drag_start_pos = QPoint()

    def mousePressEvent(self, event: QMouseEvent):
        """ボタンが押されたときの処理（右クリックメニュー or 左ドラッグ準備）"""
        if event.button() == Qt.MouseButton.RightButton:
            # 右クリックメニュー（削除機能）
            menu = QMenu(self)
            delete_action = QAction("このボタンを削除", self)
            delete_action.triggered.connect(lambda: self.launcher.delete_app(self.index))
            menu.addAction(delete_action)
            menu.exec(event.globalPosition().toPoint())
        elif event.button() == Qt.MouseButton.LeftButton:
            # 左クリック時はドラッグ開始位置を記録
            self.drag_start_pos = event.position().toPoint()
            super().mousePressEvent(event)

    def mouseMoveEvent(self, event: QMouseEvent):
        """ボタンを押し下げたまま移動したときにドラッグを開始する"""
        if not (event.buttons() & Qt.MouseButton.LeftButton):
            return

        # 誤作動防止：一定以上の距離（4ピクセル以上）ドラッグされたら移動開始
        if (event.position().toPoint() - self.drag_start_pos).manhattanLength() < QApplication.startDragDistance():
            return

        # ドラッグオブジェクトの作成
        drag = QDrag(self)
        mime_data = QMimeData()
        # 移動元のボタンインデックスをデータに持たせる
        mime_data.setText(f"INTERNAL_MOVE:{self.index}")
        drag.setMimeData(mime_data)

        # ドラッグ中の見た目をこのボタンの形にする
        drag.setPixmap(self.grab())
        drag.exec(Qt.DropAction.MoveAction)


# ====== 🎨 メインのランチャークラス（3列表示） ======
class ClaunchLikeLauncher(QWidget):
    request_show = Signal()

    def __init__(self):
        super().__init__()
        self.drag_position = QPoint()
        self.last_click_time = 0
        self.listener = None

        self.setAcceptDrops(True)
        self.apps = self.load_config()
        self.init_ui()

        self.request_show.connect(self.show_at_cursor)
        self.start_mouse_listener()

    def init_ui(self):
        self.setWindowFlags(
            Qt.WindowType.FramelessWindowHint |
            Qt.WindowType.WindowStaysOnTopHint |
            Qt.WindowType.Tool
        )

        self.main_layout = QVBoxLayout()
        self.title_label = QLabel("🎨 Quick Launcher")
        self.title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.title_label.setStyleSheet("color: #2c3e50; font-weight: bold; font-size: 13px; padding: 5px;")

        self.grid_widget = QWidget()
        self.grid_layout = QGridLayout(self.grid_widget)
        self.grid_layout.setContentsMargins(0, 0, 0, 0)

        self.main_layout.addWidget(self.title_label)
        self.main_layout.addWidget(self.grid_widget)
        self.setLayout(self.main_layout)

        # 明るいライトテーマ
        self.setStyleSheet("""
            QWidget { background-color: #f7f9fa; border: 2px solid #e2e8f0; border-radius: 8px; }
            QLabel { border: none; background: transparent; }
            QPushButton { background-color: #ffffff; color: #334155; border: 1px solid #cbd5e1; border-radius: 6px; font-size: 12px; font-weight: 500; }
            QPushButton:hover { background-color: #e0f2fe; color: #0369a1; border: 1px solid #7dd3fc; }
            QPushButton:pressed { background-color: #bae6fd; }
            QMenu { background-color: #ffffff; border: 1px solid #cbd5e1; border-radius: 4px; padding: 4px; }
            QMenu::item { color: #334155; padding: 6px 20px; border-radius: 3px; }
            QMenu::item:selected { background-color: #fffee0; color: #b45309; } /* 削除選択時は少し警告色 */
        """)

        self.refresh_buttons()

    def refresh_buttons(self):
        """現在のリストを元に【3列】でボタンを配置する"""
        while self.grid_layout.count():
            child = self.grid_layout.takeAt(0)
            if child.widget():
                child.widget().deleteLater()

        # 🌟 列数を「3列」に変更
        columns = 3
        for index, app_info in enumerate(self.apps):
            row = index // columns
            col = index % columns

            # カスタムボタンを生成
            btn = LauncherButton(app_info["name"], app_info["path"], index, self)
            self.grid_layout.addWidget(btn, row, col)

        self.adjustSize()

    # ====== 📥 ドラッグ＆ドロップ（新規登録 & 並び替え） ======
    def dragEnterEvent(self, event: QDragEnterEvent):
        if event.mimeData().hasUrls() or event.mimeData().hasText():
            event.acceptProposedAction()

    def dropEvent(self, event: QDropEvent):
        # 1. 内部のボタン並び替え（移動）処理
        if event.mimeData().hasText() and event.mimeData().text().startswith("INTERNAL_MOVE:"):
            from_index = int(event.mimeData().text().split(":")[1])

            # ドロップされた位置にあるボタン（移動先）を特定する
            drop_pos = event.position().toPoint()
            target_widget = self.childAt(drop_pos)

            if isinstance(target_widget, LauncherButton):
                to_index = target_widget.index
            else:
                to_index = len(self.apps) - 1 # ボタン以外の場所なら末尾へ

            # リスト内での位置を入れ替え
            if from_index != to_index:
                item = self.apps.pop(from_index)
                self.apps.insert(to_index, item)
                self.save_config()
                self.refresh_buttons()
            event.acceptProposedAction()
            return

        # 2. 外部からのファイルドロップ（新規登録）処理
        urls = event.mimeData().urls()
        if urls:
            for url in urls:
                file_path = url.toLocalFile()
                if file_path:
                    base_name = os.path.splitext(os.path.basename(file_path))[0]
                    self.apps.append({"name": base_name, "path": file_path})

            self.save_config()
            self.refresh_buttons()
            event.acceptProposedAction()

    # ====== ❌ ボタンの削除処理 ======
    def delete_app(self, index):
        """指定されたインデックスのアプリを削除する"""
        if 0 <= index < len(self.apps):
            self.apps.pop(index)
            self.save_config()
            self.refresh_buttons()

    # ====== 💾 設定管理 ======
    def load_config(self):
        if os.path.exists(CONFIG_FILE):
            try:
                with open(CONFIG_FILE, "r", encoding="utf-8") as f:
                    return json.load(f)
            except:
                pass
        return [{"name": "メモ帳", "path": "notepad.exe"}, {"name": "電卓", "path": "calc.exe"}]

    def save_config(self):
        try:
            with open(CONFIG_FILE, "w", encoding="utf-8") as f:
                json.dump(self.apps, f, ensure_ascii=False, indent=4)
        except Exception as e:
            print(e)

    # ====== 🖱️ 常駐・マウスイベント一式 ======
    @Slot()
    def show_at_cursor(self):
        """マウスカーソルの位置にウィンドウの【右上】を合わせて表示する"""
        cursor_pos = QCursor.pos()

        # 1. 一度現在のサイズでウィンドウ全体の寸法を計算
        self.adjustSize()
        window_width = self.frameGeometry().width()

        # 2. マウス位置からウィンドウの横幅分「左」にずらした座標を計算
        # (QPointの調整で、少し隙間を空けています)
        target_x = cursor_pos.x() - window_width - 10
        target_y = cursor_pos.y() + 10

        self.move(target_x, target_y)
        self.show()
        self.raise_()
        self.activateWindow()

import sys
import os
import subprocess
import time
import json

from PySide6.QtWidgets import (QApplication, QWidget, QPushButton, QGridLayout,
                               QVBoxLayout, QLabel, QSystemTrayIcon, QMenu, QHBoxLayout)
from PySide6.QtCore import Qt, QPoint, Signal, Slot, QMimeData
from PySide6.QtGui import QCursor, QMouseEvent, QIcon, QAction, QDragEnterEvent, QDropEvent, QDrag

from pynput import mouse

if sys.platform == "win32":
    import ctypes
    from ctypes import wintypes
    user32 = ctypes.windll.user32

CONFIG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "launcher_config.json")


# ====== 🔄 並び替えと右クリックに対応したカスタムボタン ======
class LauncherButton(QPushButton):
    def __init__(self, name, command, index, parent_launcher):
        super().__init__(name)
        self.command = command
        self.index = index
        self.launcher = parent_launcher

        self.setFixedSize(110, 40)
        self.setToolTip(command)

        # 通常のクリックでアプリ起動
        self.clicked.connect(lambda: self.launcher.launch_app(self.command))

        # ドラッグ開始の起点座標
        self.drag_start_pos = QPoint()

    def mousePressEvent(self, event: QMouseEvent):
        """ボタンが押されたときの処理（右クリックメニュー or 左ドラッグ準備）"""
        if event.button() == Qt.MouseButton.RightButton:
            # 右クリックメニュー（削除機能）
            menu = QMenu(self)
            delete_action = QAction("このボタンを削除", self)
            delete_action.triggered.connect(lambda: self.launcher.delete_app(self.index))
            menu.addAction(delete_action)
            menu.exec(event.globalPosition().toPoint())
        elif event.button() == Qt.MouseButton.LeftButton:
            # 左クリック時はドラッグ開始位置を記録
            self.drag_start_pos = event.position().toPoint()
            super().mousePressEvent(event)

    def mouseMoveEvent(self, event: QMouseEvent):
        """ボタンを押し下げたまま移動したときにドラッグを開始する"""
        if not (event.buttons() & Qt.MouseButton.LeftButton):
            return

        # 誤作動防止：一定以上の距離（4ピクセル以上）ドラッグされたら移動開始
        if (event.position().toPoint() - self.drag_start_pos).manhattanLength() < QApplication.startDragDistance():
            return

        # ドラッグオブジェクトの作成
        drag = QDrag(self)
        mime_data = QMimeData()
        # 移動元のボタンインデックスをデータに持たせる
        mime_data.setText(f"INTERNAL_MOVE:{self.index}")
        drag.setMimeData(mime_data)

        # ドラッグ中の見た目をこのボタンの形にする
        drag.setPixmap(self.grab())
        drag.exec(Qt.DropAction.MoveAction)


# ====== 🎨 メインのランチャークラス（3列表示） ======
class ClaunchLikeLauncher(QWidget):
    request_show = Signal()

    def __init__(self):
        super().__init__()
        self.drag_position = QPoint()
        self.last_click_time = 0
        self.listener = None

        # 【追加】ピン留め状態を管理するフラグ（初期値はオフ）
        self.is_pinned = False

        self.setAcceptDrops(True)
        self.apps = self.load_config()
        self.init_ui()

        self.request_show.connect(self.show_at_cursor)
        self.start_mouse_listener()

    def init_ui(self):
        # ウィンドウフラグの設定を専用メソッドに集約
        self.update_window_flags()

        self.main_layout = QVBoxLayout()

        # タイトルとピン留めボタンを横並びにするヘッダー
        header_layout = QHBoxLayout()

        self.title_label = QLabel("🎨 Quick Launcher")
        self.title_label.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
        self.title_label.setStyleSheet("color: #2c3e50; font-weight: bold; font-size: 13px; padding: 5px;")

        # 📌 ピン留め用のトグルボタン（トグルONで凹む設定）
        self.pin_button = QPushButton("📌")
        self.pin_button.setCheckable(True)
        self.pin_button.setFixedSize(28, 24)
        self.pin_button.setToolTip("最前面に固定 (D&D登録用)")
        self.pin_button.clicked.connect(self.toggle_pin)

        header_layout.addWidget(self.title_label)
        header_layout.addStretch()
        header_layout.addWidget(self.pin_button)

        self.grid_widget = QWidget()
        self.grid_layout = QGridLayout(self.grid_widget)
        self.grid_layout.setContentsMargins(0, 0, 0, 0)

        self.main_layout.addLayout(header_layout)
        self.main_layout.addWidget(self.grid_widget)
        self.setLayout(self.main_layout)

        # 明るいライトテーマ
        self.setStyleSheet("""
            QWidget { background-color: #f7f9fa; border: 2px solid #e2e8f0; border-radius: 8px; }
            QLabel { border: none; background: transparent; }
            QPushButton { background-color: #ffffff; color: #334155; border: 1px solid #cbd5e1; border-radius: 6px; font-size: 12px; font-weight: 500; }
            QPushButton:hover { background-color: #e0f2fe; color: #0369a1; border: 1px solid #7dd3fc; }
            QPushButton:pressed { background-color: #bae6fd; }
            QMenu { background-color: #ffffff; border: 1px solid #cbd5e1; border-radius: 4px; padding: 4px; }
            QMenu::item { color: #334155; padding: 6px 20px; border-radius: 3px; }
            QMenu::item:selected { background-color: #fffee0; color: #b45309; }
        """)

        # 📌 ピン留めボタンのみ専用のスタイルを適用（ONの時に青く光るように変更）
        self.pin_button.setStyleSheet("""
            QPushButton { background-color: #ffffff; border: 1px solid #cbd5e1; border-radius: 4px; }
            QPushButton:hover { background-color: #f1f5f9; border: 1px solid #94a3b8; }
            QPushButton:checked { background-color: #bae6fd; border: 1px solid #0284c7; color: #0369a1; }
        """)

        self.refresh_buttons()

    def update_window_flags(self):
        """ピン留め状態に応じてウィンドウの最前面属性を切り替える"""
        flags = Qt.WindowType.FramelessWindowHint | Qt.WindowType.Tool
        # ピン留め中、または通常時も常に最前面に出す仕様を維持
        flags |= Qt.WindowType.WindowStaysOnTopHint
        self.setWindowFlags(flags)

    @Slot(bool)
    def toggle_pin(self, checked):
        """ピンボタンが押された時の処理"""
        self.is_pinned = checked
        geo = self.geometry()
        self.update_window_flags()
        self.show()  # フラグ適用のため再表示
        self.setGeometry(geo)

    def refresh_buttons(self):
        """現在のリストを元に【3列】でボタンを配置する"""
        while self.grid_layout.count():
            child = self.grid_layout.takeAt(0)
            if child.widget():
                child.widget().deleteLater()

        columns = 3
        for index, app_info in enumerate(self.apps):
            row = index // columns
            col = index % columns

            btn = LauncherButton(app_info["name"], app_info["path"], index, self)
            self.grid_layout.addWidget(btn, row, col)

        self.adjustSize()

    # ====== 📥 ドラッグ＆ドロップ（新規登録 & 並び替え） ======
    def dragEnterEvent(self, event: QDragEnterEvent):
        if event.mimeData().hasUrls() or event.mimeData().hasText():
            event.acceptProposedAction()

    def dropEvent(self, event: QDropEvent):
        # 1. 内部のボタン並び替え（移動）処理
        if event.mimeData().hasText() and event.mimeData().text().startswith("INTERNAL_MOVE:"):
            from_index = int(event.mimeData().text().split(":")[1])

            drop_pos = event.position().toPoint()
            target_widget = self.childAt(drop_pos)

            if isinstance(target_widget, LauncherButton):
                to_index = target_widget.index
            else:
                to_index = len(self.apps) - 1

            if from_index != to_index:
                item = self.apps.pop(from_index)
                self.apps.insert(to_index, item)
                self.save_config()
                self.refresh_buttons()
            event.acceptProposedAction()
            return

        # 2. 外部からのファイルドロップ（新規登録）処理
        urls = event.mimeData().urls()
        if urls:
            for url in urls:
                file_path = url.toLocalFile()
                if file_path:
                    base_name = os.path.splitext(os.path.basename(file_path))[0]
                    self.apps.append({"name": base_name, "path": file_path})

            self.save_config()
            self.refresh_buttons()
            event.acceptProposedAction()

    # ====== ❌ ボタンの削除処理 ======
    def delete_app(self, index):
        if 0 <= index < len(self.apps):
            self.apps.pop(index)
            self.save_config()
            self.refresh_buttons()

    # ====== 💾 設定管理 ======
    def load_config(self):
        if os.path.exists(CONFIG_FILE):
            try:
                with open(CONFIG_FILE, "r", encoding="utf-8") as f:
                    return json.load(f)
            except:
                pass
        return [{"name": "メモ帳", "path": "notepad.exe"}, {"name": "電卓", "path": "calc.exe"}]

    def save_config(self):
        try:
            with open(CONFIG_FILE, "w", encoding="utf-8") as f:
                json.dump(self.apps, f, ensure_ascii=False, indent=4)
        except Exception as e:
            print(e)

    # ====== 🖱️ 常駐・マウスイベント一式 ======
    @Slot()
    def show_at_cursor(self):
        cursor_pos = QCursor.pos()
        self.adjustSize()
        window_width = self.frameGeometry().width()

        target_x = cursor_pos.x() - window_width - 10
        target_y = cursor_pos.y() + 10

        self.move(target_x, target_y)
        self.show()
        self.raise_()
        self.activateWindow()

    def launch_app(self, command):
        try:
            os.startfile(command)
        except:
            try:
                subprocess.Popen(command, shell=True)
            except Exception as e:
                print(e)

        # 【修正】ピン留めがオフのときだけ、アプリ起動後にランチャーを隠す
        if not self.is_pinned:
            self.hide()

    def mousePressEvent(self, event: QMouseEvent):
        if event.button() == Qt.MouseButton.LeftButton:
            self.drag_position = event.globalPosition().toPoint() - self.frameGeometry().topLeft()
            event.accept()

    def mouseMoveEvent(self, event: QMouseEvent):
        if event.buttons() == Qt.MouseButton.LeftButton:
            self.move(event.globalPosition().toPoint() - self.drag_position)
            event.accept()

    def changeEvent(self, event):
        super().changeEvent(event)
        if event.type() == event.Type.ActivationChange and not self.isActiveWindow():
            # 【修正】ピン留めがオフのときだけ、フォーカスアウトで自動非表示にする
            if not self.is_pinned:
                if QApplication.mouseButtons() == Qt.MouseButton.NoButton:
                    self.hide()

    def start_mouse_listener(self):
        def on_click(x, y, button, pressed):
            if button == mouse.Button.left and pressed:
                current_time = time.time()
                if current_time - self.last_click_time < 0.35:
                    if self.is_desktop_clicked():
                        self.request_show.emit()
                self.last_click_time = current_time

        self.listener = mouse.Listener(on_click=on_click)
        self.listener.daemon = True
        self.listener.start()

    def is_desktop_clicked(self):
        if sys.platform != "win32":
            return True
        try:
            point = wintypes.POINT()
            user32.GetCursorPos(ctypes.byref(point))
            hwnd = user32.WindowFromPoint(point)
            buf = ctypes.create_unicode_buffer(256)
            user32.GetClassNameW(hwnd, buf, ctypes.sizeof(buf))
            return buf.value in ["Progman", "WorkerW", "SysListView32"]
        except:
            return False

    def closeEvent(self, event):
        if self.listener:
            self.listener.stop()
        super().closeEvent(event)


class TrayIconManager:
    def __init__(self, launcher_widget):
        self.launcher = launcher_widget
        self.tray = QSystemTrayIcon(launcher_widget)
        self.tray.setIcon(QIcon.fromTheme("system-run"))

        menu = QMenu()
        show_action = QAction("開く", menu)
        show_action.triggered.connect(self.launcher.show_at_cursor)
        exit_action = QAction("終了", menu)
        exit_action.triggered.connect(QApplication.instance().quit)

        menu.addAction(show_action)
        menu.addSeparator()
        menu.addAction(exit_action)

        self.tray.setContextMenu(menu)
        self.tray.show()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    app.setQuitOnLastWindowClosed(False)

    launcher = ClaunchLikeLauncher()
    tray_manager = TrayIconManager(launcher)

    sys.exit(app.exec())
