# Macro management and execution

import json
import os
import uuid
import pyautogui
import subprocess
import time
from PIL import Image


class MacroManager:
    def __init__(self, data_file, images_dir, app_dir):
        self.data_file = data_file
        self.images_dir = images_dir
        self.app_dir = app_dir
        self.macros = {}
        self.load_macros()
        
    def load_macros(self):
        """Load macros from JSON file"""
        try:
            if os.path.exists(self.data_file):
                with open(self.data_file, "r") as file:
                    self.macros = json.load(file)
        except Exception as e:
            print(f"Error loading macros: {e}")
            self.macros = {}

    def save_macros(self):
        """Save macros to JSON file"""
        try:
            with open(self.data_file, "w") as file:
                json.dump(self.macros, file, indent=4)
        except Exception as e:
            print(f"Error saving macros: {e}")

    def get_sorted_macros(self, sort_by="name"):
        """Get macros sorted by specified criteria"""
        macro_list = list(self.macros.items())
        
        if sort_by == "name":
            macro_list.sort(key=lambda x: x[1]["name"].lower())
        elif sort_by == "type":
            macro_list.sort(key=lambda x: (x[1].get("type", ""), x[1]["name"].lower()))
        elif sort_by == "recent":
            # Sort by last_used timestamp if available, otherwise by name
            macro_list.sort(key=lambda x: (x[1].get("last_used", 0), x[1]["name"].lower()), reverse=True)
        
        return macro_list

    def filter_macros_by_tab(self, macro_list, tab_name):
        """Filter macros based on tab name"""
        if tab_name == "All":
            return macro_list
        
        filtered = []
        for macro_id, macro in macro_list:
            # Check type match
            if macro.get("type", "").title() == tab_name:
                filtered.append((macro_id, macro))
            # Check custom category match
            elif macro.get("category") == tab_name:
                filtered.append((macro_id, macro))
        
        return filtered

    def get_unique_tabs(self):
        """Get list of unique tabs based on macro types and categories"""
        tabs = ["All"]
        unique_types = set()
        
        for macro in self.macros.values():
            if macro.get("type"):
                unique_types.add(macro["type"].title())
            if macro.get("category"):
                unique_types.add(macro["category"])
        
        for tab_type in sorted(unique_types):
            if tab_type not in ["All"]:
                tabs.append(tab_type)
                
        return tabs

    def add_macro(self, name, macro_type, command, category="", modifiers=None, image_path=""):
        """Add a new macro"""
        if modifiers is None:
            modifiers = {"ctrl": False, "alt": False, "shift": False, "enter": False}
            
        macro_id = str(uuid.uuid4())
        
        # Process image if provided
        image_path_reference = ""
        if image_path:
            try:
                # Generate unique filename for the image
                file_ext = os.path.splitext(image_path)[1].lower()
                unique_filename = f"{uuid.uuid4().hex}{file_ext}"
                dest_path = os.path.join(self.images_dir, unique_filename)
                
                # Resize image to max 256x256
                with Image.open(image_path) as img:
                    img.thumbnail((256, 256))
                    img.save(dest_path)
                
                # Store the relative path to the image
                image_path_reference = os.path.join("macro_images", unique_filename)
            except Exception as e:
                print(f"Error processing image: {e}")

        # Create macro data
        macro_data = {
            "name": name,
            "type": macro_type,
            "command": command,
            "image_path": image_path_reference,
            "modifiers": modifiers,
            "last_used": 0
        }
        
        if category:
            macro_data["category"] = category

        self.macros[macro_id] = macro_data
        self.save_macros()
        return macro_id

    def update_macro(self, macro_id, name, macro_type, command, category="", modifiers=None, image_path=""):
        """Update an existing macro"""
        if macro_id not in self.macros:
            return False
            
        if modifiers is None:
            modifiers = {"ctrl": False, "alt": False, "shift": False, "enter": False}
            
        macro = self.macros[macro_id]
        
        # Keep the old image or update with new one
        image_path_reference = macro.get("image_path", "")
        if image_path:
            try:
                # Generate unique filename for the image
                file_ext = os.path.splitext(image_path)[1].lower()
                unique_filename = f"{uuid.uuid4().hex}{file_ext}"
                dest_path = os.path.join(self.images_dir, unique_filename)
                
                # Resize image to max 256x256
                with Image.open(image_path) as img:
                    img.thumbnail((256, 256))
                    img.save(dest_path)
                
                # Store the relative path to the image
                image_path_reference = os.path.join("macro_images", unique_filename)
            except Exception as e:
                print(f"Error processing image: {e}")

        # Update macro data
        updated_macro = {
            "name": name,
            "type": macro_type,
            "command": command,
            "image_path": image_path_reference,
            "modifiers": modifiers,
            "last_used": macro.get("last_used", 0)
        }
        
        if category:
            updated_macro["category"] = category
        
        self.macros[macro_id] = updated_macro
        self.save_macros()
        return True

    def delete_macro(self, macro_id):
        """Delete a macro"""
        if macro_id not in self.macros:
            return False
            
        macro = self.macros[macro_id]
        
        # Delete associated image file if it exists
        if "image_path" in macro and macro["image_path"]:
            try:
                img_path = os.path.join(self.app_dir, macro["image_path"])
                if os.path.exists(img_path):
                    os.remove(img_path)
            except Exception as e:
                print(f"Error removing image file: {e}")
                
        del self.macros[macro_id]
        self.save_macros()
        return True

    def execute_macro(self, macro_id):
        """Execute a macro by ID"""
        if macro_id not in self.macros:
            return False

        macro = self.macros[macro_id]
        
        # Update last_used timestamp for recent sorting
        self.macros[macro_id]["last_used"] = time.time()
        self.save_macros()
        
        try:
            if macro["type"] == "text":
                # Handle key modifiers
                modifiers = macro.get("modifiers", {})

                # Add modifier keys if enabled
                if modifiers.get("ctrl", False):
                    pyautogui.keyDown('ctrl')
                if modifiers.get("alt", False):
                    pyautogui.keyDown('alt')
                if modifiers.get("shift", False):
                    pyautogui.keyDown('shift')
                
                # Handle single character vs multi-character commands
                if str(macro["command"]) and len(str(macro["command"])) == 1:
                    pyautogui.keyDown(macro["command"])
                    time.sleep(0.5)
                    pyautogui.keyUp(macro["command"])
                else:
                    pyautogui.typewrite(macro["command"], interval=0.02)
                
                # Release modifier keys in reverse order
                if modifiers.get("shift", False):
                    pyautogui.keyUp('shift')
                if modifiers.get("alt", False):
                    pyautogui.keyUp('alt')
                if modifiers.get("ctrl", False):
                    pyautogui.keyUp('ctrl')

                # Add Enter/Return if requested
                if modifiers.get("enter", False):
                    pyautogui.press('enter')

            elif macro["type"] == "app":
                subprocess.Popen(macro["command"], shell=True)
                
            return True
        except Exception as e:
            print(f"Error executing macro: {e}")
            return False