import sys, time, json, threading, os
from PyQt5 import QtCore, QtGui, QtWidgets
from pynput import mouse, keyboard
from pynput.keyboard import Key, Controller as KeyboardController, Listener as KeyboardListener, GlobalHotKeys
from pynput.mouse import Button, Controller as MouseController, Listener as MouseListener
from PyQt5.QtWidgets import QFileDialog, QMessageBox, QSystemTrayIcon, QMenu, QListWidgetItem, QInputDialog

class MacroEvent:
    def __init__(self, event_type, time_delay, details):
        self.event_type = event_type
        self.time_delay = time_delay
        self.details = details

class Macro:
    def __init__(self, name):
        self.name = name
        self.events = []

class Settings:
    def __init__(self):
        self.playback_speed = 1.0
        self.loop = False
        self.save_path = "macros"
        self.hotkeys = {}
        self.schedule_playbacks = {}

class SettingsDialog(QtWidgets.QDialog):
    def __init__(self, settings, parent=None):
        super(SettingsDialog, self).__init__(parent)
        self.setWindowTitle("Settings")
        self.setFixedSize(400, 300)
        self.settings = settings
        layout = QtWidgets.QVBoxLayout()

        # Playback Speed
        speed_layout = QtWidgets.QHBoxLayout()
        speed_label = QtWidgets.QLabel("Playback Speed:")
        self.speed_combo = QtWidgets.QComboBox()
        self.speed_combo.addItems(["0.5x", "1x", "1.5x", "2x"])
        index = {0.5:0, 1.0:1, 1.5:2, 2.0:3}.get(self.settings.playback_speed, 1)
        self.speed_combo.setCurrentIndex(index)
        speed_layout.addWidget(speed_label)
        speed_layout.addWidget(self.speed_combo)
        layout.addLayout(speed_layout)

        # Loop Playback
        loop_layout = QtWidgets.QHBoxLayout()
        loop_label = QtWidgets.QLabel("Loop Playback:")
        self.loop_checkbox = QtWidgets.QCheckBox()
        self.loop_checkbox.setChecked(self.settings.loop)
        loop_layout.addWidget(loop_label)
        loop_layout.addWidget(self.loop_checkbox)
        layout.addLayout(loop_layout)

        # Save Path
        path_layout = QtWidgets.QHBoxLayout()
        path_label = QtWidgets.QLabel("Save Path:")
        self.path_edit = QtWidgets.QLineEdit(self.settings.save_path)
        self.browse_btn = QtWidgets.QPushButton("Browse")
        self.browse_btn.clicked.connect(self.browse_folder)
        path_layout.addWidget(path_label)
        path_layout.addWidget(self.path_edit)
        path_layout.addWidget(self.browse_btn)
        layout.addLayout(path_layout)

        # Hotkeys
        hotkeys_label = QtWidgets.QLabel("Hotkeys (Format: <ctrl>+<alt>+<key>):")
        layout.addWidget(hotkeys_label)
        self.hotkeys_edits = {}
        actions = ["start_recording", "stop_recording", "start_playback", "stop_playback"]
        for action in actions:
            hk_layout = QtWidgets.QHBoxLayout()
            hk_label = QtWidgets.QLabel(f"{action.replace('_', ' ').title()}:")
            hk_edit = QtWidgets.QLineEdit(self.settings.hotkeys.get(action, ""))
            self.hotkeys_edits[action] = hk_edit
            hk_layout.addWidget(hk_label)
            hk_layout.addWidget(hk_edit)
            layout.addLayout(hk_layout)

        # Schedule Playback
        schedule_label = QtWidgets.QLabel("Schedule Playbacks:")
        layout.addWidget(schedule_label)
        self.schedule_list = QtWidgets.QListWidget()
        for macro_name, schedule in self.settings.schedule_playbacks.items():
            item = QListWidgetItem(f"Macro: {macro_name}, Time: {schedule['time']}, Repeat: {schedule['repeat']}")
            self.schedule_list.addItem(item)
        layout.addWidget(self.schedule_list)
        schedule_btn_layout = QtWidgets.QHBoxLayout()
        add_schedule_btn = QtWidgets.QPushButton("Add Schedule")
        remove_schedule_btn = QtWidgets.QPushButton("Remove Schedule")
        add_schedule_btn.clicked.connect(self.add_schedule)
        remove_schedule_btn.clicked.connect(self.remove_schedule)
        schedule_btn_layout.addWidget(add_schedule_btn)
        schedule_btn_layout.addWidget(remove_schedule_btn)
        layout.addLayout(schedule_btn_layout)

        # Buttons
        buttons = QtWidgets.QHBoxLayout()
        save_btn = QtWidgets.QPushButton("Save")
        cancel_btn = QtWidgets.QPushButton("Cancel")
        save_btn.clicked.connect(self.save_settings)
        cancel_btn.clicked.connect(self.reject)
        buttons.addWidget(save_btn)
        buttons.addWidget(cancel_btn)
        layout.addLayout(buttons)

        self.setLayout(layout)

    def browse_folder(self):
        folder = QFileDialog.getExistingDirectory(self, "Select Save Folder", self.settings.save_path)
        if folder:
            self.path_edit.setText(folder)

    def add_schedule(self):
        macro_names = [macro.name for macro in self.parent().macros]
        if not macro_names:
            QMessageBox.warning(self, "Warning", "No macros available to schedule.")
            return
        macro, ok = QInputDialog.getItem(self, "Select Macro", "Macro:", macro_names, 0, False)
        if not ok:
            return
        time_str, ok = QInputDialog.getText(self, "Set Time", "Enter time (HH:MM:SS):")
        if not ok:
            return
        repeat, ok = QInputDialog.getItem(self, "Repeat", "Repeat:", ["Yes", "No"], 1, False)
        if not ok:
            return
        self.settings.schedule_playbacks[macro] = {"time": time_str, "repeat": True if repeat == "Yes" else False}
        item = QListWidgetItem(f"Macro: {macro}, Time: {time_str}, Repeat: {repeat}")
        self.schedule_list.addItem(item)

    def remove_schedule(self):
        selected = self.schedule_list.currentRow()
        if selected >= 0:
            item = self.schedule_list.takeItem(selected)
            text = item.text()
            parts = text.split(", ")
            macro_part = parts[0].split(": ")[1]
            del self.settings.schedule_playbacks[macro_part]
            del item

    def save_settings(self):
        self.settings.playback_speed = float(self.speed_combo.currentText().replace('x',''))
        self.settings.loop = self.loop_checkbox.isChecked()
        self.settings.save_path = self.path_edit.text()
        for action, edit in self.hotkeys_edits.items():
            self.settings.hotkeys[action] = edit.text()
        self.accept()

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.setWindowTitle("Advanced Macro Recorder")
        self.setFixedSize(600, 400)
        self.settings = Settings()
        self.macros = []
        self.recording = False
        self.playing = False
        self.current_macro = None
        self.hotkey_listener = None
        self.schedule_threads = []
        self.kb_controller = KeyboardController()
        self.mouse_controller = MouseController()
        self.init_ui()
        self.init_tray()
        self.register_hotkeys()
        self.load_settings()

    def init_ui(self):
        central = QtWidgets.QWidget()
        layout = QtWidgets.QVBoxLayout()

        # Macro List
        self.macro_list = QtWidgets.QListWidget()
        layout.addWidget(self.macro_list)

        # Buttons
        btn_layout = QtWidgets.QHBoxLayout()
        self.record_btn = QtWidgets.QPushButton("Record")
        self.stop_btn = QtWidgets.QPushButton("Stop")
        self.play_btn = QtWidgets.QPushButton("Play")
        self.save_btn = QtWidgets.QPushButton("Save")
        self.load_btn = QtWidgets.QPushButton("Load")
        self.delete_btn = QtWidgets.QPushButton("Delete")
        self.export_btn = QtWidgets.QPushButton("Export")
        self.import_btn = QtWidgets.QPushButton("Import")
        btn_layout.addWidget(self.record_btn)
        btn_layout.addWidget(self.stop_btn)
        btn_layout.addWidget(self.play_btn)
        btn_layout.addWidget(self.save_btn)
        btn_layout.addWidget(self.load_btn)
        btn_layout.addWidget(self.delete_btn)
        btn_layout.addWidget(self.export_btn)
        btn_layout.addWidget(self.import_btn)
        layout.addLayout(btn_layout)

        # Status
        self.status_label = QtWidgets.QLabel("Status: Idle")
        layout.addWidget(self.status_label)

        # Settings Button
        self.settings_btn = QtWidgets.QPushButton("Settings")
        layout.addWidget(self.settings_btn)

        central.setLayout(layout)
        self.setCentralWidget(central)

        # Button Actions
        self.record_btn.clicked.connect(self.start_recording)
        self.stop_btn.clicked.connect(self.stop_action)
        self.play_btn.clicked.connect(self.start_playback)
        self.save_btn.clicked.connect(self.save_macro)
        self.load_btn.clicked.connect(self.load_macro)
        self.delete_btn.clicked.connect(self.delete_macro)
        self.export_btn.clicked.connect(self.export_macro)
        self.import_btn.clicked.connect(self.import_macro)
        self.settings_btn.clicked.connect(self.open_settings)

    def init_tray(self):
        self.tray = QSystemTrayIcon(self)
        self.tray.setIcon(self.style().standardIcon(QtWidgets.QStyle.SP_ComputerIcon))
        tray_menu = QMenu()

        open_action = tray_menu.addAction("Open")
        exit_action = tray_menu.addAction("Exit")
        open_action.triggered.connect(self.show)
        exit_action.triggered.connect(QtWidgets.qApp.quit)

        self.tray.setContextMenu(tray_menu)
        self.tray.activated.connect(self.on_tray_activated)
        self.tray.show()

    def on_tray_activated(self, reason):
        if reason == QSystemTrayIcon.Trigger:
            self.show()

    def closeEvent(self, event):
        event.ignore()
        self.hide()

    def register_hotkeys(self):
        if self.hotkey_listener:
            self.hotkey_listener.stop()
        hotkey_dict = {}
        for action, key in self.settings.hotkeys.items():
            if key:
                hotkey_dict[key] = getattr(self, action, None)
        if hotkey_dict:
            self.hotkey_listener = GlobalHotKeys(hotkey_dict)
            self.hotkey_listener.start()

    def load_settings(self):
        if not os.path.exists(self.settings.save_path):
            os.makedirs(self.settings.save_path)
        if os.path.exists("settings.json"):
            with open("settings.json", "r") as f:
                data = json.load(f)
                self.settings.playback_speed = data.get("playback_speed", 1.0)
                self.settings.loop = data.get("loop", False)
                self.settings.save_path = data.get("save_path", "macros")
                self.settings.hotkeys = data.get("hotkeys", {})
                self.settings.schedule_playbacks = data.get("schedule_playbacks", {})
        self.register_hotkeys()
        self.start_schedules()

    def save_settings_to_file(self):
        data = {
            "playback_speed": self.settings.playback_speed,
            "loop": self.settings.loop,
            "save_path": self.settings.save_path,
            "hotkeys": self.settings.hotkeys,
            "schedule_playbacks": self.settings.schedule_playbacks
        }
        with open("settings.json", "w") as f:
            json.dump(data, f, indent=4)

    def start_schedules(self):
        for macro_name, schedule in self.settings.schedule_playbacks.items():
            t = threading.Thread(target=self.schedule_playback, args=(macro_name, schedule["time"], schedule["repeat"]))
            t.daemon = True
            t.start()
            self.schedule_threads.append(t)

    def schedule_playback(self, macro_name, time_str, repeat):
        while True:
            now = time.strftime("%H:%M:%S", time.localtime())
            if now == time_str:
                macro = next((m for m in self.macros if m.name == macro_name), None)
                if macro:
                    self.play_macro(macro)
            time.sleep(1)
            if not repeat:
                break

    def start_recording(self):
        if self.playing:
            QMessageBox.warning(self, "Warning", "Cannot record while playing back.")
            return
        name, ok = QInputDialog.getText(self, "Macro Name", "Enter name for the macro:")
        if not ok or not name:
            return
        if any(m.name == name for m in self.macros):
            QMessageBox.warning(self, "Warning", "Macro with this name already exists.")
            return
        self.current_macro = Macro(name)
        self.macros.append(self.current_macro)
        self.update_macro_list()
        self.recording = True
        self.status_label.setText("Status: Recording")
        self.mouse_listener = MouseListener(on_move=self.on_move, on_click=self.on_click)
        self.keyboard_listener = KeyboardListener(on_press=self.on_press, on_release=self.on_release)
        self.start_time = time.time()
        self.last_time = self.start_time
        self.mouse_listener.start()
        self.keyboard_listener.start()

    def stop_action(self):
        if self.recording:
            self.stop_recording()
        elif self.playing:
            self.stop_playback()

    def stop_recording(self):
        self.recording = False
        self.mouse_listener.stop()
        self.keyboard_listener.stop()
        self.status_label.setText("Status: Idle")
        self.save_settings_to_file()

    def on_move(self, x, y):
        if not self.recording:
            return
        current_time = time.time()
        delay = (current_time - self.last_time) * 1000 / self.settings.playback_speed
        event = MacroEvent("move", delay, {"x": x, "y": y})
        self.current_macro.events.append(event)
        self.last_time = current_time

    def on_click(self, x, y, button, pressed):
        if not self.recording:
            return
        current_time = time.time()
        delay = (current_time - self.last_time) * 1000 / self.settings.playback_speed
        event_type = "click_down" if pressed else "click_up"
        event = MacroEvent(event_type, delay, {"button": button, "x": x, "y": y})
        self.current_macro.events.append(event)
        self.last_time = current_time

    def on_press(self, key):
        if not self.recording:
            return
        current_time = time.time()
        delay = (current_time - self.last_time) * 1000 / self.settings.playback_speed
        event = MacroEvent("key_down", delay, {"key": key})
        self.current_macro.events.append(event)
        self.last_time = current_time

    def on_release(self, key):
        if not self.recording:
            return
        current_time = time.time()
        delay = (current_time - self.last_time) * 1000 / self.settings.playback_speed
        event = MacroEvent("key_up", delay, {"key": key})
        self.current_macro.events.append(event)
        self.last_time = current_time

    def start_playback(self):
        selected_items = self.macro_list.selectedItems()
        if not selected_items:
            QMessageBox.warning(self, "Warning", "No macro selected.")
            return
        macro_name = selected_items[0].text()
        macro = next((m for m in self.macros if m.name == macro_name), None)
        if not macro:
            QMessageBox.warning(self, "Warning", "Selected macro not found.")
            return
        self.playing = True
        self.status_label.setText("Status: Playing")
        t = threading.Thread(target=self.play_macro, args=(macro,))
        t.start()

    def play_macro(self, macro):
        while self.playing:
            for event in macro.events:
                if not self.playing:
                    break
                time.sleep(event.time_delay / 1000.0)
                if event.event_type == "move":
                    self.mouse_controller.position = (event.details["x"], event.details["y"])
                elif event.event_type == "click_down":
                    self.mouse_controller.position = (event.details["x"], event.details["y"])
                    self.mouse_controller.press(event.details["button"])
                elif event.event_type == "click_up":
                    self.mouse_controller.position = (event.details["x"], event.details["y"])
                    self.mouse_controller.release(event.details["button"])
                elif event.event_type == "key_down":
                    self.kb_controller.press(event.details["key"])
                elif event.event_type == "key_up":
                    self.kb_controller.release(event.details["key"])
            if not self.settings.loop:
                break
        self.playing = False
        self.status_label.setText("Status: Idle")

    def stop_playback(self):
        if self.playing:
            self.playing = False
            self.status_label.setText("Status: Stopped")

    def save_macro(self):
        selected_items = self.macro_list.selectedItems()
        if not selected_items:
            QMessageBox.warning(self, "Warning", "No macro selected.")
            return
        macro_name = selected_items[0].text()
        macro = next((m for m in self.macros if m.name == macro_name), None)
        if not macro:
            QMessageBox.warning(self, "Warning", "Selected macro not found.")
            return
        file_path, _ = QFileDialog.getSaveFileName(self, "Save Macro", self.settings.save_path, "JSON Files (*.json)")
        if file_path:
            data = []
            for event in macro.events:
                detail = event.details.copy()
                if "button" in detail and isinstance(detail["button"], Button):
                    detail["button"] = detail["button"].name
                if "key" in detail and isinstance(detail["key"], Key):
                    detail["key"] = detail["key"].name
                data.append({
                    "event_type": event.event_type,
                    "time_delay": event.time_delay,
                    "details": detail
                })
            with open(file_path, "w") as f:
                json.dump(data, f, indent=4)
            QMessageBox.information(self, "Saved", f"Macro saved to {file_path}")

    def load_macro(self):
        file_path, _ = QFileDialog.getOpenFileName(self, "Load Macro", self.settings.save_path, "JSON Files (*.json)")
        if file_path:
            with open(file_path, "r") as f:
                data = json.load(f)
                name, ok = QInputDialog.getText(self, "Macro Name", "Enter name for the loaded macro:")
                if not ok or not name:
                    return
                if any(m.name == name for m in self.macros):
                    QMessageBox.warning(self, "Warning", "Macro with this name already exists.")
                    return
                macro = Macro(name)
                for item in data:
                    details = item["details"]
                    if "button" in details and details["button"]:
                        details["button"] = Button[details["button"]]
                    if "key" in details and details["key"]:
                        details["key"] = Key[details["key"]]
                    event = MacroEvent(item["event_type"], item["time_delay"], details)
                    macro.events.append(event)
                self.macros.append(macro)
                self.update_macro_list()
                QMessageBox.information(self, "Loaded", f"Macro '{name}' loaded successfully.")

    def delete_macro(self):
        selected_items = self.macro_list.selectedItems()
        if not selected_items:
            QMessageBox.warning(self, "Warning", "No macro selected.")
            return
        macro_name = selected_items[0].text()
        macro = next((m for m in self.macros if m.name == macro_name), None)
        if macro:
            reply = QMessageBox.question(self, 'Delete Macro', f"Are you sure you want to delete macro '{macro_name}'?",
                                         QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
            if reply == QMessageBox.Yes:
                self.macros.remove(macro)
                self.update_macro_list()

    def export_macro(self):
        selected_items = self.macro_list.selectedItems()
        if not selected_items:
            QMessageBox.warning(self, "Warning", "No macro selected.")
            return
        macro_name = selected_items[0].text()
        macro = next((m for m in self.macros if m.name == macro_name), None)
        if not macro:
            QMessageBox.warning(self, "Warning", "Selected macro not found.")
            return
        file_path, _ = QFileDialog.getSaveFileName(self, "Export Macro", self.settings.save_path, "JSON Files (*.json)")
        if file_path:
            data = []
            for event in macro.events:
                detail = event.details.copy()
                if "button" in detail and isinstance(detail["button"], Button):
                    detail["button"] = detail["button"].name
                if "key" in detail and isinstance(detail["key"], Key):
                    detail["key"] = detail["key"].name
                data.append({
                    "event_type": event.event_type,
                    "time_delay": event.time_delay,
                    "details": detail
                })
            with open(file_path, "w") as f:
                json.dump(data, f, indent=4)
            QMessageBox.information(self, "Exported", f"Macro exported to {file_path}")

    def import_macro(self):
        file_path, _ = QFileDialog.getOpenFileName(self, "Import Macro", self.settings.save_path, "JSON Files (*.json)")
        if file_path:
            with open(file_path, "r") as f:
                data = json.load(f)
                name, ok = QInputDialog.getText(self, "Macro Name", "Enter name for the imported macro:")
                if not ok or not name:
                    return
                if any(m.name == name for m in self.macros):
                    QMessageBox.warning(self, "Warning", "Macro with this name already exists.")
                    return
                macro = Macro(name)
                for item in data:
                    details = item["details"]
                    if "button" in details and details["button"]:
                        details["button"] = Button[details["button"]]
                    if "key" in details and details["key"]:
                        details["key"] = Key[details["key"]]
                    event = MacroEvent(item["event_type"], item["time_delay"], details)
                    macro.events.append(event)
                self.macros.append(macro)
                self.update_macro_list()
                QMessageBox.information(self, "Imported", f"Macro '{name}' imported successfully.")

    def update_macro_list(self):
        self.macro_list.clear()
        for macro in self.macros:
            self.macro_list.addItem(macro.name)

    def open_settings(self):
        dialog = SettingsDialog(self.settings, self)
        if dialog.exec_():
            self.settings.playback_speed = self.settings.playback_speed
            self.settings.loop = self.settings.loop
            self.settings.save_path = self.settings.save_path
            self.settings.hotkeys = self.settings.hotkeys
            self.settings.schedule_playbacks = self.settings.schedule_playbacks
            self.save_settings_to_file()
            self.register_hotkeys()

    def save_settings_to_file(self):
        data = {
            "playback_speed": self.settings.playback_speed,
            "loop": self.settings.loop,
            "save_path": self.settings.save_path,
            "hotkeys": self.settings.hotkeys,
            "schedule_playbacks": self.settings.schedule_playbacks
        }
        with open("settings.json", "w") as f:
            json.dump(data, f, indent=4)

    def start_recording(self):
        if self.playing:
            QMessageBox.warning(self, "Warning", "Cannot record while playing back.")
            return
        name, ok = QInputDialog.getText(self, "Macro Name", "Enter name for the macro:")
        if not ok or not name:
            return
        if any(m.name == name for m in self.macros):
            QMessageBox.warning(self, "Warning", "Macro with this name already exists.")
            return
        self.current_macro = Macro(name)
        self.macros.append(self.current_macro)
        self.update_macro_list()
        self.recording = True
        self.status_label.setText("Status: Recording")
        self.mouse_listener = MouseListener(on_move=self.on_move, on_click=self.on_click)
        self.keyboard_listener = KeyboardListener(on_press=self.on_press, on_release=self.on_release)
        self.start_time = time.time()
        self.last_time = self.start_time
        self.mouse_listener.start()
        self.keyboard_listener.start()

    def start_playback(self):
        selected_items = self.macro_list.selectedItems()
        if not selected_items:
            QMessageBox.warning(self, "Warning", "No macro selected.")
            return
        macro_name = selected_items[0].text()
        macro = next((m for m in self.macros if m.name == macro_name), None)
        if not macro:
            QMessageBox.warning(self, "Warning", "Selected macro not found.")
            return
        self.playing = True
        self.status_label.setText("Status: Playing")
        t = threading.Thread(target=self.play_macro, args=(macro,))
        t.start()

    def play_macro(self, macro):
        while self.playing:
            for event in macro.events:
                if not self.playing:
                    break
                time.sleep(event.time_delay / 1000.0 / self.settings.playback_speed)
                if event.event_type == "move":
                    self.mouse_controller.position = (event.details["x"], event.details["y"])
                elif event.event_type == "click_down":
                    self.mouse_controller.position = (event.details["x"], event.details["y"])
                    self.mouse_controller.press(event.details["button"])
                elif event.event_type == "click_up":
                    self.mouse_controller.position = (event.details["x"], event.details["y"])
                    self.mouse_controller.release(event.details["button"])
                elif event.event_type == "key_down":
                    self.kb_controller.press(event.details["key"])
                elif event.event_type == "key_up":
                    self.kb_controller.release(event.details["key"])
            if not self.settings.loop:
                break
        self.playing = False
        self.status_label.setText("Status: Idle")

    def stop_playback(self):
        if self.playing:
            self.playing = False
            self.status_label.setText("Status: Stopped")

    def save_macro(self):
        selected_items = self.macro_list.selectedItems()
        if not selected_items:
            QMessageBox.warning(self, "Warning", "No macro selected.")
            return
        macro_name = selected_items[0].text()
        macro = next((m for m in self.macros if m.name == macro_name), None)
        if not macro:
            QMessageBox.warning(self, "Warning", "Selected macro not found.")
            return
        file_path, _ = QFileDialog.getSaveFileName(self, "Save Macro", self.settings.save_path, "JSON Files (*.json)")
        if file_path:
            data = []
            for event in macro.events:
                detail = event.details.copy()
                if "button" in detail and isinstance(detail["button"], Button):
                    detail["button"] = detail["button"].name
                if "key" in detail and isinstance(detail["key"], Key):
                    detail["key"] = detail["key"].name
                data.append({
                    "event_type": event.event_type,
                    "time_delay": event.time_delay,
                    "details": detail
                })
            with open(file_path, "w") as f:
                json.dump(data, f, indent=4)
            QMessageBox.information(self, "Saved", f"Macro saved to {file_path}")

    def load_macro(self):
        file_path, _ = QFileDialog.getOpenFileName(self, "Load Macro", self.settings.save_path, "JSON Files (*.json)")
        if file_path:
            with open(file_path, "r") as f:
                data = json.load(f)
                name, ok = QInputDialog.getText(self, "Macro Name", "Enter name for the loaded macro:")
                if not ok or not name:
                    return
                if any(m.name == name for m in self.macros):
                    QMessageBox.warning(self, "Warning", "Macro with this name already exists.")
                    return
                macro = Macro(name)
                for item in data:
                    details = item["details"]
                    if "button" in details and details["button"]:
                        details["button"] = Button[details["button"]]
                    if "key" in details and details["key"]:
                        details["key"] = Key[details["key"]]
                    event = MacroEvent(item["event_type"], item["time_delay"], details)
                    macro.events.append(event)
                self.macros.append(macro)
                self.update_macro_list()
                QMessageBox.information(self, "Loaded", f"Macro '{name}' loaded successfully.")

    def delete_macro(self):
        selected_items = self.macro_list.selectedItems()
        if not selected_items:
            QMessageBox.warning(self, "Warning", "No macro selected.")
            return
        macro_name = selected_items[0].text()
        macro = next((m for m in self.macros if m.name == macro_name), None)
        if macro:
            reply = QMessageBox.question(self, 'Delete Macro', f"Are you sure you want to delete macro '{macro_name}'?",
                                         QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
            if reply == QMessageBox.Yes:
                self.macros.remove(macro)
                self.update_macro_list()

    def export_macro(self):
        selected_items = self.macro_list.selectedItems()
        if not selected_items:
            QMessageBox.warning(self, "Warning", "No macro selected.")
            return
        macro_name = selected_items[0].text()
        macro = next((m for m in self.macros if m.name == macro_name), None)
        if not macro:
            QMessageBox.warning(self, "Warning", "Selected macro not found.")
            return
        file_path, _ = QFileDialog.getSaveFileName(self, "Export Macro", self.settings.save_path, "JSON Files (*.json)")
        if file_path:
            data = []
            for event in macro.events:
                detail = event.details.copy()
                if "button" in detail and isinstance(detail["button"], Button):
                    detail["button"] = detail["button"].name
                if "key" in detail and isinstance(detail["key"], Key):
                    detail["key"] = detail["key"].name
                data.append({
                    "event_type": event.event_type,
                    "time_delay": event.time_delay,
                    "details": detail
                })
            with open(file_path, "w") as f:
                json.dump(data, f, indent=4)
            QMessageBox.information(self, "Exported", f"Macro exported to {file_path}")

    def import_macro(self):
        file_path, _ = QFileDialog.getOpenFileName(self, "Import Macro", self.settings.save_path, "JSON Files (*.json)")
        if file_path:
            with open(file_path, "r") as f:
                data = json.load(f)
                name, ok = QInputDialog.getText(self, "Macro Name", "Enter name for the imported macro:")
                if not ok or not name:
                    return
                if any(m.name == name for m in self.macros):
                    QMessageBox.warning(self, "Warning", "Macro with this name already exists.")
                    return
                macro = Macro(name)
                for item in data:
                    details = item["details"]
                    if "button" in details and details["button"]:
                        details["button"] = Button[details["button"]]
                    if "key" in details and details["key"]:
                        details["key"] = Key[details["key"]]
                    event = MacroEvent(item["event_type"], item["time_delay"], details)
                    macro.events.append(event)
                self.macros.append(macro)
                self.update_macro_list()
                QMessageBox.information(self, "Imported", f"Macro '{name}' imported successfully.")

    def update_macro_list(self):
        self.macro_list.clear()
        for macro in self.macros:
            self.macro_list.addItem(macro.name)

    def open_settings(self):
        dialog = SettingsDialog(self.settings, self)
        if dialog.exec_():
            self.settings.playback_speed = dialog.settings.playback_speed
            self.settings.loop = dialog.settings.loop
            self.settings.save_path = dialog.settings.save_path
            self.settings.hotkeys = dialog.settings.hotkeys
            self.settings.schedule_playbacks = dialog.settings.schedule_playbacks
            self.save_settings_to_file()
            self.register_hotkeys()

    def save_settings_to_file(self):
        data = {
            "playback_speed": self.settings.playback_speed,
            "loop": self.settings.loop,
            "save_path": self.settings.save_path,
            "hotkeys": self.settings.hotkeys,
            "schedule_playbacks": self.settings.schedule_playbacks
        }
        with open("settings.json", "w") as f:
            json.dump(data, f, indent=4)

    def start_schedules(self):
        for macro_name, schedule in self.settings.schedule_playbacks.items():
            t = threading.Thread(target=self.schedule_playback, args=(macro_name, schedule["time"], schedule["repeat"]))
            t.daemon = True
            t.start()
            self.schedule_threads.append(t)

    def schedule_playback(self, macro_name, time_str, repeat):
        while True:
            now = time.strftime("%H:%M:%S", time.localtime())
            if now == time_str:
                macro = next((m for m in self.macros if m.name == macro_name), None)
                if macro:
                    self.play_macro(macro)
            time.sleep(1)
            if not repeat:
                break

    def play_macro(self, macro):
        if self.playing:
            return
        self.playing = True
        self.status_label.setText(f"Status: Playing '{macro.name}'")
        for event in macro.events:
            if not self.playing:
                break
            time.sleep(event.time_delay / 1000.0 / self.settings.playback_speed)
            if event.event_type == "move":
                self.mouse_controller.position = (event.details["x"], event.details["y"])
            elif event.event_type == "click_down":
                self.mouse_controller.position = (event.details["x"], event.details["y"])
                self.mouse_controller.press(event.details["button"])
            elif event.event_type == "click_up":
                self.mouse_controller.position = (event.details["x"], event.details["y"])
                self.mouse_controller.release(event.details["button"])
            elif event.event_type == "key_down":
                self.kb_controller.press(event.details["key"])
            elif event.event_type == "key_up":
                self.kb_controller.release(event.details["key"])
        if self.settings.loop:
            self.play_macro(macro)
        else:
            self.playing = False
            self.status_label.setText("Status: Idle")

def main():
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main() fix alot of bugs in the code make the recoding work make the playback work doing the thing macro does fix all other things so all works completly thank you