import QtQuick
import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Services.Keyboard
import qs.Services.UI

// MangoService integrates with MangoWC compositor using mmsg IPC commands
// for real-time window management, workspace control, and state monitoring
Item {
  id: root

  // Facade interface properties
  property ListModel workspaces: ListModel {}
  property var windows: []
  property int focusedWindowIndex: -1

  // Facade interface signals
  signal workspaceChanged
  signal activeWindowChanged
  signal windowListChanged
  signal displayScalesChanged

  // MangoWC-specific state
  property bool initialized: false
  property bool overviewActive: false
  property var workspaceCache: ({}) // Cache for workspace data to detect changes
  property var windowCache: ({}) // Cache for window data to detect changes
  property var monitorCache: ({}) // Cache for monitor/scale data
  property string currentLayout: "" // Current layout name
  property string currentLayoutSymbol: "" // Current layout symbol (e.g., 'S' for scroller)
  property string currentKeyboardLayout: "" // Current keyboard layout name
  property string selectedMonitor: "" // Currently selected/focused monitor

  // mmsg command templates for MangoWC IPC (mmsg is the MangoWC message interface)
  readonly property var mmsgCommands: ({
                                         "query": {
                                           "workspaces": ["mmsg", "-g", "-t"],
                                           "windows": ["mmsg", "-g", "-c"],
                                           "layout": ["mmsg", "-g", "-l"],
                                           "keyboard": ["mmsg", "-g", "-k"],
                                           "outputs": ["mmsg", "-g", "-A"],
                                           "monitors": ["mmsg", "-g", "-o"],
                                           "eventStream": ["mmsg", "-w"]
                                         },
                                         "action": {
                                           "view": ["mmsg", "-s", "-d", "view"],
                                           "tag": ["mmsg", "-s", "-t"],
                                           "focusMaster": ["mmsg", "-s", "-d", "focusmaster"],
                                           "killClient": ["mmsg", "-s", "-d", "killclient"],
                                           "toggleOverview": ["mmsg", "-s", "-d", "toggleoverview"],
                                           "setLayout": ["mmsg", "-s", "-d", "setlayout"],
                                           "quit": ["mmsg", "-s", "-q"]
                                         }
                                       })

  readonly property string overviewLayoutSymbol: "󰃇" // Symbol representing overview layout
  readonly property int defaultWorkspaceId: 1 // Default workspace ID when none specified

  // Debounce timer for rapid state changes to avoid excessive updates
  Timer {
    id: updateTimer
    interval: 50
    repeat: false
    onTriggered: safeUpdate()
  }

  // Event stream process for real-time MangoWC state monitoring using mmsg -w
  // Monitors events: workspace changes, window focus/movement, layout changes, monitor selection
  Process {
    id: eventStream
    running: false
    command: mmsgCommands.query.eventStream

    stdout: SplitParser {
      onRead: function (line) {
        try {
          handleEvent(line.trim());
        } catch (e) {
          Logger.e("MangoService", "Event parsing error:", e, line);
        }
      }
    }

    onExited: function (exitCode) {
      if (exitCode !== 0) {
        Logger.e("MangoService", "Event stream exited, restarting...");
        restartTimer.start();
      }
    }
  }

  // Restart timer for event stream recovery on failure
  Timer {
    id: restartTimer
    interval: 1000
    onTriggered: {
      if (initialized) {
        eventStream.running = true;
      }
    }
  }

  // Process to query workspaces using mmsg -g -t
  Process {
    id: workspacesProcess
    running: false
    command: mmsgCommands.query.workspaces
    property string accumulatedOutput: ""

    stdout: SplitParser {
      onRead: function (line) {
        workspacesProcess.accumulatedOutput += line + "\n";
      }
    }

    onExited: function (exitCode) {
      if (exitCode === 0) {
        parseWorkspaces(accumulatedOutput);
      } else {
        Logger.e("MangoService", "Workspaces query failed:", exitCode);
      }
      accumulatedOutput = "";
    }
  }

  // Process to query windows using mmsg -g -c
  Process {
    id: windowsProcess
    running: false
    command: mmsgCommands.query.windows
    property string accumulatedOutput: ""
    property var currentWindow: ({})

    onRunningChanged: {
      if (running) {
        windowsProcess.currentWindow = {};
      }
    }

    stdout: SplitParser {
      onRead: function (line) {
        const trimmed = line.trim();
        if (!trimmed)
          return;
        const parts = trimmed.split(' ');
        if (parts.length >= 3) {
          const outputName = parts[0];
          const property = parts[1];
          const value = parts.slice(2).join(' ');

          if (!windowsProcess.currentWindow[outputName]) {
            windowsProcess.currentWindow[outputName] = {
              "id": outputName,
              "output": outputName
            };
          }

          switch (property) {
          case "title":
            windowsProcess.currentWindow[outputName].title = value;
            break;
          case "appid":
            windowsProcess.currentWindow[outputName].appId = value;
            windowsProcess.currentWindow[outputName].class = value;
            break;
          case "fullscreen":
            windowsProcess.currentWindow[outputName].fullscreen = (value === "1");
            break;
          case "floating":
            windowsProcess.currentWindow[outputName].floating = (value === "1");
            break;
          case "x":
            windowsProcess.currentWindow[outputName].x = parseInt(value);
            break;
          case "y":
            windowsProcess.currentWindow[outputName].y = parseInt(value);
            break;
          case "width":
            windowsProcess.currentWindow[outputName].width = parseInt(value);
            break;
          case "height":
            windowsProcess.currentWindow[outputName].height = parseInt(value);
            break;
          }
        }
      }
    }

    onExited: function (exitCode) {
      if (exitCode === 0) {
        parseWindows(windowsProcess.currentWindow);
      } else {
        Logger.e("MangoService", "Windows query failed:", exitCode);
      }
      accumulatedOutput = "";
      windowsProcess.currentWindow = {};
    }
  }

  // Process to query current layout using mmsg -g -l
  Process {
    id: layoutProcess
    running: false
    command: mmsgCommands.query.layout

    stdout: SplitParser {
      onRead: function (line) {
        try {
          const parts = line.trim().split(/\s+/);
          if (parts.length >= 2) {
            const layoutSymbol = parts.slice(1).join(' ');
            handleLayoutChange(layoutSymbol);
          }
        } catch (e) {
          Logger.e("MangoService", "Layout parsing error:", e, line);
        }
      }
    }

    onExited: function (exitCode) {
      if (exitCode !== 0) {
        Logger.e("MangoService", "Layout query failed:", exitCode);
      }
    }
  }

  // Process to query keyboard layout using mmsg -g -k
  Process {
    id: keyboardProcess
    running: false
    command: mmsgCommands.query.keyboard

    stdout: SplitParser {
      onRead: function (line) {
        try {
          const parts = line.trim().split(/\s+/);
          if (parts.length >= 2 && parts[1] === "kb_layout") {
            const layoutName = parts.slice(2).join(' ');
            if (layoutName && layoutName !== currentKeyboardLayout) {
              currentKeyboardLayout = layoutName;
              KeyboardLayoutService.setCurrentLayout(layoutName);
            }
          }
        } catch (e) {
          Logger.e("MangoService", "Keyboard layout parsing error:", e, line);
        }
      }
    }

    onExited: function (exitCode) {
      if (exitCode !== 0) {
        Logger.e("MangoService", "Keyboard query failed:", exitCode);
      }
    }
  }

  // Process to query output scales using mmsg -g -A
  Process {
    id: outputsProcess
    running: false
    command: mmsgCommands.query.outputs

    stdout: SplitParser {
      onRead: function (line) {
        try {
          const parts = line.trim().split(/\s+/);
          if (parts.length >= 3 && parts[1] === "scale_factor") {
            const outputName = parts[0];
            const scaleFactor = parseFloat(parts[2]);

            if (!monitorCache[outputName]) {
              monitorCache[outputName] = {};
            }

            monitorCache[outputName].scale = scaleFactor;
            monitorCache[outputName].name = outputName;
          }
        } catch (e) {
          Logger.e("MangoService", "Output parsing error:", e, line);
        }
      }
    }

    onExited: function (exitCode) {
      if (exitCode === 0) {
        updateDisplayScales();
      } else {
        Logger.e("MangoService", "Outputs query failed:", exitCode);
      }
    }
  }

  // Process to query monitor states using mmsg -g -o
  Process {
    id: monitorStateProcess
    running: false
    command: mmsgCommands.query.monitors

    stdout: SplitParser {
      onRead: function (line) {
        try {
          const parts = line.trim().split(/\s+/);
          if (parts.length >= 3 && parts[1] === "selmon") {
            const outputName = parts[0];
            const isSelected = parts[2] === "1";
            if (isSelected) {
              selectedMonitor = outputName;
              Logger.d("MangoService", `Initial selected monitor: ${outputName}`);
            }
          }
        } catch (e) {
          Logger.e("MangoService", "Monitor state parsing error:", e, line);
        }
      }
    }

    onExited: function (exitCode) {
      if (exitCode !== 0) {
        Logger.e("MangoService", "Monitor state query failed:", exitCode);
      }
    }
  }

  // Process to enumerate available outputs using mmsg -g -O
  Process {
    id: outputEnumProcess
    running: false
    command: ["mmsg", "-g", "-O"]

    stdout: SplitParser {
      onRead: function (line) {
        try {
          const trimmed = line.trim();

          const outputName = trimmed.replace(/^\+\s*/, '');
          if (outputName && !monitorCache[outputName]) {
            monitorCache[outputName] = {
              "name": outputName,
              "scale": 1.0,
              "active": false,
              "focused": false
            };
          }
        } catch (e) {
          Logger.e("MangoService", "Output enumeration error:", e, line);
        }
      }
    }

    onExited: function (exitCode) {
      if (exitCode !== 0) {
        Logger.e("MangoService", "Output enumeration failed:", exitCode);
      }
    }
  }

  // Initialize MangoService and establish connection to MangoWC
  function initialize() {
    if (initialized) {
      Logger.w("MangoService", "Already initialized");
      return;
    }

    try {
      Logger.i("MangoService", "Service started");

      queryOutputEnum();
      queryMonitorState();
      eventStream.running = true;
      queryWorkspaces();
      queryWindows();
      queryLayout();
      queryKeyboard();
      queryOutputs();

      initialized = true;
      Logger.i("MangoService", "Service initialized successfully");
    } catch (e) {
      Logger.e("MangoService", "Initialization failed:", e);
      eventStream.running = true;
    }
  }

  // Switch to a specific workspace/tag
  function switchToWorkspace(workspace) {
    try {
      const tagId = workspace.idx || workspace.id || defaultWorkspaceId;
      const outputName = workspace.output || selectedMonitor || "";
      let command = mmsgCommands.action.tag.slice();

      // Only add -o parameter for multi-monitor setups
      if (outputName && Object.keys(monitorCache).length > 1) {
        command.push("-o", outputName);
      }
      command.push(tagId.toString());

      Quickshell.execDetached(command);
    } catch (e) {
      Logger.e("MangoService", "Failed to switch workspace:", e);
    }
  }

  // Focus a specific window on its workspace
  function focusWindow(window) {
    try {
      if (window && window.output) {
        let command = mmsgCommands.action.view.slice();
        const isMultiMonitor = Object.keys(monitorCache).length > 1;

        if (isMultiMonitor) {
          command.push("-o", window.output);
        }
        command.push(window.workspaceId.toString());
        Quickshell.execDetached(command);

        Qt.callLater(() => {
                       let focusCommand = mmsgCommands.action.focusMaster.slice();
                       if (isMultiMonitor) {
                         focusCommand.push("-o", window.output);
                       }
                       Quickshell.execDetached(focusCommand);
                     });
      }
    } catch (e) {
      Logger.e("MangoService", "Failed to focus window:", e);
    }
  }

  function closeWindow(window) {
    try {
      const command = mmsgCommands.action.killClient.slice();
      if (selectedMonitor && Object.keys(monitorCache).length > 1) {
        command.push("-o", selectedMonitor);
      }
      Quickshell.execDetached(command);
    } catch (e) {
      Logger.e("MangoService", "Failed to close window:", e);
    }
  }

  function toggleOverview() {
    try {
      const command = mmsgCommands.action.toggleOverview.slice();
      if (selectedMonitor && Object.keys(monitorCache).length > 1) {
        command.push("-o", selectedMonitor);
      }
      Quickshell.execDetached(command);
    } catch (e) {
      Logger.e("MangoService", "Failed to toggle overview:", e);
    }
  }

  function setLayout(layoutName) {
    try {
      const command = mmsgCommands.action.setLayout.slice();
      command.push(layoutName);
      Quickshell.execDetached(command);
    } catch (e) {
      Logger.e("MangoService", "Failed to set layout:", e);
    }
  }

  function logout() {
    try {
      Quickshell.execDetached(mmsgCommands.action.quit);
    } catch (e) {
      Logger.e("MangoService", "Failed to logout:", e);
    }
  }

  // Parse workspace data from mmsg -g -t output
  // Handles formats: tag details, tag masks, and binary states
  // State bits: bit 0 = active/selected, bit 1 = urgent
  function parseWorkspaces(output) {
    const lines = output.trim().split('\n');
    const workspacesList = [];
    const newWorkspaceCache = {};
    let outputClients = {};

    for (const line of lines) {
      const trimmed = line.trim();
      if (!trimmed)
        continue;
      const tagMatch = trimmed.match(/^(\S+)\s+tag\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)$/);
      if (tagMatch) {
        const outputName = tagMatch[1];
        const tagNum = tagMatch[2];
        const state = tagMatch[3];
        const clients = tagMatch[4];
        const focused = tagMatch[5];
        const tagId = parseInt(tagNum);

        const isActive = (parseInt(state) & 1) !== 0;
        const isUrgent = (parseInt(state) & 2) !== 0;
        const isOccupied = parseInt(clients) > 0;
        const isFocused = isActive && parseInt(focused) === 1;

        if (!outputClients[outputName]) {
          outputClients[outputName] = 0;
        }

        const workspaceData = {
          "id": tagId,
          "idx": tagId,
          "name": tagId.toString(),
          "output": outputName,
          "isActive": isActive,
          "isFocused": isFocused || (isActive && (outputName === selectedMonitor)),
          "isUrgent": isUrgent,
          "isOccupied": isOccupied,
          "clients": parseInt(clients)
        };

        newWorkspaceCache[`${outputName}-${tagId}`] = workspaceData;
        workspacesList.push(workspaceData);
      }

      const clientsMatch = trimmed.match(/^(\S+)\s+clients\s+(\d+)$/);
      if (clientsMatch) {
        const outputName = clientsMatch[1];
        const clientCount = clientsMatch[2];
        outputClients[outputName] = parseInt(clientCount);
      }

      const tagsMatch = trimmed.match(/^(\S+)\s+tags\s+(\d+)\s+(\d+)\s+(\d+)$/);
      if (tagsMatch) {
        const outputName = tagsMatch[1];
        const occ = tagsMatch[2];
        const seltags = tagsMatch[3];
        const urg = tagsMatch[4];

        const occBits = occ.padStart(9, '0');
        const selBits = seltags.padStart(9, '0');
        const urgBits = urg.padStart(9, '0');

        for (var i = 0; i < 9; i++) {
          const tagId = i + 1;
          const isActive = selBits[8 - i] === '1';
          const isUrgent = urgBits[8 - i] === '1';
          const isOccupied = occBits[8 - i] === '1';

          const workspaceData = {
            "id": tagId,
            "idx": tagId,
            "name": tagId.toString(),
            "output": outputName,
            "isActive": isActive,
            "isFocused": false,
            "isUrgent"// Will be determined by selected monitor
            : isUrgent,
            "isOccupied": isOccupied,
            "clients": 0 // Will be updated by tag-specific data
          };

          const key = `${outputName}-${tagId}`;
          if (!newWorkspaceCache[key]) {
            newWorkspaceCache[key] = workspaceData;
            workspacesList.push(workspaceData);
          }
        }
      }

      const layoutMatch = trimmed.match(/^(\S+)\s+layout\s+(\S+)$/);
      if (layoutMatch) {
        const layoutSymbol = layoutMatch[2];
        handleLayoutChange(layoutSymbol);
      }
    }

    if (JSON.stringify(newWorkspaceCache) !== JSON.stringify(workspaceCache)) {
      workspaceCache = newWorkspaceCache;

      workspacesList.sort((a, b) => {
                            if (a.id !== b.id)
                            return a.id - b.id;
                            return a.output.localeCompare(b.output);
                          });

      workspaces.clear();
      for (var i = 0; i < workspacesList.length; i++) {
        workspaces.append(workspacesList[i]);
      }

      workspaceChanged();
    }
  }

  // Parse window data from mmsg -g -c output into window list
  function parseWindows(windowData) {
    const windowsList = [];
    const newWindowCache = {};
    let newFocusedIndex = -1;

    const windowEntries = Object.entries(windowData);
    for (var i = 0; i < windowEntries.length; i++) {
      const outputName = windowEntries[i][0];
      const data = windowEntries[i][1];
      if (data.title || data.appId) {
        const isFocused = (outputName === selectedMonitor);

        let activeTagId = defaultWorkspaceId;
        const workspaceEntries = Object.entries(workspaceCache);
        for (var j = 0; j < workspaceEntries.length; j++) {
          const key = workspaceEntries[j][0];
          const tagData = workspaceEntries[j][1];
          if (tagData.output === outputName && tagData.isActive) {
            activeTagId = tagData.id;
            break;
          }
        }

        const windowInfo = {
          "id": `${outputName}-${data.appId || 'unknown'}`,
          "title": data.title || "",
          "appId": data.appId || "",
          "class": data.appId || "",
          "workspaceId": activeTagId,
          "isFocused": isFocused,
          "output": outputName,
          "fullscreen": data.fullscreen || false,
          "floating": data.floating || false,
          "x": data.x || 0,
          "y": data.y || 0,
          "width": data.width || 0,
          "height": data.height || 0,
          "geometry": {
            "x": data.x || 0,
            "y": data.y || 0,
            "width": data.width || 0,
            "height": data.height || 0
          }
        };

        windowsList.push(windowInfo);
        newWindowCache[windowInfo.id] = windowInfo;

        if (isFocused) {
          newFocusedIndex = windowsList.length - 1;
          Logger.d("MangoService", `Focused window detected: ${data.title} on ${outputName}`);
        }
      }
    }

    if (JSON.stringify(newWindowCache) !== JSON.stringify(windowCache)) {
      windowCache = newWindowCache;
      windows = windowsList;

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

      windowListChanged();
    }
  }

  // Handle layout change events and update overview state
  function handleLayoutChange(layoutSymbol) {
    const wasOverview = overviewActive;
    const isOverview = (layoutSymbol === overviewLayoutSymbol);

    if (wasOverview !== isOverview) {
      overviewActive = isOverview;
      Logger.d("MangoService", `Overview mode: ${overviewActive}`);
    }

    if (layoutSymbol !== currentLayoutSymbol) {
      currentLayoutSymbol = layoutSymbol;
      currentLayout = layoutSymbol;
    }
  }

  // Update display scales and notify CompositorService
  function updateDisplayScales() {
    const scales = {};
    const monitorEntries = Object.entries(monitorCache);
    for (var i = 0; i < monitorEntries.length; i++) {
      const outputName = monitorEntries[i][0];
      const data = monitorEntries[i][1];
      scales[outputName] = {
        "name": data.name || outputName,
        "scale": data.scale || 1.0,
        "width": data.width || 0,
        "height": data.height || 0,
        "refresh_rate": data.refresh_rate || 0,
        "x": data.x || 0,
        "y": data.y || 0,
        "active": data.active || false,
        "focused": data.focused || false
      };
    }

    if (CompositorService && CompositorService.onDisplayScalesUpdated) {
      CompositorService.onDisplayScalesUpdated(scales);
    }
    displayScalesChanged();
  }

  // Handle real-time events from mmsg -w event stream and trigger updates
  function handleEvent(eventLine) {
    const parts = eventLine.trim().split(/\s+/);
    if (parts.length < 2)
      return;
    const eventType = parts[1];

    switch (eventType) {
    case "selmon":
      if (parts.length >= 3) {
        const monitorName = parts[0];
        const isSelected = parts[2] === "1";
        if (isSelected) {
          selectedMonitor = monitorName;
          Logger.d("MangoService", `Selected monitor changed to: ${monitorName}`);
        }
      }
      updateTimer.restart();
      break;
    case "tag":
    case "title":
    case "appid":
    case "fullscreen":
    case "floating":
    case "layout":
    case "kb_layout":
    case "scale_factor":
    case "toggle":
    case "last_layer":
    case "keymode":
    case "clients":
    case "tags":
      updateTimer.restart();
      break;
    }
  }

  // Start workspace query process
  function queryWorkspaces() {
    workspacesProcess.running = true;
  }

  // Start window query process
  function queryWindows() {
    windowsProcess.running = true;
  }

  // Start layout query process
  function queryLayout() {
    layoutProcess.running = true;
  }

  // Start keyboard layout query process
  function queryKeyboard() {
    keyboardProcess.running = true;
  }

  // Start output scales query process
  function queryOutputs() {
    outputsProcess.running = true;
  }

  // Query display scales (alias for queryOutputs)
  function queryDisplayScales() {
    queryOutputs();
  }

  // Start output enumeration process
  function queryOutputEnum() {
    outputEnumProcess.running = true;
  }

  // Start monitor state query process
  function queryMonitorState() {
    monitorStateProcess.running = true;
  }

  // Safely update all state by querying workspaces, windows, and monitor state
  function safeUpdate() {
    try {
      queryWorkspaces();
      queryWindows();
      queryMonitorState();
    } catch (e) {
      Logger.e("MangoService", "Safe update failed:", e);
    }
  }

  // Get the ID of the currently active workspace/tag
  function getCurrentActiveTagId() {
    const workspaceEntries1 = Object.entries(workspaceCache);
    for (var i = 0; i < workspaceEntries1.length; i++) {
      const key = workspaceEntries1[i][0];
      const tagData = workspaceEntries1[i][1];
      if (tagData.isActive && tagData.output === selectedMonitor) {
        return tagData.id;
      }
    }

    const workspaceEntries2 = Object.entries(workspaceCache);
    for (var i = 0; i < workspaceEntries2.length; i++) {
      const key = workspaceEntries2[i][0];
      const tagData = workspaceEntries2[i][1];
      if (tagData.isActive) {
        return tagData.id;
      }
    }
    return defaultWorkspaceId;
  }
}
