#!/usr/bin/env python3

# This script is used to display help information for Hyprland keybinds.

import os
import sys
import re
import subprocess


HARDCODED_VARIABLES = {
    "exec, ": "",
    "VoidSymbol": "CAPS",
    "brave --proxy-pac-url=\"https://pac.cn01.woodburn.au/proxy.pac\" --enable-features=UseOzonePlatform --ozone-platform=wayland --use-gl=angle --ignore-gpu-blocklist --enable-features=VaapiVideoEncoder,VaapiVideoDecoder,CanvasOopRasterization,VaapiIgnoreDriverChecks,VaapiVideoDecodeLinuxGL,AcceleratedVideoEncoder,Vulkan,DefaultANGLEVulkan,VulkanFromANGLE --disable-gpu-memory-buffer-video-frames": "brave",
    "~/.config/hypr/scripts/":"󰯂 ",
    ".sh": "",
    ".py": "",
    "alacritty --config-file ~/dotfiles/.alacritty-nozellij.toml": "Terminal (No Zellij)",
    "ydotool key 56:1 105:1 105:0 56:0":"Back",
    "ydotool key 56:1 106:1 106:0 56:0":"Forward",
    "mouse_left":"Left Tilt",
    "mouse_right":"Right Tilt",
    "[float] kitty --class float-80 -e":"",
    "alacritty --class float-80 -e":"",
    "alacritty --class float -e":"",
    "$":" ",
    "mouse:272": "Left Drag",
    "mouse:273": "Right Drag",
    "hyprctl": "",
    "dispatch ": "",
    "vdesk,": "󰧨",
    "vdeskreset": "󰧨 Reset",
    "movetodesk": "󰶭 󰧨",
    "movetoworkspace": "󰶭 󰧨",
    "togglespecialworkspace": "󰔡 󰧨",
    "special:":"",
    "lastdesk": "󰧨 Last Active",
}

IGNORED_ACTIONS = [
    "hyprctl notify",
    "submap, reset",
    "hyprpanel -q"
]

TYPE_MAP = {
    'bind': ' ',
    'bindl': '',
    'bindle': 'Lock',
    'bindm': '󰍽',
}
SUBMAP_MAP ={
    "main": "",
    "capsmode": "󰚟󰌎",
    "opacity": "󰚟󱡔",
}

KEYCODES = {
    20: "-"
}

# Read the keybinds.conf file
def read_keybinds(file_path = os.path.expanduser('~/.config/hypr/keybinds.conf')):
    if not os.path.exists(file_path):
        print(f"Error: The file {file_path} does not exist.")
        sys.exit(1)

    with open(file_path, 'r') as file:
        lines = file.readlines()

    return lines

def parse_keybinds(lines):
    """Parse keybinds from the configuration file and return a list of structured keybind information."""
    keybinds = []
    variables = {}
    current_submap = "main"
    
    for line_num, line in enumerate(lines, 1):
        line = line.strip()
        
        # Skip empty lines and comments
        if not line or line.startswith('#'):
            continue
            
        # Handle variable definitions
        if line.startswith('$'):
            var_match = re.match(r'\$(\w+)\s*=\s*(.+)', line)
            if var_match:
                variables[var_match.group(1)] = var_match.group(2)
            continue
            
        # Handle submap declarations
        if line.startswith('submap ='):
            submap_match = re.match(r'submap\s*=\s*(\w+)', line)
            if submap_match:
                current_submap = submap_match.group(1)
            continue
            
        # Handle bind declarations
        if line.startswith('bind') or line.startswith('bindle') or line.startswith('bindl') or line.startswith('bindm'):
            bind_match = re.match(r'(bind[lme]?)\s*=\s*([^,]+),\s*([^,]+),\s*(.+)', line)
            if bind_match:
                bind_type = bind_match.group(1)
                modifiers = bind_match.group(2).strip()
                key = bind_match.group(3).strip()
                action = bind_match.group(4).strip()
                
                # Ignore certain actions
                if any(ignored in action for ignored in IGNORED_ACTIONS):
                    continue

                # Replace variables in the key, modifiers, and action
                for var_name, var_value in variables.items():
                    modifiers = modifiers.replace(f'${var_name}', var_value)
                    key = key.replace(f'${var_name}', var_value)
                    # action = action.replace(f'${var_name}', var_value)

                # Update for HARDCODED
                for var_name, var_value in HARDCODED_VARIABLES.items():
                    modifiers = modifiers.replace(f'{var_name}', var_value)
                    key = key.replace(f'{var_name}', var_value)
                    action = action.replace(f'{var_name}', var_value)

                if action.startswith('submap,'):
                    smap = action.split(',')[1].strip()
                    action = SUBMAP_MAP.get(smap, smap)

                # Convert any 'code:<number>' in key to the corresponding Unicode character
                code_match = re.match(r'code:(\d+)', key)
                if code_match:
                    code_num = int(code_match.group(1))
                    key = KEYCODES[code_num] if code_num in KEYCODES else f"code:{code_num}"

                # Add icon for logging
                if ">>" in action:
                    log_action = action.split('>>')[0].strip()
                    action = f"{log_action} "


                # Cleanup action
                action = action.strip(',')
                action = action.replace(',', '')
                
                # Format the keybind description
                key_combo = f"{modifiers} + {key}" if modifiers else key
                
                keybind_info = {
                    'type': bind_type,
                    'submap': current_submap,
                    'key_combo': key_combo,
                    'action': action,
                    'line_num': line_num,
                    'raw_line': line
                }
                keybinds.append(keybind_info)
    return keybinds

