#!/usr/bin/env python3
# args_json: {"type":"object","required":["action"],"properties":{"action":{"type":"string","description":"list|focus|close|minimize|maximize|move|resize|desktops|desktop"},"title":{"type":"string","description":"Window title substring"},"x":{"type":"string","description":"X position (for move)"},"y":{"type":"string","description":"Y position (for move)"},"width":{"type":"string","description":"Width (for resize)"},"height":{"type":"string","description":"Height (for resize)"},"number":{"type":"string","description":"Desktop number (for desktop)"}}}
# ollie:prompt
# ## gui_windows
#
# KWin window management via D-Bus.
#
# **Args**: `[action, ...]`
# - Actions: `list`, `focus`, `close`, `minimize`, `maximize`, `move`, `resize`, `desktops`
#
# ```
# gui_windows(action="list")
# gui_windows(action="focus", title="Firefox")
# ```
# ollie:end
"""Manage KDE windows via KWin D-Bus scripting interface.

Commands:
  list                       - list all windows (title, class, geometry, desktop, state)
  focus <title_substr>       - raise and focus window matching title
  close <title_substr>       - close window matching title
  minimize <title_substr>    - minimize window
  maximize <title_substr>    - toggle maximize on window
  move <title_substr> <x> <y>       - move window to position
  resize <title_substr> <w> <h>     - resize window
  desktops                   - list virtual desktops
  desktop <number>           - switch to virtual desktop
  tile <left|right|title_substr>    - tile window to half-screen
"""
import sys
import subprocess
import json
import tempfile
import os
import time


def kwin_script(js_code):
    """Execute a KWin script and return output."""
    # Write script to temp file
    with tempfile.NamedTemporaryFile(mode='w', suffix='.js', delete=False) as f:
        f.write(js_code)
        script_path = f.name

    try:
        # Load the script
        load_result = subprocess.run([
            "gdbus", "call", "--session",
            "--dest", "org.kde.KWin",
            "--object-path", "/Scripting",
            "--method", "org.kde.kwin.Scripting.loadScript",
            script_path, "ollie_gui_windows"
        ], capture_output=True, text=True, timeout=5)

        if load_result.returncode != 0:
            print(f"ERROR: loadScript failed: {load_result.stderr}", file=sys.stderr)
            return None

        # Parse script ID from "(int32 N,)"
        script_id = load_result.stdout.strip().strip("()").split(",")[0].replace("int32 ", "")

        # Run the script
        run_result = subprocess.run([
            "gdbus", "call", "--session",
            "--dest", "org.kde.KWin",
            "--object-path", f"/Scripting/Script{script_id}",
            "--method", "org.kde.kwin.Script.run"
        ], capture_output=True, text=True, timeout=5)

        # Give script time to execute (async)
        time.sleep(0.3)

        # Read output from KWin's journal/stdout - scripts use print()
        # Unfortunately KWin script output goes to systemd journal, not easily capturable.
        # For listing we'll use wmctrl/qdbus instead.

        # Unload
        subprocess.run([
            "gdbus", "call", "--session",
            "--dest", "org.kde.KWin",
            "--object-path", "/Scripting",
            "--method", "org.kde.kwin.Scripting.unloadScript",
            "ollie_gui_windows"
        ], capture_output=True, text=True, timeout=5)

        return True
    finally:
        os.unlink(script_path)


def list_windows_wmctrl():
    """List windows using wmctrl (works on X11 and XWayland)."""
    try:
        result = subprocess.run(
            ["wmctrl", "-l", "-G", "-p"],
            capture_output=True, text=True, timeout=5
        )
        if result.returncode == 0:
            return result.stdout
    except FileNotFoundError:
        pass
    return None


def list_windows_qdbus():
    """List windows using KWin's D-Bus interface for Wayland."""
    # Use KWin scripting to dump window list to a known file
    out_file = "/tmp/ollie_winlist.txt"
    js = f"""
    var clients = workspace.windowList();
    var lines = [];
    for (var i = 0; i < clients.length; i++) {{
        var c = clients[i];
        if (c.skipTaskbar || c.specialWindow) continue;
        var geo = c.frameGeometry;
        var state = [];
        if (c.minimized) state.push('minimized');
        if (c.maximized) state.push('maximized');
        if (c.fullScreen) state.push('fullscreen');
        if (c.active) state.push('active');
        lines.push(
            c.caption + '\\t' +
            c.resourceClass + '\\t' +
            geo.x + ',' + geo.y + ',' + geo.width + ',' + geo.height + '\\t' +
            'desktop:' + c.desktop + '\\t' +
            state.join(',')
        );
    }}
    callDBus('org.ollie.SessionManager', '/org/ollie/SessionManager',
             'org.ollie.SessionManager', 'SetConfig',
             'winlist', lines.join('\\n'));
    """
    # Alternative: write to file via script. But KWin scripts are sandboxed.
    # Use qdbus to get window list if available
    try:
        result = subprocess.run(
            ["qdbus6", "org.kde.KWin", "/KWin", "org.kde.KWin.queryWindowInfo"],
            capture_output=True, text=True, timeout=5
        )
        if result.returncode == 0:
            return result.stdout
    except FileNotFoundError:
        try:
            result = subprocess.run(
                ["qdbus", "org.kde.KWin", "/KWin", "org.kde.KWin.queryWindowInfo"],
                capture_output=True, text=True, timeout=5
            )
            if result.returncode == 0:
                return result.stdout
        except FileNotFoundError:
            pass
    return None


