import QtQuick
import Quickshell
import Quickshell.I3
import Quickshell.Io
import Quickshell.Wayland
import qs.Commons
import qs.Services.Keyboard

Item {
  id: root

  // Configurable IPC command name (overridden to "scrollmsg" for Scroll)
  property string msgCommand: "swaymsg"

  // Properties that match the facade interface
  property ListModel workspaces: ListModel {}
  property var windows: []
  property int focusedWindowIndex: -1

  // Signals that match the facade interface
  signal workspaceChanged
  signal activeWindowChanged
  signal windowListChanged
  signal displayScalesChanged

  // I3-specific properties
  property bool initialized: false

  // Cache for window-to-workspace mapping
  property var windowWorkspaceMap: ({})

  // Track window usage counts per workspace to handle duplicates
  property var windowUsageCountsPerWorkspace: ({})

  // Debounce timer for updates
  Timer {
    id: updateTimer
    interval: 50
    repeat: false
    onTriggered: safeUpdate()
  }

  // Initialization
  function initialize() {
    if (initialized)
      return;
    try {
      I3.refreshWorkspaces();
      Qt.callLater(() => {
                     safeUpdateWorkspaces();
                     queryWindowWorkspaces();
                     queryDisplayScales();
                     queryKeyboardLayout();
                   });
      initialized = true;
      Logger.i("SwayService", "Service started");
    } catch (e) {
      Logger.e("SwayService", "Failed to initialize:", e);
    }
  }

  // Query window-to-workspace mapping via IPC
  function queryWindowWorkspaces() {
    swayTreeProcess.running = true;
  }

  // Sway tree process for getting window workspace information
  Process {
    id: swayTreeProcess
    running: false
    command: [msgCommand, "-t", "get_tree", "-r"]

    property string accumulatedOutput: ""

    stdout: SplitParser {
      onRead: function (line) {
        swayTreeProcess.accumulatedOutput += line;
      }
    }

    onExited: function (exitCode) {
      if (exitCode !== 0 || !accumulatedOutput) {
        Logger.e("SwayService", "Failed to query tree, exit code:", exitCode);
        accumulatedOutput = "";
        return;
      }

      try {
        const treeData = JSON.parse(accumulatedOutput);
        const newMap = {};
        const workspaceWindows = {}; // Track windows per workspace

        // Recursively find all windows and their workspaces
        function traverseTree(node, workspaceNum) {
          if (!node)
            return;

          // If this is a workspace node, update the workspace number
          if (node.type === "workspace" && node.num !== undefined) {
            workspaceNum = node.num;
            if (!workspaceWindows[workspaceNum]) {
              workspaceWindows[workspaceNum] = [];
            }
          }

          // If this is a regular or floating container with app_id/class (i.e., a window)
          if ((node.type === "con" || node.type === "floating_con") && (node.app_id || node.window_properties)) {
            const appId = node.app_id || (node.window_properties ? node.window_properties.class : null);
            const title = node.name || "";
            const id = node.id;

            if (appId && workspaceNum !== undefined && workspaceNum >= 0) {
              // Store window info for this workspace
              workspaceWindows[workspaceNum].push({
                                                    appId: appId,
                                                    title: title,
                                                    id: id
                                                  });
            }
          }

          // Traverse children
          if (node.nodes && node.nodes.length > 0) {
            for (const child of node.nodes) {
              traverseTree(child, workspaceNum);
            }
          }

          // Traverse floating nodes
          if (node.floating_nodes && node.floating_nodes.length > 0) {
            for (const child of node.floating_nodes) {
              traverseTree(child, workspaceNum);
            }
          }
        }

        traverseTree(treeData, -1);

        // Now build the map with workspace-specific keys
        for (const wsNum in workspaceWindows) {
          const windows = workspaceWindows[wsNum];
          const appTitleCounts = {}; // Count occurrences of each appId:title in this workspace

          for (const win of windows) {
            const baseKey = `${win.appId}:${win.title}`;

            // Track how many times we've seen this appId:title combo in this workspace
            if (!appTitleCounts[baseKey]) {
              appTitleCounts[baseKey] = 0;
            }
            const occurrence = appTitleCounts[baseKey];
            appTitleCounts[baseKey]++;

            // Create unique key with workspace and occurrence index
            const uniqueKey = `ws${wsNum}:${baseKey}[${occurrence}]`;
            newMap[uniqueKey] = parseInt(wsNum);

            // Also store by ID if available (most reliable)
            if (win.id) {
              newMap[`id:${win.id}`] = parseInt(wsNum);
            }
          }
        }

        windowWorkspaceMap = newMap;

        // Update windows with new workspace information
        Qt.callLater(safeUpdateWindows);
      } catch (e) {
        Logger.e("SwayService", "Failed to parse tree:", e);
      } finally {
        accumulatedOutput = "";
      }
    }
  }

  // Query display scales
  function queryDisplayScales() {
    swayOutputsProcess.running = true;
  }

  // Sway outputs process for display scale detection
  Process {
    id: swayOutputsProcess
    running: false
    command: [msgCommand, "-t", "get_outputs", "-r"]

    property string accumulatedOutput: ""

    stdout: SplitParser {
      onRead: function (line) {
        swayOutputsProcess.accumulatedOutput += line;
      }
    }

    onExited: function (exitCode) {
      if (exitCode !== 0 || !accumulatedOutput) {
        Logger.e("SwayService", "Failed to query outputs, exit code:", exitCode);
        accumulatedOutput = "";
        return;
      }

      try {
        const outputsData = JSON.parse(accumulatedOutput);
        const scales = {};

        for (const output of outputsData) {
          if (output.name) {
            scales[output.name] = {
              "name": output.name,
              "scale": output.scale || 1.0,
              "width": output.current_mode ? output.current_mode.width : 0,
              "height": output.current_mode ? output.current_mode.height : 0,
              "refresh_rate": output.current_mode ? output.current_mode.refresh : 0,
              "x": output.rect ? output.rect.x : 0,
              "y": output.rect ? output.rect.y : 0,
              "active": output.active || false,
              "focused": output.focused || false,
              "current_workspace": output.current_workspace || ""
            };
          }
        }

        // Notify CompositorService (it will emit displayScalesChanged)
        if (CompositorService && CompositorService.onDisplayScalesUpdated) {
          CompositorService.onDisplayScalesUpdated(scales);
        }
      } catch (e) {
        Logger.e("SwayService", "Failed to parse outputs:", e);
      } finally {
        // Clear accumulated output for next query
        accumulatedOutput = "";
      }
    }
  }

  function queryKeyboardLayout() {
    swayInputsProcess.running = true;
  }
  // Sway inputs process for keyboard layout detection
  Process {
    id: swayInputsProcess
    running: false
    command: [msgCommand, "-t", "get_inputs", "-r"]

    property string accumulatedOutput: ""

    stdout: SplitParser {
      onRead: function (line) {
        // Accumulate lines instead of parsing each one
        swayInputsProcess.accumulatedOutput += line;
      }
    }

    onExited: function (exitCode) {
      if (exitCode !== 0 || !accumulatedOutput) {
        Logger.e("SwayService", "Failed to query inputs, exit code:", exitCode);
        accumulatedOutput = "";
        return;
      }

      try {
        const inputsData = JSON.parse(accumulatedOutput);
        for (const input of inputsData) {
          if (input.type == "keyboard") {
            const layoutName = input.xkb_active_layout_name;
            KeyboardLayoutService.setCurrentLayout(layoutName);
            Logger.d("SwayService", "Keyboard layout switched:", layoutName);
            break;
          }
        }
      } catch (e) {
        Logger.e("SwayService", "Failed to parse inputs:", e);
      } finally {
        // Clear accumulated output for next query
        accumulatedOutput = "";
      }
    }
  }

  // Safe update wrapper
  function safeUpdate() {
    queryWindowWorkspaces();
    safeUpdateWorkspaces();
  }

  // Safe workspace update
  function safeUpdateWorkspaces() {
    try {
      workspaces.clear();

      if (!I3.workspaces || !I3.workspaces.values) {
        return;
      }

      const hlWorkspaces = I3.workspaces.values;

      for (var i = 0; i < hlWorkspaces.length; i++) {
        const ws = hlWorkspaces[i];
        if (!ws || ws.id < 1)
          continue;
        const wsData = {
          "id": i,
          "idx": ws.num,
          "name": ws.name || "",
          "output": (ws.monitor && ws.monitor.name) ? ws.monitor.name : "",
          "isActive": ws.active === true,
          "isFocused": ws.focused === true,
          "isUrgent": ws.urgent === true,
          "isOccupied": true,
          "handle": ws
        };

        workspaces.append(wsData);
      }
    } catch (e) {
      Logger.e("SwayService", "Error updating workspaces:", e);
    }
  }

  // Safe window update
  function safeUpdateWindows() {
    try {
      const windowsList = [];

      // Reset usage counts per workspace before processing windows
      windowUsageCountsPerWorkspace = {};

      if (!ToplevelManager.toplevels || !ToplevelManager.toplevels.values) {
        windows = [];
        focusedWindowIndex = -1;
        windowListChanged();
        return;
      }

      const hlToplevels = ToplevelManager.toplevels.values;
      let newFocusedIndex = -1;

      for (var i = 0; i < hlToplevels.length; i++) {
        const toplevel = hlToplevels[i];
        if (!toplevel)
          continue;
        const windowData = extractWindowData(toplevel);
        if (windowData) {
          windowsList.push(windowData);

          if (windowData.isFocused) {
            newFocusedIndex = windowsList.length - 1;
          }
        }
      }

      windows = windowsList;

      if (newFocusedIndex !== focusedWindowIndex) {
        focusedWindowIndex = newFocusedIndex;
        activeWindowChanged();
      }

      windowListChanged();
    } catch (e) {
      Logger.e("SwayService", "Error updating windows:", e);
    }
  }

  // Extract window data safely from a toplevel
  function extractWindowData(toplevel) {
    if (!toplevel)
      return null;

    try {
      // Safely extract properties
      const appId = getAppId(toplevel);
      const title = safeGetProperty(toplevel, "title", "");
      const focused = toplevel.activated === true;

      // Try to find workspace ID from our cached map by trying all workspaces
      let workspaceId = -1;
      let foundWorkspaceNum = -1;

      // Build base key for this window
      const baseKey = `${appId}:${title}`;

      // Try to find this window in any workspace
      for (var i = 0; i < workspaces.count; i++) {
        const ws = workspaces.get(i);
        if (!ws)
          continue;

        const wsNum = ws.idx;

        // Initialize usage count for this workspace if needed
        if (!windowUsageCountsPerWorkspace[wsNum]) {
          windowUsageCountsPerWorkspace[wsNum] = {};
        }

        // Get current usage count for this appId:title in this workspace
        if (!windowUsageCountsPerWorkspace[wsNum][baseKey]) {
          windowUsageCountsPerWorkspace[wsNum][baseKey] = 0;
        }

        const occurrence = windowUsageCountsPerWorkspace[wsNum][baseKey];
        const uniqueKey = `ws${wsNum}:${baseKey}[${occurrence}]`;

        // Check if this key exists in our map
        if (windowWorkspaceMap[uniqueKey] !== undefined) {
          foundWorkspaceNum = windowWorkspaceMap[uniqueKey];
          workspaceId = ws.id;

          // Increment the usage count for this workspace
          windowUsageCountsPerWorkspace[wsNum][baseKey]++;
          break;
        }
      }

      return {
        "title": title,
        "appId": appId,
        "isFocused": focused,
        "workspaceId": workspaceId,
        "handle": toplevel
      };
    } catch (e) {
      return null;
    }
  }

  function getAppId(toplevel) {
    if (!toplevel)
      return "";

    return toplevel.appId;
  }

  // Safe property getter
  function safeGetProperty(obj, prop, defaultValue) {
    try {
      const value = obj[prop];
      if (value !== undefined && value !== null) {
        return String(value);
      }
    } catch (e)

      // Property access failed
    {}
    return defaultValue;
  }

  function handleInputEvent(ev) {
    try {
      const eventData = JSON.parse(ev);
      if (eventData.change == "xkb_layout" && eventData.input != null) {
        const input = eventData.input;
        if (input.type == "keyboard" && input.xkb_active_layout_name != null) {
          const layoutName = input.xkb_active_layout_name;
          KeyboardLayoutService.setCurrentLayout(layoutName);
          Logger.d("SwayService", "Keyboard layout switched:", layoutName);
        }
      }
    } catch (e) {
      Logger.e("SwayService", "Error handling input event:", e);
    }
  }

  // Connections to I3
  Connections {
    target: I3.workspaces
    enabled: initialized
    function onValuesChanged() {
      safeUpdateWorkspaces();
      workspaceChanged();
    }
  }

  Connections {
    target: ToplevelManager
    enabled: initialized
    function onActiveToplevelChanged() {
      updateTimer.restart();
    }
  }

  // Some programs change title of window dependent on content
  Connections {
    target: ToplevelManager ? ToplevelManager.activeToplevel : null
    enabled: initialized
    function onTitleChanged() {
      updateTimer.restart();
    }
  }

  Connections {
    target: I3
    enabled: initialized
    function onRawEvent(event) {
      safeUpdateWorkspaces();
      workspaceChanged();
      updateTimer.restart();

      if (event.type === "output") {
        Qt.callLater(queryDisplayScales);
      }
    }
  }

  I3IpcListener {
    subscriptions: ["input"]
    onIpcEvent: function (event) {
      handleInputEvent(event.data);
    }
  }

  // Public functions
  function switchToWorkspace(workspace) {
    try {
      workspace.handle.activate();
    } catch (e) {
      Logger.e("SwayService", "Failed to switch workspace:", e);
    }
  }

  function focusWindow(window) {
    try {
      window.handle.activate();
    } catch (e) {
      Logger.e("SwayService", "Failed to switch window:", e);
    }
  }

  function closeWindow(window) {
    try {
      window.handle.close();
    } catch (e) {
      Logger.e("SwayService", "Failed to close window:", e);
    }
  }

  function turnOffMonitors() {
    try {
      Quickshell.execDetached([msgCommand, "output", "*", "dpms", "off"]);
    } catch (e) {
      Logger.e("SwayService", "Failed to turn off monitors:", e);
    }
  }

  function turnOnMonitors() {
    try {
      Quickshell.execDetached([msgCommand, "output", "*", "dpms", "on"]);
    } catch (e) {
      Logger.e("SwayService", "Failed to turn on monitors:", e);
    }
  }

  function logout() {
    try {
      Quickshell.execDetached([msgCommand, "exit"]);
    } catch (e) {
      Logger.e("SwayService", "Failed to logout:", e);
    }
  }

  function cycleKeyboardLayout() {
    try {
      Quickshell.execDetached([msgCommand, "input", "type:keyboard", "xkb_switch_layout", "next"]);
    } catch (e) {
      Logger.e("SwayService", "Failed to cycle keyboard layout:", e);
    }
  }

  function getFocusedScreen() {
    // de-activated until proper testing
    return null;

    // const i3Mon = I3.focusedMonitor;
    // if (i3Mon) {
    //   const monitorName = i3Mon.name;
    //   for (let i = 0; i < Quickshell.screens.length; i++) {
    //     if (Quickshell.screens[i].name === monitorName) {
    //       return Quickshell.screens[i];
    //     }
    //   }
    // }
    // return null;
  }

  function spawn(command) {
    try {
      Quickshell.execDetached([msgCommand, "exec", "--"].concat(command));
    } catch (e) {
      Logger.e("SwayService", "Failed to spawn command:", e);
    }
  }
}
