import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import json
import os
import uuid
import pyautogui
import subprocess
import threading
import pystray
from PIL import Image, ImageTk
from flask import Flask, render_template_string, request, jsonify, send_file
import webbrowser
from waitress import serve
import logging
import socket
import qrcode
import sys

class MacroPadServer:
    def __init__(self, root):
        self.root = root
        self.root.title("MacroPad Server")
        self.root.geometry("800x600")
        self.configure_styles()

        # Set up directories
        if getattr(sys, 'frozen', False):
            base_dir = os.path.dirname(sys.executable)
        else:
            base_dir = os.path.dirname(os.path.abspath(__file__))
        self.data_file = os.path.join(base_dir, "macros.json")
        self.images_dir = os.path.join(base_dir, "macro_images")
        self.app_dir = base_dir
        os.makedirs(self.images_dir, exist_ok=True)

        self.macros = {}
        self.load_macros()
        self.image_cache = {}  # Cache for images

        # Set up server
        self.server_running = False
        self.web_app = None
        self.flask_thread = None
        self.server_port = 40000

        # Create UI
        self.create_ui()

        # Set up window close handler
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)

    def configure_styles(self):
        # Dark theme
        self.bg_color = "#2e2e2e"
        self.fg_color = "#ffffff"
        self.highlight_color = "#3e3e3e"
        self.accent_color = "#007acc"
        self.button_bg = "#505050"
        self.button_fg = "#ffffff"

        self.root.configure(bg=self.bg_color)

        # Configure ttk styles
        style = ttk.Style()
        style.theme_use("clam")
        style.configure("TButton", background=self.button_bg, foreground=self.button_fg)
        style.map("TButton", background=[("active", self.accent_color)])
        style.configure("TFrame", background=self.bg_color)
        style.configure("TLabel", background=self.bg_color, foreground=self.fg_color)

    def create_ui(self):
        # Create main container
        main_frame = ttk.Frame(self.root)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        # Left side: Macro list and buttons
        left_frame = ttk.Frame(main_frame)
        left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        # Macro list
        self.listbox_frame = ttk.Frame(left_frame)
        self.listbox_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))

        # Right side: Details and server controls
        right_frame = ttk.Frame(main_frame)
        right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=(10, 0))

        # Button container
        button_frame = ttk.Frame(left_frame)
        button_frame.pack(fill=tk.X, pady=(0, 10))

        button_style = {'style': 'TButton'}

        # Add buttons
        ttk.Button(button_frame, text="Add Macro", command=self.add_macro, **button_style).pack(side=tk.LEFT, padx=2)
        ttk.Button(button_frame, text="Edit Macro", command=self.edit_macro, **button_style).pack(side=tk.LEFT, padx=2)
        ttk.Button(button_frame, text="Delete Macro", command=self.delete_macro, **button_style).pack(side=tk.LEFT, padx=2)

        # Server controls
        server_frame = ttk.Frame(right_frame)
        server_frame.pack(fill=tk.X, pady=10)

        ttk.Label(server_frame, text="Web Server Control:").grid(row=0, column=0, sticky="w", pady=5)

        self.server_button = ttk.Button(server_frame, text="Start Web Server", command=self.toggle_server)
        self.server_button.grid(row=0, column=1, padx=5, pady=5)

        # Status display
        self.status_var = tk.StringVar(value="Web server not running")
        ttk.Label(server_frame, textvariable=self.status_var).grid(row=1, column=0, columnspan=2, sticky="w", pady=5)

        # QR code placeholder
        self.qr_label = ttk.Label(right_frame)
        self.qr_label.pack(pady=10)

        # Server URL display
        self.url_var = tk.StringVar(value="")
        self.url_label = ttk.Label(right_frame, textvariable=self.url_var)
        self.url_label.pack(pady=5)
        
        # Open in browser button
        self.browser_button = ttk.Button(right_frame, text="Open in Browser", 
                                         command=self.open_in_browser, state=tk.DISABLED)
        self.browser_button.pack(pady=5)

        # Display any existing macros
        self.display_macros()

    def load_macros(self):
        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):
        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 display_macros(self):
        # Clear the previous listbox
        for widget in self.listbox_frame.winfo_children():
            widget.destroy()

        # Create scrollable canvas for macros
        canvas = tk.Canvas(self.listbox_frame, bg=self.bg_color, highlightthickness=0)
        scrollbar = ttk.Scrollbar(self.listbox_frame, orient="vertical", command=canvas.yview)
        scrollable_frame = ttk.Frame(canvas)

        # Configure canvas and scrollable frame
        scrollable_frame.bind(
            "<Configure>",
            lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
        )
        canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
        canvas.configure(yscrollcommand=scrollbar.set)

        # Pack canvas and scrollbar
        canvas.pack(side="left", fill="both", expand=True)
        scrollbar.pack(side="right", fill="y")

        # Display macros
        row = 0
        for macro_id, macro in self.macros.items():
            frame = ttk.Frame(scrollable_frame)
            frame.pack(fill="x", pady=5, padx=5)
            
            # Create button with image if available
            button_text = macro["name"]
            button = tk.Button(
                frame, text=button_text, bg=self.button_bg, fg=self.button_fg,
                activebackground=self.accent_color, activeforeground=self.button_fg,
                relief=tk.RAISED, bd=2, pady=8, command=lambda mid=macro_id: self.execute_macro(mid)
            )

            # Try to display image on button
            if "image_path" in macro and macro["image_path"]:
                try:
                    if macro["image_path"] in self.image_cache:
                        button_image = self.image_cache[macro["image_path"]]
                    else:
                        img_path = os.path.join(self.app_dir, macro["image_path"])
                        img = Image.open(img_path)
                        img = img.resize((32, 32))
                        button_image = ImageTk.PhotoImage(img)
                        self.image_cache[macro["image_path"]] = button_image
                    
                    button.config(image=button_image, compound=tk.LEFT)
                    button.image = button_image  # Keep a reference
                except Exception as e:
                    print(f"Error loading image for {macro['name']}: {e}")

            button.pack(side=tk.LEFT, fill=tk.X, expand=True)
            row += 1

        # Display message if no macros
        if not self.macros:
            label = tk.Label(scrollable_frame, text="No macros defined",
                          bg=self.bg_color, fg=self.fg_color)
            label.pack(pady=20)

    def toggle_server(self):
        if self.server_running:
            self.stop_server()
        else:
            self.start_server()

    def start_server(self):
        try:
            if not self.server_running:
                self.server_running = True
                self.flask_thread = threading.Thread(target=self.run_web_server)
                self.flask_thread.daemon = True
                self.flask_thread.start()
                self.server_button.config(text="Stop Web Server")
                
                # Get the systems internal IP address
                ip_address = self.get_ip_addresses()
                if ip_address:
                    # Set the URL display
                    url = f"http://{ip_address}:{self.server_port}"
                    url_text = "Web UI available at:\n" + "\n" + url
                    self.url_var.set(url_text)
                    
                    # Enable browser button
                    self.browser_button.config(state=tk.NORMAL)
                    
                    # Generate and display QR code for the first IP
                    self.generate_qr_code(url)
                else:
                    self.url_var.set("No network interfaces found")
        except Exception as e:
            self.status_var.set(f"Error starting server: {e}")
            self.server_running = False

    def stop_server(self):
        if self.server_running:
            self.server_running = False
            self.status_var.set("Web server stopped")
            self.server_button.config(text="Start Web Server")
            self.url_var.set("")
            self.browser_button.config(state=tk.DISABLED)
            
            # Clear QR code
            self.qr_label.config(image="")
            
            # The Flask server will be stopped on the next request

    def get_ip_addresses(self):
        """Get the primary internal IPv4 address of the machine."""
        try:
            # Create a socket to connect to an external server
            # This helps determine which network interface is used for outbound connections
            s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            # We don't need to actually send data - just configure the socket
            s.connect(("8.8.8.8", 80))
            # Get the IP address that would be used for this connection
            ip = s.getsockname()[0]
            s.close()
            return ip
        except Exception as e:
            print(f"Error getting IP address: {e}")
            # Fallback method if the above doesn't work
            try:
                hostname = socket.gethostname()
                ip = socket.gethostbyname(hostname)
                # Don't return localhost address
                if ip.startswith("127."):
                    for addr_info in socket.getaddrinfo(hostname, None):
                        potential_ip = addr_info[4][0]
                        # If IPv4 and not localhost
                        if '.' in potential_ip and not potential_ip.startswith("127."):
                            return potential_ip
                else:
                    return ip
            except:
                return "127.0.0.1"  # Last resort fallback

    def generate_qr_code(self, url):
        try:
            # Try to import qrcode
            import qrcode
            
            # Generate QR code
            qr = qrcode.QRCode(
                version=1,
                error_correction=qrcode.constants.ERROR_CORRECT_L,
                box_size=10,
                border=4,
            )
            qr.add_data(url)
            qr.make(fit=True)
            
            # Create an image from the QR code
            qr_img = qr.make_image(fill_color="black", back_color="white")
            
            # Convert to PhotoImage for display
            qr_photoimg = ImageTk.PhotoImage(qr_img)
            
            # Update label
            self.qr_label.config(image=qr_photoimg)
            self.qr_label.image = qr_photoimg  # Keep a reference
        except ImportError:
            self.qr_label.config(text="QR code generation requires 'qrcode' package")
        except Exception as e:
            print(f"Error generating QR code: {e}")
            self.qr_label.config(text="Error generating QR code")

    def open_in_browser(self):
        # Open the web interface in the default browser
        if self.server_running:
            webbrowser.open(f"http://localhost:{self.server_port}")

    def create_web_app(self):
        app = Flask(__name__)
        
        # Disable Flask's logging except for errors
        log = logging.getLogger('werkzeug')
        log.setLevel(logging.ERROR)
        
        # Define HTML templates
        index_html = '''
        <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MacroPad Web Interface</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #2e2e2e;
color: #ffffff;
}
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
h1 {
color: #007acc;
margin: 0;
}
.refresh-button {
background-color: #505050;
color: white;
border: none;
border-radius: 8px;
padding: 10px 15px;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s;
display: flex;
align-items: center;
}
.refresh-button:hover {
background-color: #007acc;
}
.refresh-button svg {
margin-right: 5px;
}
.macro-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 15px;
margin-top: 20px;
}
.macro-button {
background-color: #505050;
color: white;
border: none;
border-radius: 8px;
padding: 15px 10px;
text-align: center;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100px;
}
.macro-button:hover, .macro-button:active {
background-color: #007acc;
}
.macro-button img {
max-width: 64px;
max-height: 64px;
margin-bottom: 10px;
}
.status {
text-align: center;
margin: 20px 0;
padding: 10px;
border-radius: 5px;
}
.success {
background-color: #4CAF50;
color: white;
display: none;
}
.error {
background-color: #f44336;
color: white;
display: none;
}
@media (max-width: 600px) {
.macro-grid {
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
}
.macro-button {
padding: 10px 5px;
font-size: 14px;
}
h1 {
font-size: 24px;
}
.refresh-button {
padding: 8px 12px;
font-size: 14px;
}
}
</style>
</head>
<body>
<div class="header-container">
  <h1>MacroPad Web Interface</h1>
  <button class="refresh-button" onclick="loadMacros()">
    <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
      <path d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z"/>
      <path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z"/>
    </svg>
    Refresh
  </button>
</div>
<div class="status success" id="success-status">Macro executed successfully!</div>
<div class="status error" id="error-status">Failed to execute macro</div>
<div class="macro-grid" id="macro-grid">
<!-- Macros will be loaded here -->
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
loadMacros();
});
function loadMacros() {
fetch('/api/macros')
.then(response => response.json())
.then(macros => {
const macroGrid = document.getElementById('macro-grid');
macroGrid.innerHTML = '';
if (Object.keys(macros).length === 0) {
macroGrid.innerHTML = '<p style="grid-column: 1 / -1; text-align: center;">No macros defined.</p>';
return;
}
for (const [macroId, macro] of Object.entries(macros)) {
const button = document.createElement('button');
button.className = 'macro-button';
button.onclick = function() { executeMacro(macroId); };
// Add image if available
if (macro.image_path) {
const img = document.createElement('img');
img.src = `/api/image/${encodeURIComponent(macro.image_path)}`;
img.alt = macro.name;
img.onerror = function() {
this.style.display = 'none';
};
button.appendChild(img);
}
const text = document.createTextNode(macro.name);
button.appendChild(text);
macroGrid.appendChild(button);
}
})
.catch(error => {
console.error('Error loading macros:', error);
});
}
function executeMacro(macroId) {
fetch('/api/execute', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ macro_id: macroId })
})
.then(response => response.json())
.then(data => {
const successStatus = document.getElementById('success-status');
const errorStatus = document.getElementById('error-status');
if (data.success) {
successStatus.style.display = 'block';
errorStatus.style.display = 'none';
setTimeout(() => { successStatus.style.display = 'none'; }, 2000);
} else {
errorStatus.style.display = 'block';
successStatus.style.display = 'none';
setTimeout(() => { errorStatus.style.display = 'none'; }, 2000);
}
})
.catch(error => {
console.error('Error executing macro:', error);
const errorStatus = document.getElementById('error-status');
errorStatus.style.display = 'block';
setTimeout(() => { errorStatus.style.display = 'none'; }, 2000);
});
}
</script>
</body>
</html>
        '''
        
        @app.route('/')
        def index():
            return render_template_string(index_html)
        
        @app.route('/api/macros')
        def get_macros():
            return jsonify(self.macros)
        
        @app.route('/api/image/<path:image_path>')
        def get_image(image_path):
            try:
                # Using os.path.join would translate slashes incorrectly on Windows
                # Use normpath instead
                image_path = os.path.join(self.app_dir, image_path)
                return send_file(image_path)
            except Exception as e:
                return str(e), 404
        
        @app.route('/api/execute', methods=['POST'])
        def execute_macro():
            if not self.server_running:
                return jsonify({"success": False, "error": "Server is shutting down"})
            
            data = request.get_json()
            if not data or 'macro_id' not in data:
                return jsonify({"success": False, "error": "Invalid request"})
            
            macro_id = data['macro_id']
            success = self.execute_macro(macro_id)
            return jsonify({"success": success})
        
        return app

    def run_web_server(self):
        # Create Flask app
        self.web_app = self.create_web_app()
        
        # Update the status in the GUI
        self.status_var.set(f"Web server running on port {self.server_port}")
        
        # Run the Flask app with waitress for production-ready serving
        try:
            serve(self.web_app, host='0.0.0.0', port=self.server_port, threads=4)
        except Exception as e:
            self.status_var.set(f"Web server error: {e}")
            self.server_running = False

    def add_macro(self):
        dialog = tk.Toplevel(self.root)
        dialog.title("Add Macro")
        dialog.geometry("450x350")  # Increased height for additional options
        dialog.transient(self.root)
        dialog.configure(bg=self.bg_color)

        # Apply dark theme to dialog
        tk.Label(dialog, text="Macro Name:", bg=self.bg_color, fg=self.fg_color).grid(row=0, column=0, padx=5, pady=5, sticky="w")
        name_entry = tk.Entry(dialog, width=30, bg=self.highlight_color, fg=self.fg_color, insertbackground=self.fg_color)
        name_entry.grid(row=0, column=1, padx=5, pady=5)

        tk.Label(dialog, text="Type:", bg=self.bg_color, fg=self.fg_color).grid(row=1, column=0, padx=5, pady=5, sticky="w")
        type_var = tk.StringVar(value="text")

        radio_style = {'bg': self.bg_color, 'fg': self.fg_color, 'selectcolor': self.accent_color}
        tk.Radiobutton(dialog, text="Text", variable=type_var, value="text", **radio_style).grid(row=1, column=1, sticky="w")
        tk.Radiobutton(dialog, text="Application", variable=type_var, value="app", **radio_style).grid(row=2, column=1, sticky="w")

        tk.Label(dialog, text="Command/Text:", bg=self.bg_color, fg=self.fg_color).grid(row=3, column=0, padx=5, pady=5, sticky="w")
        command_text = tk.Text(dialog, width=30, height=5, bg=self.highlight_color, fg=self.fg_color, insertbackground=self.fg_color)
        command_text.grid(row=3, column=1, padx=5, pady=5)

        # Modifiers frame
        mod_frame = tk.Frame(dialog, bg=self.bg_color)
        mod_frame.grid(row=4, column=0, columnspan=2, sticky="w", padx=5, pady=5)

        tk.Label(mod_frame, text="Key Modifiers:", bg=self.bg_color, fg=self.fg_color).pack(side=tk.LEFT, padx=5)

        # Add checkboxes for modifiers
        ctrl_var = tk.BooleanVar(value=False)
        alt_var = tk.BooleanVar(value=False)
        shift_var = tk.BooleanVar(value=False)
        enter_var = tk.BooleanVar(value=False)

        checkbox_style = {'bg': self.bg_color, 'fg': self.fg_color, 'selectcolor': self.accent_color,
                          'activebackground': self.bg_color, 'activeforeground': self.fg_color}

        tk.Checkbutton(mod_frame, text="Ctrl", variable=ctrl_var, **checkbox_style).pack(side=tk.LEFT, padx=5)
        tk.Checkbutton(mod_frame, text="Alt", variable=alt_var, **checkbox_style).pack(side=tk.LEFT, padx=5)
        tk.Checkbutton(mod_frame, text="Shift", variable=shift_var, **checkbox_style).pack(side=tk.LEFT, padx=5)
        tk.Checkbutton(mod_frame, text="Add Enter", variable=enter_var, **checkbox_style).pack(side=tk.LEFT, padx=5)

        image_path = tk.StringVar()
        tk.Label(dialog, text="Image:", bg=self.bg_color, fg=self.fg_color).grid(row=5, column=0, padx=5, pady=5, sticky="w")
        image_entry = tk.Entry(dialog, textvariable=image_path, width=30, bg=self.highlight_color, fg=self.fg_color, insertbackground=self.fg_color)
        image_entry.grid(row=5, column=1, padx=5, pady=5)

        button_style = {'bg': self.button_bg, 'fg': self.button_fg, 'activebackground': self.accent_color,
                        'activeforeground': self.button_fg, 'bd': 0, 'relief': tk.FLAT}

        tk.Button(dialog, text="Browse...", command=lambda: image_path.set(filedialog.askopenfilename(
            filetypes=[("Image files", "*.jpg *.jpeg *.png *.gif *.bmp")])), **button_style).grid(row=5, column=2)

        def save_macro():
            name = name_entry.get().strip()
            if not name:
                tk.messagebox.showerror("Error", "Macro name is required")
                return

            macro_type = type_var.get()
            command = command_text.get("1.0", tk.END).strip()
            macro_id = str(uuid.uuid4())

            # Process image if provided
            image_path_reference = ""
            img_path = image_path.get()
            if img_path:
                try:
                    # Generate unique filename for the image
                    file_ext = os.path.splitext(img_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(img_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 with modifier keys
            self.macros[macro_id] = {
                "name": name,
                "type": macro_type,
                "command": command,
                "image_path": image_path_reference,
                "modifiers": {
                    "ctrl": ctrl_var.get(),
                    "alt": alt_var.get(),
                    "shift": shift_var.get(),
                    "enter": enter_var.get()
                }
            }

            self.save_macros()
            self.display_macros()
            dialog.destroy()

        tk.Button(dialog, text="Save", command=save_macro, **button_style).grid(row=6, column=0, padx=5, pady=20)
        tk.Button(dialog, text="Cancel", command=dialog.destroy, **button_style).grid(row=6, column=1, padx=5, pady=20)

    def edit_macro(self):
        # First, show a dialog to select which macro to edit
        if not self.macros:
            tk.messagebox.showinfo("No Macros", "There are no macros to edit.")
            return

        dialog = tk.Toplevel(self.root)
        dialog.title("Select Macro to Edit")
        dialog.geometry("200x340")
        dialog.transient(self.root)
        dialog.configure(bg=self.bg_color)

        # Create a listbox to show available macros
        tk.Label(dialog, text="Select a macro to edit:", bg=self.bg_color, fg=self.fg_color).pack(pady=5)

        listbox = tk.Listbox(dialog, bg=self.highlight_color, fg=self.fg_color, selectbackground=self.accent_color)
        listbox.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)

        # Populate the listbox with macro names
        macro_ids = []
        for macro_id, macro in self.macros.items():
            listbox.insert(tk.END, macro["name"])
            macro_ids.append(macro_id)

        def on_select():
            if not listbox.curselection():
                tk.messagebox.showwarning("No Selection", "Please select a macro to edit.")
                return

            idx = listbox.curselection()[0]
            selected_macro_id = macro_ids[idx]
            dialog.destroy()
            self.open_edit_dialog(selected_macro_id)

        button_style = {'bg': self.button_bg, 'fg': self.button_fg, 'activebackground': self.accent_color,
                        'activeforeground': self.button_fg, 'bd': 0, 'relief': tk.FLAT}

        tk.Button(dialog, text="Edit Selected", command=on_select, **button_style).pack(pady=10)
        tk.Button(dialog, text="Cancel", command=dialog.destroy, **button_style).pack(pady=5)

    def open_edit_dialog(self, macro_id):
        # Open dialog to edit the selected macro
        macro = self.macros[macro_id]

        dialog = tk.Toplevel(self.root)
        dialog.title("Edit Macro")
        dialog.geometry("450x350")  # Increased height for additional options
        dialog.transient(self.root)
        dialog.configure(bg=self.bg_color)

        # Apply dark theme to dialog
        tk.Label(dialog, text="Macro Name:", bg=self.bg_color, fg=self.fg_color).grid(row=0, column=0, padx=5, pady=5, sticky="w")
        name_entry = tk.Entry(dialog, width=30, bg=self.highlight_color, fg=self.fg_color, insertbackground=self.fg_color)
        name_entry.grid(row=0, column=1, padx=5, pady=5)
        name_entry.insert(0, macro["name"])

        tk.Label(dialog, text="Type:", bg=self.bg_color, fg=self.fg_color).grid(row=1, column=0, padx=5, pady=5, sticky="w")
        type_var = tk.StringVar(value=macro["type"])

        radio_style = {'bg': self.bg_color, 'fg': self.fg_color, 'selectcolor': self.accent_color}
        tk.Radiobutton(dialog, text="Text", variable=type_var, value="text", **radio_style).grid(row=1, column=1, sticky="w")
        tk.Radiobutton(dialog, text="Application", variable=type_var, value="app", **radio_style).grid(row=2, column=1, sticky="w")

        tk.Label(dialog, text="Command/Text:", bg=self.bg_color, fg=self.fg_color).grid(row=3, column=0, padx=5, pady=5, sticky="w")
        command_text = tk.Text(dialog, width=30, height=5, bg=self.highlight_color, fg=self.fg_color, insertbackground=self.fg_color)
        command_text.grid(row=3, column=1, padx=5, pady=5)
        command_text.insert("1.0", macro["command"])

        # Modifiers frame
        mod_frame = tk.Frame(dialog, bg=self.bg_color)
        mod_frame.grid(row=4, column=0, columnspan=2, sticky="w", padx=5, pady=5)

        tk.Label(mod_frame, text="Key Modifiers:", bg=self.bg_color, fg=self.fg_color).pack(side=tk.LEFT, padx=5)

        # Get existing modifiers or set defaults
        modifiers = macro.get("modifiers", {})

        # Add checkboxes for modifiers
        ctrl_var = tk.BooleanVar(value=modifiers.get("ctrl", False))
        alt_var = tk.BooleanVar(value=modifiers.get("alt", False))
        shift_var = tk.BooleanVar(value=modifiers.get("shift", False))
        enter_var = tk.BooleanVar(value=modifiers.get("enter", False))

        checkbox_style = {'bg': self.bg_color, 'fg': self.fg_color, 'selectcolor': self.accent_color,
                        'activebackground': self.bg_color, 'activeforeground': self.fg_color}

        tk.Checkbutton(mod_frame, text="Ctrl", variable=ctrl_var, **checkbox_style).pack(side=tk.LEFT, padx=5)
        tk.Checkbutton(mod_frame, text="Alt", variable=alt_var, **checkbox_style).pack(side=tk.LEFT, padx=5)
        tk.Checkbutton(mod_frame, text="Shift", variable=shift_var, **checkbox_style).pack(side=tk.LEFT, padx=5)
        tk.Checkbutton(mod_frame, text="Add Enter", variable=enter_var, **checkbox_style).pack(side=tk.LEFT, padx=5)

        image_path = tk.StringVar()
        tk.Label(dialog, text="Image:", bg=self.bg_color, fg=self.fg_color).grid(row=5, column=0, padx=5, pady=5, sticky="w")
        image_entry = tk.Entry(dialog, textvariable=image_path, width=30, bg=self.highlight_color, fg=self.fg_color, insertbackground=self.fg_color)
        image_entry.grid(row=5, column=1, padx=5, pady=5)

        button_style = {'bg': self.button_bg, 'fg': self.button_fg, 'activebackground': self.accent_color,
                        'activeforeground': self.button_fg, 'bd': 0, 'relief': tk.FLAT}

        tk.Button(dialog, text="Browse...", command=lambda: image_path.set(filedialog.askopenfilename(
            filetypes=[("Image files", "*.jpg *.jpeg *.png *.gif *.bmp")])), **button_style).grid(row=5, column=2)

        def save_edited_macro():
            name = name_entry.get().strip()
            if not name:
                tk.messagebox.showerror("Error", "Macro name is required")
                return

            new_type = type_var.get()
            command = command_text.get("1.0", tk.END).strip()

            # Keep the old image or update with new one
            image_path_reference = macro.get("image_path", "")
            img_path = image_path.get()
            if img_path:
                try:
                    # Generate unique filename for the image
                    file_ext = os.path.splitext(img_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(img_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 with modifiers
            self.macros[macro_id] = {
                "name": name,
                "type": new_type,
                "command": command,
                "image_path": image_path_reference,
                "modifiers": {
                    "ctrl": ctrl_var.get(),
                    "alt": alt_var.get(),
                    "shift": shift_var.get(),
                    "enter": enter_var.get()
                }
            }

            self.save_macros()
            self.display_macros()
            dialog.destroy()

        tk.Button(dialog, text="Save", command=save_edited_macro, **button_style).grid(row=6, column=0, padx=5, pady=20)
        tk.Button(dialog, text="Cancel", command=dialog.destroy, **button_style).grid(row=6, column=1, padx=5, pady=20)

    def delete_macro(self):
        # Show a dialog to select which macro to delete
        if not self.macros:
            tk.messagebox.showinfo("No Macros", "There are no macros to delete.")
            return

        dialog = tk.Toplevel(self.root)
        dialog.title("Delete Macro")
        dialog.geometry("200x340")
        dialog.transient(self.root)
        dialog.configure(bg=self.bg_color)

        # Create a listbox to show available macros
        tk.Label(dialog, text="Select a macro to delete:", bg=self.bg_color, fg=self.fg_color).pack(pady=5)

        listbox = tk.Listbox(dialog, bg=self.highlight_color, fg=self.fg_color, selectbackground=self.accent_color)
        listbox.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)

        # Populate the listbox with macro names
        macro_ids = []
        for macro_id, macro in self.macros.items():
            listbox.insert(tk.END, macro["name"])
            macro_ids.append(macro_id)

        def on_delete():
            if not listbox.curselection():
                tk.messagebox.showwarning("No Selection", "Please select a macro to delete.")
                return

            idx = listbox.curselection()[0]
            selected_macro_id = macro_ids[idx]
            selected_name = self.macros[selected_macro_id]["name"]

            # Confirm deletion
            if tk.messagebox.askyesno("Confirm Deletion", f"Are you sure you want to delete macro '{selected_name}'?"):
                # Delete associated image file if it exists
                macro = self.macros[selected_macro_id]
                if "image_path" in macro and macro["image_path"]:
                    try:
                        img_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 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[selected_macro_id]
                self.save_macros()
                self.display_macros()
                dialog.destroy()

        button_style = {'bg': self.button_bg, 'fg': self.button_fg, 'activebackground': self.accent_color,
                        'activeforeground': self.button_fg, 'bd': 0, 'relief': tk.FLAT}

        tk.Button(dialog, text="Delete Selected", command=on_delete, **button_style).pack(pady=10)
        tk.Button(dialog, text="Cancel", command=dialog.destroy, **button_style).pack(pady=5)

    def execute_macro(self, macro_id):
        if macro_id not in self.macros:
            return False

        macro = self.macros[macro_id]
        try:
            if macro["type"] == "text":
                # Handle key modifiers
                modifiers = macro.get("modifiers", {})
                keys_to_press = []

                # Add modifier keys if enabled
                if modifiers.get("ctrl", False):
                    keys_to_press.append('ctrl')
                if modifiers.get("alt", False):
                    keys_to_press.append('alt')
                if modifiers.get("shift", False):
                    keys_to_press.append('shift')

                # If there are modifier keys, use hotkey functionality
                if keys_to_press:
                    # For single characters with modifiers, use hotkey
                    if len(macro["command"]) == 1:
                        keys_to_press.append(macro["command"])
                        pyautogui.hotkey(*keys_to_press)
                    else:
                        # For longer text, press modifiers, type text, then release
                        for key in keys_to_press:
                            pyautogui.keyDown(key)
                        pyautogui.typewrite(macro["command"], interval=0.02)
                        for key in reversed(keys_to_press):
                            pyautogui.keyUp(key)
                else:
                    # No modifiers, just type the text
                    pyautogui.typewrite(macro["command"], interval=0.02)

                # 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

    def create_tray_icon(self):
        # Create tray icon
        icon_image = Image.new("RGB", (64, 64), self.accent_color)
        menu = (
            pystray.MenuItem('Show', self.show_window),
            pystray.MenuItem('Exit', self.exit_app)
        )
        self.tray_icon = pystray.Icon("macropad", icon_image, "MacroPad Server", menu)
        self.tray_icon.run_detached()

    def show_window(self, icon=None, item=None):
        # Show the window from tray
        self.root.deiconify()
        self.root.state('normal')
        self.root.lift()
        self.root.focus_force()

    def exit_app(self, icon=None, item=None):
        # Actually exit the app
        self.server_running = False
        if hasattr(self, 'tray_icon'):
            self.tray_icon.stop()
        self.root.quit()

    def minimize_to_tray(self):
        # Create tray icon if it doesn't exist
        if not hasattr(self, 'tray_icon'):
            self.create_tray_icon()
        self.root.withdraw()

    def on_closing(self):
        # When the window is closed, minimize to tray instead of closing
        self.minimize_to_tray()


if __name__ == "__main__":
    root = tk.Tk()
    app = MacroPadServer(root)
    root.mainloop()