def list_windows():
    """List windows using best available method."""
    # Try wmctrl first (most parseable)
    result = list_windows_wmctrl()
    if result:
        print("ID\tDESKTOP\tPID\tX\tY\tW\tH\tTITLE")
        for line in result.strip().split("\n"):
            parts = line.split(None, 7)
            if len(parts) >= 8:
                wid, desktop, pid, x, y, w, h = parts[0], parts[1], parts[2], parts[3], parts[4], parts[5], parts[6]
                title = parts[7] if len(parts) > 7 else ""
                # Strip hostname prefix from title
                if " " in title:
                    title = title.split(" ", 1)[1] if title.split(" ", 1)[0].isalpha() else title
                print(f"{wid}\t{desktop}\t{pid}\t{x}\t{y}\t{w}\t{h}\t{title}")
        return

    # Try xdotool
    try:
        result = subprocess.run(
            ["xdotool", "search", "--onlyvisible", "--name", ""],
            capture_output=True, text=True, timeout=5
        )
        if result.returncode == 0 and result.stdout.strip():
            print("ID\tTITLE")
            for wid in result.stdout.strip().split("\n"):
                name_result = subprocess.run(
                    ["xdotool", "getwindowname", wid],
                    capture_output=True, text=True, timeout=2
                )
                name = name_result.stdout.strip() if name_result.returncode == 0 else "?"
                print(f"{wid}\t{name}")
            return
    except FileNotFoundError:
        pass

    print("ERROR: no window listing tool available (install wmctrl or xdotool)", file=sys.stderr)
    sys.exit(1)


def find_window(title_substr):
    """Find window ID by title substring."""
    try:
        result = subprocess.run(
            ["wmctrl", "-l"],
            capture_output=True, text=True, timeout=5
        )
        if result.returncode == 0:
            for line in result.stdout.strip().split("\n"):
                if title_substr.lower() in line.lower():
                    return line.split()[0]
    except FileNotFoundError:
        pass

    try:
        result = subprocess.run(
            ["xdotool", "search", "--name", title_substr],
            capture_output=True, text=True, timeout=5
        )
        if result.returncode == 0 and result.stdout.strip():
            return result.stdout.strip().split("\n")[0]
    except FileNotFoundError:
        pass

    return None


def focus_window(title_substr):
    wid = find_window(title_substr)
    if not wid:
        print(f"ERROR: no window matching '{title_substr}'", file=sys.stderr)
        sys.exit(1)

    try:
        subprocess.run(["wmctrl", "-i", "-a", wid], check=True, timeout=5)
    except (FileNotFoundError, subprocess.CalledProcessError):
        try:
            subprocess.run(["xdotool", "windowactivate", wid], check=True, timeout=5)
        except (FileNotFoundError, subprocess.CalledProcessError) as e:
            print(f"ERROR: {e}", file=sys.stderr)
            sys.exit(1)
    print(f"focused: {wid}")


def close_window(title_substr):
    wid = find_window(title_substr)
    if not wid:
        print(f"ERROR: no window matching '{title_substr}'", file=sys.stderr)
        sys.exit(1)
    try:
        subprocess.run(["wmctrl", "-i", "-c", wid], check=True, timeout=5)
    except (FileNotFoundError, subprocess.CalledProcessError):
        try:
            subprocess.run(["xdotool", "windowclose", wid], check=True, timeout=5)
        except (FileNotFoundError, subprocess.CalledProcessError) as e:
            print(f"ERROR: {e}", file=sys.stderr)
            sys.exit(1)
    print(f"closed: {wid}")


def minimize_window(title_substr):
    wid = find_window(title_substr)
    if not wid:
        print(f"ERROR: no window matching '{title_substr}'", file=sys.stderr)
        sys.exit(1)
    try:
        subprocess.run(["xdotool", "windowminimize", wid], check=True, timeout=5)
    except (FileNotFoundError, subprocess.CalledProcessError):
        # KWin script fallback
        kwin_script(f"""
        var clients = workspace.windowList();
        for (var i = 0; i < clients.length; i++) {{
            if (clients[i].caption.toLowerCase().includes('{title_substr.lower()}')) {{
                clients[i].minimized = true;
                break;
            }}
        }}
        """)
    print(f"minimized: {wid}")


