import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Commons
import qs.Modules.Panels.Settings
import qs.Services.UI
import qs.Widgets

PopupWindow {
  id: root

  property var toplevel: null
  property var appData: null
  property Item anchorItem: null
  property ShellScreen targetScreen: null

  property string menuMode: "app" // "app" or "launcher"
  property string launcherWidgetSection: ""
  property int launcherWidgetIndex: -1
  property var launcherWidgetSettings: ({})

  property bool hovered: menuHoverHandler.hovered
  property var onAppClosed: null // Callback function for when an app is closed
  property bool canAutoClose: false

  // Track which menu item is hovered
  property int hoveredItem: -1 // -1: none, otherwise the index of the item in `items`

  property var items: []

  signal requestClose

  property real menuContentWidth: 160
  property real menuMinWidth: 120
  property real menuMaxWidth: 360
  property real menuMaxHeight: Math.max(180, Math.min(420, Math.round((targetScreen ? targetScreen.height : 600) * 0.3)))
  property int separatorCompactHeight: Style.borderS + Style.margin2S
  property string forcedGroupMenuMode: ""
  readonly property int separatorIndex: {
    for (let i = 0; i < root.items.length; i++) {
      if (root.items[i] && root.items[i].separator === true)
        return i;
    }
    return -1;
  }
  readonly property bool splitExtendedLayout: separatorIndex >= 0
  readonly property var scrollItems: splitExtendedLayout ? root.items.slice(0, separatorIndex) : root.items
  readonly property var fixedItems: splitExtendedLayout ? root.items.slice(separatorIndex + 1) : []
  readonly property real menuInnerHeight: Math.max(0, implicitHeight - Style.margin2M)
  readonly property real fixedActionsHeight: listHeight(fixedItems)
  readonly property real separatorBlockHeight: splitExtendedLayout ? separatorCompactHeight : 0
  readonly property real scrollAreaHeight: splitExtendedLayout ? Math.max(0, menuInnerHeight - fixedActionsHeight - separatorBlockHeight) : menuInnerHeight
  readonly property bool listOverflowing: menuFlick && menuFlick.contentHeight > menuFlick.height
  readonly property real menuBodyHeight: {
    if (splitExtendedLayout) {
      return listHeight(scrollItems) + separatorBlockHeight + fixedActionsHeight;
    }
    return listHeight(root.items);
  }

  implicitWidth: menuContentWidth + Style.margin2M
  implicitHeight: Math.min(menuBodyHeight + Style.margin2M, menuMaxHeight)
  color: "transparent"
  visible: false

  // Hidden text element for measuring text width
  NText {
    id: textMeasure
    visible: false
    pointSize: Style.fontSizeS
    family: "Sans Serif" // Match your NText font if different
    wrapMode: Text.NoWrap
    elide: Text.ElideNone
  }

  // Calculate the maximum width needed for all menu items
  function calculateMenuWidth() {
    let maxWidth = 0; // Start with 0, we'll apply minimum later
    if (root.items && root.items.length > 0) {
      for (let i = 0; i < root.items.length; i++) {
        const item = root.items[i];
        if (item && item.text) {
          // Calculate width: margins + icon (if present) + spacing + text width
          let itemWidth = Style.margin2S; // left and right margins

          if (item.icon && item.icon !== "") {
            itemWidth += Style.fontSizeL + Style.marginS; // icon + spacing
          }

          // Measure actual text width
          textMeasure.text = item.text;
          const textWidth = textMeasure.contentWidth;
          itemWidth += textWidth;

          if (itemWidth > maxWidth) {
            maxWidth = itemWidth;
          }
        }
      }
    }
    // Keep menu readable without allowing extremely wide labels.
    menuContentWidth = Math.max(menuMinWidth, Math.min(menuMaxWidth, Math.ceil(maxWidth)));
  }

  function getCurrentAppId() {
    return appData?.appId || toplevel?.appId || "";
  }

  function getValidToplevels() {
    if (!ToplevelManager || !ToplevelManager.toplevels)
      return [];
    const source = appData?.toplevels && appData.toplevels.length > 0 ? appData.toplevels : (toplevel ? [toplevel] : []);
    const allToplevels = ToplevelManager.toplevels.values || [];
    return source.filter(window => window && allToplevels.includes(window));
  }

  function getPrimaryToplevel() {
    const windows = getValidToplevels();
    if (windows.length === 0)
      return null;
    if (ToplevelManager && ToplevelManager.activeToplevel && windows.includes(ToplevelManager.activeToplevel))
      return ToplevelManager.activeToplevel;
    return windows[0];
  }

  function isItemActionable(index) {
    if (index < 0 || index >= root.items.length)
      return false;
    const item = root.items[index];
    return item && typeof item.action === "function";
  }

  function rowHeightForItem(item) {
    return item && item.separator === true ? 16 : 32;
  }

  function listHeight(items) {
    let total = 0;
    if (!items)
      return total;
    for (let i = 0; i < items.length; i++) {
      total += rowHeightForItem(items[i]);
    }
    return total;
  }

  function initItems() {
    if (menuMode === "launcher") {
      root.items = [
            {
              "icon": "adjustments",
              "text": I18n.tr("actions.dock-settings"),
              "action": function () {
                handleDockSettings();
              }
            },
            {
              "icon": "adjustments",
              "text": I18n.tr("actions.launcher-settings"),
              "action": function () {
                handleLauncherSettings();
              }
            }
          ];
      calculateMenuWidth();
      return;
    }

    const windows = getValidToplevels();
    const primaryToplevel = getPrimaryToplevel();
    const appId = getCurrentAppId();
    const isRunning = windows.length > 0;
    const isPinned = isAppPinned(appId);
    const grouped = Settings.data.dock.groupApps && windows.length > 1;
    const rawGroupMenuMode = forcedGroupMenuMode || Settings.data.dock.groupContextMenuMode || "extended";
    const menuModeForGroup = grouped ? ((rawGroupMenuMode === "list" || rawGroupMenuMode === "extended") ? rawGroupMenuMode : "extended") : "single";

    var next = [];

    if (!grouped || menuModeForGroup === "single") {
      if (isRunning) {
        next.push({
                    "icon": "eye",
                    "text": I18n.tr("common.focus"),
                    "action": function () {
                      handleFocus(primaryToplevel);
                    }
                  });
      }

      next.push({
                  "icon": !isPinned ? "pin" : "unpin",
                  "text": !isPinned ? I18n.tr("common.pin") : I18n.tr("common.unpin"),
                  "action": function () {
                    handlePin(appId);
                  }
                });

      if (isRunning) {
        next.push({
                    "icon": "close",
                    "text": I18n.tr("common.close"),
                    "action": function () {
                      handleClose(primaryToplevel);
                    }
                  });
      }
    } else {
      windows.forEach((window, index) => {
                        const windowTitle = (window.title && window.title.trim() !== "") ? window.title : (appId || ("Window " + (index + 1)));
                        next.push({
                                    "icon": window === ToplevelManager?.activeToplevel ? "circle-filled" : "square-rounded",
                                    "text": windowTitle,
                                    "action": function () {
                                      handleFocus(window);
                                    }
                                  });
                      });

      if (menuModeForGroup === "extended") {
        next.push({
                    "separator": true
                  });
        next.push({
                    "icon": "eye",
                    "text": I18n.tr("common.focus"),
                    "action": function () {
                      handleFocus(primaryToplevel);
                    }
                  });
        next.push({
                    "icon": !isPinned ? "pin" : "unpin",
                    "text": !isPinned ? I18n.tr("common.pin") : I18n.tr("common.unpin"),
                    "action": function () {
                      handlePin(appId);
                    }
                  });
        next.push({
                    "icon": "close",
                    "text": I18n.tr("common.close") + " All",
                    "action": function () {
                      handleCloseAll(windows);
                    }
                  });
      }
    }

    // Keep grouped list mode as a clean window switcher.
    const canAddDesktopActions = !grouped || menuModeForGroup === "extended";

    // Create a menu entry for each app-specific action defined in its .desktop file
    if (canAddDesktopActions && typeof DesktopEntries !== 'undefined' && DesktopEntries.byId && appId) {
      const entry = (DesktopEntries.heuristicLookup) ? DesktopEntries.heuristicLookup(appId) : DesktopEntries.byId(appId);
      if (entry != null) {
        entry.actions.forEach(function (action) {
          next.push({
                      "icon": "chevron-right",
                      "text": action.name,
                      "action": function () {
                        if (action.command && action.command.length > 0) {
                          Quickshell.execDetached(action.command);
                        } else if (action.execute) {
                          action.execute();
                        }
                        if (Settings.data.dock.dockType === "attached") {
                          const panel = PanelService.getPanel("staticDockPanel", root.screen, false);
                          if (panel)
                            panel.close();
                        }
                      }
                    });
        });
      }
    }

    root.items = next;
    // Force width recalculation when items change
    calculateMenuWidth();
  }

  // Helper function to normalize app IDs for case-insensitive matching
  function normalizeAppId(appId) {
    if (!appId || typeof appId !== 'string')
      return "";
    return appId.toLowerCase().trim();
  }

  // Helper function to get desktop entry ID from an app ID
  function getDesktopEntryId(appId) {
    if (!appId)
      return appId;

    // Try to find the desktop entry using heuristic lookup
    if (typeof DesktopEntries !== 'undefined' && DesktopEntries.heuristicLookup) {
      try {
        const entry = DesktopEntries.heuristicLookup(appId);
        if (entry && entry.id) {
          return entry.id;
        }
      } catch (e)
        // Fall through to return original appId
      {}
    }

    // Try direct lookup
    if (typeof DesktopEntries !== 'undefined' && DesktopEntries.byId) {
      try {
        const entry = DesktopEntries.byId(appId);
        if (entry && entry.id) {
          return entry.id;
        }
      } catch (e)
        // Fall through to return original appId
      {}
    }

    // Return original appId if we can't find a desktop entry
    return appId;
  }

  // Helper functions for pin/unpin functionality
  function isAppPinned(appId) {
    if (!appId)
      return false;
    const pinnedApps = Settings.data.dock.pinnedApps || [];
    const normalizedId = normalizeAppId(appId);
    return pinnedApps.some(pinnedId => normalizeAppId(pinnedId) === normalizedId);
  }

  function toggleAppPin(appId) {
    if (!appId)
      return;

    // Get the desktop entry ID for consistent pinning
    const desktopEntryId = getDesktopEntryId(appId);
    const normalizedId = normalizeAppId(desktopEntryId);

    let pinnedApps = (Settings.data.dock.pinnedApps || []).slice(); // Create a copy

    // Find existing pinned app with case-insensitive matching
    const existingIndex = pinnedApps.findIndex(pinnedId => normalizeAppId(pinnedId) === normalizedId);
    const isPinned = existingIndex >= 0;

    if (isPinned) {
      // Unpin: remove from array
      pinnedApps.splice(existingIndex, 1);
    } else {
      // Pin: add desktop entry ID to array
      pinnedApps.push(desktopEntryId);
    }

    // Update the settings
    Settings.data.dock.pinnedApps = pinnedApps;
  }

  // Dock position for context menu placement
  property string dockPosition: "bottom"

  anchor.item: anchorItem
  // Position menu on opposite side of dock with comfortable spacing
  anchor.rect.x: {
    if (!anchorItem)
      return 0;
    switch (dockPosition) {
    case "left":
      return anchorItem.width + Style.marginL; // Open to right of dock
    case "right":
      return -implicitWidth - Style.marginL; // Open to left of dock
    default:
      return (anchorItem.width - implicitWidth) / 2; // Center horizontally
    }
  }
  anchor.rect.y: {
    if (!anchorItem)
      return 0;
    switch (dockPosition) {
    case "top":
      return anchorItem.height + Style.marginL; // Open below dock
    case "bottom":
      return -implicitHeight - Style.marginL; // Open above dock (default)
    case "left":
    case "right":
      return (anchorItem.height - implicitHeight) / 2; // Center vertically
    default:
      return -implicitHeight - Style.marginL;
    }
  }

  function show(item, toplevelData, screen, groupModeOverride) {
    if (!item) {
      return;
    }

    // First hide completely
    visible = false;

    // Then set up new data
    anchorItem = item;
    if (toplevelData && typeof toplevelData === "object" && (toplevelData.appId !== undefined || toplevelData.toplevels !== undefined)) {
      appData = toplevelData;
      toplevel = toplevelData.toplevel || null;
    } else {
      appData = toplevelData ? {
                                 "appId": toplevelData.appId,
                                 "toplevel": toplevelData,
                                 "toplevels": toplevelData ? [toplevelData] : []
                               } : null;
      toplevel = toplevelData;
    }
    targetScreen = screen || null;
    forcedGroupMenuMode = groupModeOverride || "";
    initItems();

    visible = true;
    canAutoClose = false;
    gracePeriodTimer.restart();
  }

  // Helper function to determine which menu item is under the mouse
  function getHoveredItem(mouseY) {
    const startY = Style.marginM;
    const localY = mouseY - startY;

    if (localY < 0)
      return -1;

    function findIndexInList(items, relativeY, baseIndex) {
      let offset = 0;
      for (let i = 0; i < items.length; i++) {
        const h = rowHeightForItem(items[i]);
        if (relativeY >= offset && relativeY < offset + h)
          return baseIndex + i;
        offset += h;
      }
      return -1;
    }

    if (splitExtendedLayout) {
      if (localY < scrollAreaHeight) {
        return findIndexInList(scrollItems, localY + (menuFlick ? menuFlick.contentY : 0), 0);
      }
      if (localY < scrollAreaHeight + separatorBlockHeight) {
        return -1;
      }
      return findIndexInList(fixedItems, localY - scrollAreaHeight - separatorBlockHeight, separatorIndex + 1);
    } else {
      return findIndexInList(scrollItems, localY + (menuFlick ? menuFlick.contentY : 0), 0);
    }
  }

  function fixedItemGlobalIndex(localIndex) {
    if (!splitExtendedLayout)
      return localIndex;
    return separatorIndex + 1 + localIndex;
  }

  function isScrollableHovered(mouseY) {
    const localY = mouseY - Style.marginM;
    return localY >= 0 && localY < scrollAreaHeight;
  }

  function onWheelScroll(deltaY) {
    if (!menuFlick || menuFlick.contentHeight <= menuFlick.height)
      return;
    const nextY = menuFlick.contentY - deltaY;
    menuFlick.contentY = Math.max(0, Math.min(nextY, menuFlick.contentHeight - menuFlick.height));
  }

  function resetMenuState() {
    root.items.length = 0;
    root.appData = null;
    root.toplevel = null;
    root.forcedGroupMenuMode = "";
    menuContentWidth = menuMinWidth;
    hoveredItem = -1;
    if (menuFlick)
      menuFlick.contentY = 0;
  }

  function hide() {
    visible = false;
    resetMenuState();
  }

  function hideWithoutReset() {
    visible = false;
  }

  function closeAndReset() {
    hide();
    root.requestClose();
  }

  function handleFocus(targetToplevel) {
    if (targetToplevel?.activate) {
      targetToplevel.activate();
    }
    closeAndReset();
  }

  function handlePin(appId) {
    if (appId) {
      root.toggleAppPin(appId);
    }
    Qt.callLater(() => closeAndReset());
  }

  function handleClose(targetToplevel) {
    const isValidToplevel = targetToplevel && ToplevelManager && ToplevelManager.toplevels.values.includes(targetToplevel);

    if (isValidToplevel && targetToplevel.close) {
      targetToplevel.close();
      if (root.onAppClosed && typeof root.onAppClosed === "function") {
        Qt.callLater(root.onAppClosed);
      }
    }
    closeAndReset();
  }

  function handleCloseAll(windows) {
    windows.forEach(window => {
                      if (window && ToplevelManager && ToplevelManager.toplevels.values.includes(window) && window.close) {
                        window.close();
                      }
                    });
    if (root.onAppClosed && typeof root.onAppClosed === "function") {
      Qt.callLater(root.onAppClosed);
    }
    closeAndReset();
  }

  function handleLauncherSettings() {
    if (targetScreen) {
      var panel = PanelService.getPanel("settingsPanel", targetScreen);
      panel.requestedTab = SettingsPanel.Tab.Launcher;
      panel.toggle();
    }
    closeAndReset();
  }

  function handleDockSettings() {
    if (targetScreen) {
      var panel = PanelService.getPanel("settingsPanel", targetScreen);
      panel.requestedTab = SettingsPanel.Tab.Dock;
      panel.toggle();
    }
    closeAndReset();
  }

  function handleLauncherWidgetSettings() {
    if (targetScreen && launcherWidgetSection && launcherWidgetIndex >= 0) {
      BarService.openWidgetSettings(targetScreen, launcherWidgetSection, launcherWidgetIndex, "Launcher", launcherWidgetSettings || {});
    }
    closeAndReset();
  }

  // Short delay to ignore spurious events
  Timer {
    id: gracePeriodTimer
    interval: 1500
    repeat: false
    onTriggered: {
      root.canAutoClose = true;
      if (!menuHoverHandler.hovered) {
        closeTimer.start();
      }
    }
  }

  Timer {
    id: closeTimer
    interval: 500
    repeat: false
    running: false
    onTriggered: {
      root.hideWithoutReset();
    }
  }

  Rectangle {
    anchors.fill: parent
    anchors.margins: border.width
    color: Color.mSurfaceVariant
    radius: Style.radiusS
    border.color: Color.mOutline
    border.width: Style.borderS

    HoverHandler {
      id: menuHoverHandler
      acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
      onHoveredChanged: {
        if (hovered) {
          closeTimer.stop();
        } else {
          root.hoveredItem = -1;
          if (root.canAutoClose) {
            closeTimer.start();
          }
        }
      }
    }

    WheelHandler {
      acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
      onWheel: event => {
                 if (!root.isScrollableHovered(event.y))
                 return;
                 const delta = event.pixelDelta.y !== 0 ? event.pixelDelta.y : event.angleDelta.y / 2;
                 root.onWheelScroll(delta);
                 event.accepted = true;
               }
    }

    Flickable {
      id: menuFlick
      anchors.left: parent.left
      anchors.right: parent.right
      anchors.top: parent.top
      anchors.leftMargin: Style.marginM
      anchors.rightMargin: Style.marginM
      anchors.topMargin: Style.marginM
      height: root.scrollAreaHeight
      clip: true
      contentWidth: width
      contentHeight: scrollColumn.height
      flickableDirection: Flickable.VerticalFlick
      boundsBehavior: Flickable.StopAtBounds
      interactive: contentHeight > height

      ScrollBar.vertical: ScrollBar {
        id: menuScrollBar
        policy: ScrollBar.AsNeeded
        visible: root.listOverflowing
        interactive: true
        hoverEnabled: true
      }

      Column {
        id: scrollColumn
        width: menuFlick.width
        spacing: 0

        Repeater {
          model: root.scrollItems

          Rectangle {
            readonly property bool isSeparator: modelData && modelData.separator === true
            width: scrollColumn.width
            height: root.rowHeightForItem(modelData)
            color: (!isSeparator && root.hoveredItem === index) ? Color.mHover : "transparent"
            radius: Style.radiusXS

            Row {
              id: rowLayout
              anchors.left: parent.left
              anchors.right: parent.right
              anchors.verticalCenter: parent.verticalCenter
              anchors.leftMargin: Style.marginS
              anchors.rightMargin: Style.marginS
              spacing: Style.marginS
              visible: !isSeparator

              NIcon {
                icon: modelData.icon
                pointSize: Style.fontSizeL
                color: root.hoveredItem === index ? Color.mOnHover : Color.mOnSurfaceVariant
                visible: icon !== ""
                anchors.verticalCenter: parent.verticalCenter
              }

              NText {
                text: modelData.text
                pointSize: Style.fontSizeS
                color: root.hoveredItem === index ? Color.mOnHover : Color.mOnSurfaceVariant
                anchors.verticalCenter: parent.verticalCenter
                width: rowLayout.width - ((modelData.icon && modelData.icon !== "") ? (Style.fontSizeL + Style.marginS) : 0)
                elide: Text.ElideRight
              }
            }

            MouseArea {
              anchors.fill: parent
              enabled: !parent.isSeparator && root.isItemActionable(index)
              hoverEnabled: true
              cursorShape: Qt.PointingHandCursor
              acceptedButtons: Qt.LeftButton

              onEntered: {
                root.hoveredItem = index;
              }

              onExited: {
                if (root.hoveredItem === index) {
                  root.hoveredItem = -1;
                }
              }

              onClicked: {
                if (root.isItemActionable(index)) {
                  root.items[index].action.call();
                }
              }
            }
          }
        }
      }
    }

    Rectangle {
      id: separator
      visible: root.splitExtendedLayout
      anchors.left: parent.left
      anchors.right: parent.right
      anchors.top: menuFlick.bottom
      anchors.leftMargin: Style.marginS
      anchors.rightMargin: Style.marginS
      anchors.topMargin: Style.marginS
      height: Style.borderS
      color: Qt.alpha(Color.mOutline, 0.7)
      radius: Style.radiusXS
    }

    Column {
      id: fixedColumn
      visible: root.splitExtendedLayout && root.fixedItems.length > 0
      anchors.left: parent.left
      anchors.right: parent.right
      anchors.bottom: parent.bottom
      anchors.leftMargin: Style.marginM
      anchors.rightMargin: Style.marginM
      anchors.topMargin: Style.marginS
      anchors.bottomMargin: Style.marginM
      anchors.top: separator.bottom
      spacing: 0

      Repeater {
        model: root.fixedItems

        Rectangle {
          id: fixedItemRect
          readonly property int globalIndex: root.fixedItemGlobalIndex(index)
          width: fixedColumn.width
          height: root.rowHeightForItem(modelData)
          color: root.hoveredItem === globalIndex ? Color.mHover : "transparent"
          radius: Style.radiusXS

          Row {
            id: fixedRowLayout
            anchors.left: parent.left
            anchors.right: parent.right
            anchors.verticalCenter: parent.verticalCenter
            anchors.leftMargin: Style.marginS
            anchors.rightMargin: Style.marginS
            spacing: Style.marginS

            NIcon {
              icon: modelData.icon
              pointSize: Style.fontSizeL
              color: root.hoveredItem === fixedItemRect.globalIndex ? Color.mOnHover : Color.mOnSurfaceVariant
              visible: icon !== ""
              anchors.verticalCenter: parent.verticalCenter
            }

            NText {
              text: modelData.text
              pointSize: Style.fontSizeS
              color: root.hoveredItem === fixedItemRect.globalIndex ? Color.mOnHover : Color.mOnSurfaceVariant
              anchors.verticalCenter: parent.verticalCenter
              width: fixedRowLayout.width - ((modelData.icon && modelData.icon !== "") ? (Style.fontSizeL + Style.marginS) : 0)
              elide: Text.ElideRight
            }
          }

          MouseArea {
            anchors.fill: parent
            enabled: root.isItemActionable(parent.globalIndex)
            hoverEnabled: true
            cursorShape: Qt.PointingHandCursor
            acceptedButtons: Qt.LeftButton

            onEntered: {
              root.hoveredItem = parent.globalIndex;
            }

            onExited: {
              if (root.hoveredItem === parent.globalIndex) {
                root.hoveredItem = -1;
              }
            }

            onClicked: {
              if (root.isItemActionable(parent.globalIndex)) {
                root.items[parent.globalIndex].action.call();
              }
            }
          }
        }
      }
    }
  }
}
