import QtQuick
import QtQuick.Effects
import Quickshell
import qs.Commons
import qs.Services.Noctalia
import qs.Services.UI
import qs.Widgets

Item {
  id: root

  property ShellScreen screen
  property var widgetData: null
  property int widgetIndex: -1

  property real defaultX: 100
  property real defaultY: 100

  default property alias content: contentContainer.data

  readonly property bool isDragging: internal.isDragging
  readonly property bool isScaling: internal.isScaling

  // All Desktop widgets have these settings, but fallback just in case
  readonly property var _metadata: widgetData?.id ? DesktopWidgetRegistry.widgetMetadata[widgetData.id] : null
  property bool showBackground: widgetData.showBackground !== undefined ? widgetData.showBackground : (_metadata?.showBackground ?? true)
  property bool roundedCorners: widgetData.roundedCorners !== undefined ? widgetData.roundedCorners : (_metadata?.roundedCorners ?? true)

  property real widgetScale: 1.0
  property real minScale: 0.5
  property real maxScale: 5.0

  readonly property real scaleSensitivity: 0.0015
  readonly property real scaleUpdateThreshold: 0.015
  readonly property real cornerScaleSensitivity: 0.0003  // Much lower sensitivity for corner handles

  // Grid size ensures lines pass through screen center on both axes
  readonly property int gridSize: {
    if (!screen)
      return 30;
    var baseSize = Math.round(screen.width * 0.015);
    baseSize = Math.max(20, Math.min(60, baseSize));

    var centerX = screen.width / 2;
    var centerY = screen.height / 2;
    var bestSize = baseSize;
    var bestDistance = Infinity;

    for (var offset = -10; offset <= 10; offset++) {
      var candidate = baseSize + offset;
      if (candidate < 20 || candidate > 60)
        continue;

      var remainderX = centerX % candidate;
      var remainderY = centerY % candidate;

      if (remainderX === 0 && remainderY === 0) {
        return candidate;
      }

      var distance = Math.abs(remainderX) + Math.abs(remainderY);
      if (distance < bestDistance) {
        bestDistance = distance;
        bestSize = candidate;
      }
    }

    var gcd = function (a, b) {
      while (b !== 0) {
        var temp = b;
        b = a % b;
        a = temp;
      }
      return a;
    };

    var centerGcd = gcd(Math.round(centerX), Math.round(centerY));
    if (centerGcd > 0) {
      for (var divisor = Math.floor(centerGcd / 60); divisor <= Math.ceil(centerGcd / 20); divisor++) {
        if (centerGcd % divisor !== 0)
          continue;
        var candidate = centerGcd / divisor;
        if (candidate >= 20 && candidate <= 60) {
          if (Math.abs(candidate - baseSize) < Math.abs(bestSize - baseSize)) {
            bestSize = candidate;
          }
        }
      }
    }

    return bestSize;
  }

  QtObject {
    id: internal
    property bool isDragging: false
    property bool isScaling: false
    property real dragOffsetX: 0
    property real dragOffsetY: 0
    property real baseX: (root.widgetData && root.widgetData.x !== undefined) ? root.widgetData.x : root.defaultX
    property real baseY: (root.widgetData && root.widgetData.y !== undefined) ? root.widgetData.y : root.defaultY
    property real initialWidth: 0
    property real initialHeight: 0
    property point initialMousePos: Qt.point(0, 0)
    property real initialScale: 1.0
    property real lastScale: 1.0
    // Locks operation type to prevent switching between drag/scale mid-operation
    property string operationType: ""  // "drag" or "scale" or ""
  }

  function snapToGrid(coord) {
    if (!Settings.data.desktopWidgets.gridSnap) {
      return coord;
    }
    return Math.round(coord / root.gridSize) * root.gridSize;
  }

  function snapScaleToGrid(scale) {
    if (!Settings.data.desktopWidgets.gridSnap || !Settings.data.desktopWidgets.gridSnapScale) {
      return scale;
    }

    // Get widget's base width
    var initialWidth = internal.initialWidth;
    var initialScale = internal.initialScale;
    if (initialWidth <= 0 || initialScale <= 0) {
      return scale;
    }

    // Since initialWidth = baseWidth * initialScale
    var baseWidth = initialWidth / initialScale;

    // Snap the resulting width with the scale
    var resultingWidth = baseWidth * scale;
    var snappedWidth = root.snapToGrid(resultingWidth);

    // Check that the snappedWidth isn't smaller than one grid size
    if (snappedWidth < root.gridSize) {
      snappedWidth = root.gridSize;
    }

    // Return the ratio of the snappedWidth and the baseWidth, which is the new snapped scale
    var snappedScale = snappedWidth / baseWidth;
    return Math.max(minScale, Math.min(maxScale, snappedScale));
  }

  function updateWidgetData(properties) {
    if (widgetIndex < 0 || !screen || !screen.name) {
      return;
    }
    DesktopWidgetRegistry.updateWidgetData(screen.name, widgetIndex, properties);
  }

  function removeWidget() {
    if (widgetIndex < 0 || !screen || !screen.name) {
      return;
    }

    var monitorWidgets = Settings.data.desktopWidgets.monitorWidgets || [];
    var newMonitorWidgets = monitorWidgets.slice();

    for (var i = 0; i < newMonitorWidgets.length; i++) {
      if (newMonitorWidgets[i].name === screen.name) {
        var widgets = (newMonitorWidgets[i].widgets || []).slice();
        if (widgetIndex >= 0 && widgetIndex < widgets.length) {
          widgets.splice(widgetIndex, 1);
          newMonitorWidgets[i] = Object.assign({}, newMonitorWidgets[i], {
                                                 "widgets": widgets
                                               });
          Settings.data.desktopWidgets.monitorWidgets = newMonitorWidgets;
        }
        break;
      }
    }
  }

  function raiseToTop() {
    if (widgetIndex < 0 || !screen || !screen.name) {
      return;
    }

    var monitorWidgets = Settings.data.desktopWidgets.monitorWidgets || [];
    var newMonitorWidgets = monitorWidgets.slice();

    for (var i = 0; i < newMonitorWidgets.length; i++) {
      if (newMonitorWidgets[i].name === screen.name) {
        var widgets = (newMonitorWidgets[i].widgets || []).slice();
        if (widgetIndex < widgets.length && widgetIndex < widgets.length - 1) {
          var widget = widgets.splice(widgetIndex, 1)[0];
          widgets.push(widget);
          newMonitorWidgets[i] = Object.assign({}, newMonitorWidgets[i], {
                                                 "widgets": widgets
                                               });
          Settings.data.desktopWidgets.monitorWidgets = newMonitorWidgets;
        }
        break;
      }
    }
  }

  function lowerToBottom() {
    if (widgetIndex < 0 || !screen || !screen.name) {
      return;
    }

    var monitorWidgets = Settings.data.desktopWidgets.monitorWidgets || [];
    var newMonitorWidgets = monitorWidgets.slice();

    for (var i = 0; i < newMonitorWidgets.length; i++) {
      if (newMonitorWidgets[i].name === screen.name) {
        var widgets = (newMonitorWidgets[i].widgets || []).slice();
        if (widgetIndex < widgets.length && widgetIndex > 0) {
          var widget = widgets.splice(widgetIndex, 1)[0];
          widgets.unshift(widget);
          newMonitorWidgets[i] = Object.assign({}, newMonitorWidgets[i], {
                                                 "widgets": widgets
                                               });
          Settings.data.desktopWidgets.monitorWidgets = newMonitorWidgets;
        }
        break;
      }
    }
  }

  function openWidgetSettings() {
    DesktopWidgetRegistry.openWidgetSettings(screen, widgetIndex, widgetData.id, widgetData);
  }

  function handleContextMenuAction(action) {
    if (action === "widget-settings") {
      // Don't close - openWidgetSettings will use the popup window for the dialog
      root.openWidgetSettings();
      return true; // Signal that we're handling close ourselves
    } else if (action === "reset") {
      // Reset scale and position to defaults
      root.widgetScale = 1.0;
      internal.baseX = root.defaultX;
      internal.baseY = root.defaultY;
      root.updateWidgetData({
                              "scale": 1.0,
                              "x": Math.round(root.defaultX),
                              "y": Math.round(root.defaultY)
                            });
      return false;
    } else if (action === "raise-to-top") {
      root.raiseToTop();
      return false;
    } else if (action === "lower-to-bottom") {
      root.lowerToBottom();
      return false;
    } else if (action === "delete") {
      root.removeWidget();
      return false; // Let caller close the popup
    }
    return false;
  }

  x: Math.round(internal.isDragging ? internal.dragOffsetX : internal.baseX)
  y: Math.round(internal.isDragging ? internal.dragOffsetY : internal.baseY)

  // Note: We no longer use transform-based scaling (scale property)
  // Instead, child widgets multiply their dimensions by widgetScale
  // This prevents blurry text at fractional scale values

  Component.onCompleted: {
    // Initialize scale from widgetData when component is first created
    if (widgetData && widgetData.scale !== undefined) {
      widgetScale = widgetData.scale;
    }
  }

  onWidgetDataChanged: {
    if (!internal.isDragging && !internal.isScaling) {
      internal.baseX = (widgetData && widgetData.x !== undefined) ? widgetData.x : defaultX;
      internal.baseY = (widgetData && widgetData.y !== undefined) ? widgetData.y : defaultY;
      if (widgetData && widgetData.scale !== undefined) {
        widgetScale = widgetData.scale;
      } else if (widgetData) {
        // If widgetData exists but scale is not set, default to 1.0
        widgetScale = 1.0;
      }
    }
  }

  Rectangle {
    id: decorationRect
    anchors.fill: parent
    anchors.margins: -outlineMargin
    color: DesktopWidgetRegistry.editMode ? Qt.rgba(Color.mPrimary.r, Color.mPrimary.g, Color.mPrimary.b, 0.1) : "transparent"
    border.color: (DesktopWidgetRegistry.editMode || internal.isDragging) ? (internal.isDragging ? Color.mOutline : Color.mPrimary) : "transparent"
    border.width: DesktopWidgetRegistry.editMode ? 3 : 0
    radius: Math.min(Math.round(Style.radiusL * root.widgetScale), Style.radiusL, width / 2, height / 2)
    z: -1
  }

  Rectangle {
    id: container
    anchors.fill: parent
    radius: root.roundedCorners ? Math.min(Math.round(Style.radiusL * root.widgetScale), Style.radiusL, width / 2, height / 2) : 0
    color: Qt.alpha(Color.mSurface, Settings.data.ui.panelBackgroundOpacity)
    border {
      width: 1
      color: Qt.alpha(Color.mOutline, 0.12)
    }
    clip: true
    visible: root.showBackground

    layer.enabled: Settings.data.general.enableShadows && !internal.isDragging && root.showBackground
    layer.effect: MultiEffect {
      shadowEnabled: true
      shadowBlur: Style.shadowBlur * 1.5
      shadowOpacity: Style.shadowOpacity * 0.6
      shadowColor: "black"
      shadowHorizontalOffset: Settings.data.general.shadowOffsetX
      shadowVerticalOffset: Settings.data.general.shadowOffsetY
      blurMax: Style.shadowBlurMax
    }
  }

  Item {
    id: contentContainer
    anchors.fill: parent
    z: 1
    clip: true
  }

  // Context menu model and handler - menu is created dynamically in PopupMenuWindow
  property var contextMenuModel: {
    var hasSettings = false;
    if (widgetData && widgetData.id) {
      var widgetId = widgetData.id;
      if (DesktopWidgetRegistry.isPluginWidget(widgetId)) {
        var pluginId = widgetId.replace("plugin:", "");
        var manifest = PluginRegistry.getPluginManifest(pluginId);
        hasSettings = manifest && manifest.entryPoints && (manifest.entryPoints.settings || manifest.entryPoints.desktopWidgetSettings);
      } else {
        hasSettings = DesktopWidgetRegistry.widgetSettingsMap[widgetId] !== undefined;
      }
    }

    var items = [];
    if (hasSettings) {
      items.push({
                   "label": I18n.tr("actions.widget-settings"),
                   "action": "widget-settings",
                   "icon": "settings"
                 });
    }
    items.push({
                 "label": I18n.tr("common.reset"),
                 "action": "reset",
                 "icon": "restore"
               });
    items.push({
                 "label": I18n.tr("actions.raise-to-top"),
                 "action": "raise-to-top",
                 "icon": "stack-front"
               });
    items.push({
                 "label": I18n.tr("actions.lower-to-bottom"),
                 "action": "lower-to-bottom",
                 "icon": "stack-back"
               });
    items.push({
                 "label": I18n.tr("common.delete"),
                 "action": "delete",
                 "icon": "trash"
               });
    return items;
  }

  // Drag MouseArea - handles dragging (left-click)
  MouseArea {
    id: dragArea
    anchors.fill: parent
    z: 1000
    visible: DesktopWidgetRegistry.editMode
    cursorShape: internal.isDragging ? Qt.ClosedHandCursor : Qt.OpenHandCursor
    hoverEnabled: true
    acceptedButtons: Qt.LeftButton

    property point pressPos: Qt.point(0, 0)

    onPressed: mouse => {
                 // Prevent starting new operation if one is already in progress
                 if (internal.operationType !== "") {
                   return;
                 }

                 pressPos = Qt.point(mouse.x, mouse.y);
                 internal.operationType = "drag";
                 internal.dragOffsetX = root.x;
                 internal.dragOffsetY = root.y;
                 internal.isDragging = true;
               }

    onPositionChanged: mouse => {
                         if (internal.isDragging && pressed && internal.operationType === "drag") {
                           var globalPressPos = mapToItem(root.parent, pressPos.x, pressPos.y);
                           var globalCurrentPos = mapToItem(root.parent, mouse.x, mouse.y);

                           var deltaX = globalCurrentPos.x - globalPressPos.x;
                           var deltaY = globalCurrentPos.y - globalPressPos.y;

                           var newX = internal.dragOffsetX + deltaX;
                           var newY = internal.dragOffsetY + deltaY;

                           // Boundary clamping - allow widgets to go partially off-screen (75% can clip)
                           // This gives users more control over positioning while keeping widgets accessible
                           var scaledWidth = root.width;
                           var scaledHeight = root.height;
                           if (root.parent && scaledWidth > 0 && scaledHeight > 0) {
                             var minVisibleX = scaledWidth * 0.25;
                             var minVisibleY = scaledHeight * 0.25;
                             newX = Math.max(-scaledWidth + minVisibleX, Math.min(newX, root.parent.width - minVisibleX));
                             newY = Math.max(-scaledHeight + minVisibleY, Math.min(newY, root.parent.height - minVisibleY));
                           }

                           if (Settings.data.desktopWidgets.gridSnap) {
                             newX = root.snapToGrid(newX);
                             newY = root.snapToGrid(newY);
                             // Re-clamp after snapping
                             if (root.parent && scaledWidth > 0 && scaledHeight > 0) {
                               var minVisibleX = scaledWidth * 0.25;
                               var minVisibleY = scaledHeight * 0.25;
                               newX = Math.max(-scaledWidth + minVisibleX, Math.min(newX, root.parent.width - minVisibleX));
                               newY = Math.max(-scaledHeight + minVisibleY, Math.min(newY, root.parent.height - minVisibleY));
                             }
                           }

                           internal.dragOffsetX = newX;
                           internal.dragOffsetY = newY;
                         }
                       }

    onReleased: mouse => {
                  if (internal.isDragging && internal.operationType === "drag" && widgetIndex >= 0 && screen && screen.name) {
                    var roundedX = Math.round(internal.dragOffsetX);
                    var roundedY = Math.round(internal.dragOffsetY);
                    root.updateWidgetData({
                                            "x": roundedX,
                                            "y": roundedY
                                          });

                    internal.baseX = roundedX;
                    internal.baseY = roundedY;
                    internal.isDragging = false;
                    internal.operationType = "";
                  }
                }

    onCanceled: {
      internal.isDragging = false;
      internal.operationType = "";
    }
  }

  // Right-click MouseArea for context menu
  MouseArea {
    id: contextMenuArea
    anchors.fill: parent
    z: 1001
    visible: DesktopWidgetRegistry.editMode
    acceptedButtons: Qt.RightButton
    hoverEnabled: true

    onPressed: mouse => {
                 if (mouse.button === Qt.RightButton) {
                   var popupMenuWindow = PanelService.getPopupMenuWindow(root.screen);
                   if (popupMenuWindow) {
                     // Map click position to screen coordinates
                     var globalPos = root.mapToItem(null, mouse.x, mouse.y);
                     // Use dynamic context menu (created in PopupMenuWindow's Top layer)
                     // This ensures input events work correctly for desktop widgets (Bottom layer)
                     popupMenuWindow.showDynamicContextMenu(root.contextMenuModel, globalPos.x, globalPos.y, root.handleContextMenuAction);
                   }
                 }
               }
  }

  // Corner handles for scaling - using Repeater to avoid code duplication
  readonly property real cornerHandleSize: 8 * widgetScale
  readonly property real outlineMargin: Style.marginS * widgetScale
  readonly property color colorHandle: Color.mSecondary

  // Corner handle model: defines position, direction, cursor, and triangle points for each corner
  // xMult/yMult: multipliers for position (0 = left/top edge, 1 = right/bottom edge)
  // xDir/yDir: direction multiplier for scaling (-1 = left/up increases, 1 = right/down increases)
  // cursor: resize cursor type (FDiag for TL-BR diagonal, BDiag for TR-BL diagonal)
  // points: triangle vertices as [x, y] pairs normalized to cornerHandleSize
  readonly property var cornerHandleModel: [
    {
      xMult: 0,
      yMult: 0,
      xDir: -1,
      yDir: -1,
      cursor: Qt.SizeFDiagCursor,
      points: [[0, 0], [1, 0], [0, 1]]
    },
    {
      xMult: 1,
      yMult: 0,
      xDir: 1,
      yDir: -1,
      cursor: Qt.SizeBDiagCursor,
      points: [[1, 0], [1, 1], [0, 0]]
    },
    {
      xMult: 0,
      yMult: 1,
      xDir: -1,
      yDir: 1,
      cursor: Qt.SizeBDiagCursor,
      points: [[0, 1], [0, 0], [1, 1]]
    },
    {
      xMult: 1,
      yMult: 1,
      xDir: 1,
      yDir: 1,
      cursor: Qt.SizeFDiagCursor,
      points: [[1, 1], [1, 0], [0, 1]]
    }
  ]

  Repeater {
    model: root.cornerHandleModel

    delegate: Canvas {
      id: cornerHandle
      required property var modelData
      required property int index

      visible: DesktopWidgetRegistry.editMode && !internal.isDragging
      // Position handles at corners of decoration rectangle (which extends by outlineMargin)
      x: modelData.xMult === 0 ? -outlineMargin : (root.width + outlineMargin - cornerHandleSize)
      y: modelData.yMult === 0 ? -outlineMargin : (root.height + outlineMargin - cornerHandleSize)
      width: cornerHandleSize
      height: cornerHandleSize
      z: 2000

      onPaint: {
        var ctx = getContext("2d");
        ctx.reset();
        ctx.fillStyle = colorHandle;
        ctx.beginPath();
        ctx.moveTo(modelData.points[0][0] * cornerHandleSize, modelData.points[0][1] * cornerHandleSize);
        ctx.lineTo(modelData.points[1][0] * cornerHandleSize, modelData.points[1][1] * cornerHandleSize);
        ctx.lineTo(modelData.points[2][0] * cornerHandleSize, modelData.points[2][1] * cornerHandleSize);
        ctx.closePath();
        ctx.fill();
      }

      Component.onCompleted: requestPaint()
      onVisibleChanged: if (visible)
                          requestPaint()

      Connections {
        target: root
        function onWidthChanged() {
          if (cornerHandle.visible)
            cornerHandle.requestPaint();
        }
        function onHeightChanged() {
          if (cornerHandle.visible)
            cornerHandle.requestPaint();
        }
      }

      MouseArea {
        id: scaleMouseArea
        anchors.fill: parent
        acceptedButtons: Qt.LeftButton
        cursorShape: cornerHandle.modelData.cursor
        property point pressPos: Qt.point(0, 0)

        onPressed: mouse => {
                     if (internal.operationType !== "") {
                       return;
                     }
                     pressPos = mapToItem(root.parent, mouse.x, mouse.y);
                     internal.operationType = "scale";
                     internal.isScaling = true;
                     internal.initialScale = root.widgetScale;
                     internal.lastScale = root.widgetScale;
                     internal.initialWidth = root.width;
                     internal.initialHeight = root.height;
                   }

        onPositionChanged: mouse => {
                             if (internal.isScaling && pressed && internal.operationType === "scale") {
                               var currentPos = mapToItem(root.parent, mouse.x, mouse.y);
                               var deltaX = currentPos.x - pressPos.x;
                               var deltaY = currentPos.y - pressPos.y;

                               // Project delta onto the diagonal direction for this corner
                               // xDir/yDir indicate which direction increases scale
                               var diagonalDelta = (deltaX * cornerHandle.modelData.xDir + deltaY * cornerHandle.modelData.yDir) / Math.sqrt(2);

                               // Scale sensitivity: pixels of drag per 1.0 scale change
                               var sensitivity = 150;
                               var scaleDelta = diagonalDelta / sensitivity;
                               var newScale = Math.max(root.minScale, Math.min(root.maxScale, internal.initialScale + scaleDelta));

                               newScale = root.snapScaleToGrid(newScale);

                               if (!isNaN(newScale) && newScale > 0) {
                                 root.widgetScale = newScale;
                                 internal.lastScale = newScale;
                               }
                             }
                           }

        onReleased: mouse => {
                      if (internal.isScaling && internal.operationType === "scale") {
                        root.updateWidgetData({
                                                "scale": root.widgetScale
                                              });
                        internal.isScaling = false;
                        internal.operationType = "";
                        root.widgetScale = root.snapScaleToGrid(root.widgetScale);
                        internal.lastScale = root.widgetScale;
                      }
                    }

        onCanceled: {
          internal.isScaling = false;
          internal.operationType = "";
          internal.lastScale = root.widgetScale;
        }
      }
    }
  }
}