def format_keybind_for_display(keybind):
    """Format a keybind for display in fzf or terminal."""
    submap_prefix = f"{SUBMAP_MAP.get(keybind['submap'], keybind['submap'].upper())} " if keybind['submap'] in SUBMAP_MAP else f"{keybind['submap'].upper()} "
    type_prefix = f"{TYPE_MAP.get(keybind['type'], 'Unknown')} " if keybind['type'] in TYPE_MAP else f"{keybind['type']} "
    
    # Clean up the action for display
    action = keybind['action']    
    return f"{submap_prefix:3}{type_prefix}{keybind['key_combo']:30} → {action}"

def run_fzf_search(keybinds):
    """Run fzf with the keybinds for interactive searching."""
    try:
        # Prepare the input for fzf
        fzf_input = []
        for keybind in keybinds:
            formatted = format_keybind_for_display(keybind)
            fzf_input.append(formatted)
        
        # Run fzf
        process = subprocess.Popen(
            ['fzf', '--reverse', '--border', '--preview-window=up:3', 
             '--header=Hyprland Keybinds - Press Enter or Esc to exit',
             ],
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True
        )
        
        stdout, stderr = process.communicate(input='\n'.join(fzf_input))
        
        if process.returncode == 0 and stdout.strip():
            # Find the selected keybind
            selected_line = stdout.strip()
            for keybind in keybinds:
                if format_keybind_for_display(keybind) == selected_line:
                    print(f"\nSelected Keybind Details:")
                    print(f"Key Combination: {keybind['key_combo']}")
                    print(f"Action: {keybind['action']}")
                    print(f"Type: {keybind['type']}")
                    print(f"Submap: {keybind['submap']}")
                    print(f"Line: {keybind['line_num']}")
                    print(f"Raw: {keybind['raw_line']}")
                    break
        
    except FileNotFoundError:
        print("Error: fzf is not installed. Please install fzf to use interactive search.")
        print("On most distributions: sudo pacman -S fzf  # or apt install fzf")
        return False
    except Exception as e:
        print(f"Error running fzf: {e}")
        return False
    
    return True


if __name__ == "__main__":
    # Parse command line arguments - default to fzf unless --no-fzf is specified
    use_fzf = not (len(sys.argv) > 1 and sys.argv[1] in ['--no-fzf', '-n', 'no-fzf'])
    
    # Read and parse keybinds
    raw_lines = read_keybinds()
    parsed_keybinds = parse_keybinds(raw_lines)
    
    if use_fzf:
        # Use fzf for interactive searching
        if not run_fzf_search(parsed_keybinds):
            # Fallback to regular display if fzf fails
            use_fzf = False
    
    if not use_fzf:
        print("Hyprland Keybinds Help:")
        print("=" * 50)
        print(f"Found {len(parsed_keybinds)} keybinds\n")
        
        # Group by submap for better organization
        submaps = {}
        for keybind in parsed_keybinds:
            submap = keybind['submap']
            if submap not in submaps:
                submaps[submap] = []
            submaps[submap].append(keybind)
        
        # Display keybinds grouped by submap
        for submap_name, submap_keybinds in submaps.items():
            if submap_name != 'main':
                print(f"\n[{submap_name.upper()} SUBMAP]")
                print("-" * 30)
            else:
                print("[MAIN KEYBINDS]")
                print("-" * 30)
            
            for keybind in submap_keybinds:
                print(format_keybind_for_display(keybind))
        
        print(f"\n{'-' * 50}")
        print("Usage: python help.py [--no-fzf] to disable interactive search")
        print("For more information, refer to the Hyprland documentation.")
        print("You can also customize your keybinds in the keybinds.conf file.")
        
        # Wait for user input before exiting
        input("\nPress Enter to exit...")