def maximize_window(title_substr):
    # KWin script is the clean way
    kwin_script(f"""
    var clients = workspace.windowList();
    for (var i = 0; i < clients.length; i++) {{
        if (clients[i].caption.toLowerCase().includes('{title_substr.lower()}')) {{
            clients[i].setMaximize(!clients[i].maximized, !clients[i].maximized);
            break;
        }}
    }}
    """)
    print(f"toggled maximize: {title_substr}")


def move_window(title_substr, x, y):
    wid = find_window(title_substr)
    if not wid:
        print(f"ERROR: no window matching '{title_substr}'", file=sys.stderr)
        sys.exit(1)
    try:
        subprocess.run(["wmctrl", "-i", "-r", wid, "-e", f"0,{x},{y},-1,-1"],
                       check=True, timeout=5)
    except (FileNotFoundError, subprocess.CalledProcessError):
        try:
            subprocess.run(["xdotool", "windowmove", wid, str(x), str(y)],
                           check=True, timeout=5)
        except (FileNotFoundError, subprocess.CalledProcessError) as e:
            print(f"ERROR: {e}", file=sys.stderr)
            sys.exit(1)
    print(f"moved {wid} to {x},{y}")


def resize_window(title_substr, w, h):
    wid = find_window(title_substr)
    if not wid:
        print(f"ERROR: no window matching '{title_substr}'", file=sys.stderr)
        sys.exit(1)
    try:
        subprocess.run(["wmctrl", "-i", "-r", wid, "-e", f"0,-1,-1,{w},{h}"],
                       check=True, timeout=5)
    except (FileNotFoundError, subprocess.CalledProcessError):
        try:
            subprocess.run(["xdotool", "windowsize", wid, str(w), str(h)],
                           check=True, timeout=5)
        except (FileNotFoundError, subprocess.CalledProcessError) as e:
            print(f"ERROR: {e}", file=sys.stderr)
            sys.exit(1)
    print(f"resized {wid} to {w}x{h}")


def list_desktops():
    try:
        result = subprocess.run(
            ["wmctrl", "-d"],
            capture_output=True, text=True, timeout=5
        )
        if result.returncode == 0:
            print(result.stdout.rstrip())
            return
    except FileNotFoundError:
        pass

    # Fallback via KWin D-Bus
    try:
        result = subprocess.run([
            "gdbus", "call", "--session",
            "--dest", "org.kde.KWin",
            "--object-path", "/VirtualDesktopManager",
            "--method", "org.freedesktop.DBus.Properties.Get",
            "org.kde.KWin.VirtualDesktopManager", "desktops"
        ], capture_output=True, text=True, timeout=5)
        if result.returncode == 0:
            print(result.stdout.rstrip())
            return
    except FileNotFoundError:
        pass
    print("ERROR: cannot list desktops", file=sys.stderr)
    sys.exit(1)


def switch_desktop(num):
    try:
        subprocess.run(["wmctrl", "-s", str(int(num) - 1)], check=True, timeout=5)
        print(f"switched to desktop {num}")
    except (FileNotFoundError, subprocess.CalledProcessError):
        try:
            subprocess.run([
                "gdbus", "call", "--session",
                "--dest", "org.kde.KWin",
                "--object-path", "/VirtualDesktopManager",
                "--method", "org.kde.KWin.VirtualDesktopManager.setCurrent",
                str(num)
            ], check=True, capture_output=True, timeout=5)
            print(f"switched to desktop {num}")
        except Exception as e:
            print(f"ERROR: {e}", file=sys.stderr)
            sys.exit(1)


def main():
    sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
    from _lib.args import parse_args as _parse
    args = _parse()
    cmd = args.require("action")

    if cmd == "list":
        list_windows()
    elif cmd == "focus":
        focus_window(args.require("title"))
    elif cmd == "close":
        close_window(args.require("title"))
    elif cmd == "minimize":
        minimize_window(args.require("title"))
    elif cmd == "maximize":
        maximize_window(args.require("title"))
    elif cmd == "move":
        move_window(args.require("title"), args.require("x"), args.require("y"))
    elif cmd == "resize":
        resize_window(args.require("title"), args.require("width"), args.require("height"))
    elif cmd == "desktops":
        list_desktops()
    elif cmd == "desktop":
        switch_desktop(args.require("number"))
    else:
        print(f"unknown command: {cmd}", file=sys.stderr)
        sys.exit(1)


if __name__ == "__main__":
    main()
