import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Widgets

PopupWindow {
  id: root

  property string text: ""
  property var rows: null // Array of arrays for grid mode: [["col1", "col2"], ["row2col1", "row2col2"]]
  property bool isGridMode: rows !== null && Array.isArray(rows) && rows.length > 0
  property int columnCount: isGridMode && rows.length > 0 ? rows[0].length : 0
  property string direction: "auto" // "auto", "left", "right", "top", "bottom"
  property int margin: Style.marginXS // distance from target
  property int padding: Style.marginM
  property int gridPaddingVertical: Style.marginXS // extra vertical padding for grid mode
  property int delay: 0
  property int hideDelay: 0
  property int maxWidth: 340

  property int animationDuration: Style.animationFast
  property real animationScale: 0.85

  // Font scale calculation matching NText behavior
  readonly property real gridFontScale: {
    const baseScale = (tooltipText.family === Settings.data.ui.fontDefault ? Settings.data.ui.fontDefaultScale : Settings.data.ui.fontFixedScale);
    return baseScale * Style.uiScaleRatio;
  }

  // For measuring grid cell widths - must match actual NText font
  TextMetrics {
    id: cellMetrics
    font.family: tooltipText.family
    font.pointSize: Math.max(1, Style.fontSizeS * gridFontScale)
  }

  // For measuring grid row height - hidden NText matches actual grid items exactly
  NText {
    id: rowHeightMeasure
    visible: false
    text: "Ag"  // Characters with ascenders and descenders
    pointSize: Style.fontSizeS
    family: tooltipText.family
  }

  // Internal properties
  property var targetItem: null
  property real anchorX: 0
  property real anchorY: 0
  property bool isPositioned: false
  property bool animatingOut: true
  property int screenWidth: 1920
  property int screenHeight: 1080
  property int screenX: 0
  property int screenY: 0

  visible: false
  color: "transparent"

  anchor.item: targetItem
  anchor.rect.x: anchorX
  anchor.rect.y: anchorY

  // Timer for showing tooltip after delay
  Timer {
    id: showTimer
    interval: root.delay
    repeat: false
    onTriggered: {
      root.positionAndShow();
    }
  }

  // Timer for hiding tooltip after delay
  Timer {
    id: hideTimer
    interval: root.hideDelay
    repeat: false
    onTriggered: {
      root.startHideAnimation();
    }
  }

  // Show animation
  ParallelAnimation {
    id: showAnimation

    PropertyAnimation {
      target: tooltipContainer
      property: "opacity"
      from: 0.0
      to: 1.0
      duration: root.animationDuration
      easing.type: Easing.OutCubic
    }

    PropertyAnimation {
      target: tooltipContainer
      property: "scale"
      from: root.animationScale
      to: 1.0
      duration: root.animationDuration
      easing.type: Easing.OutBack
      easing.overshoot: 1.2
    }
  }

  // Hide animation
  ParallelAnimation {
    id: hideAnimation

    PropertyAnimation {
      target: tooltipContainer
      property: "opacity"
      from: 1.0
      to: 0.0
      duration: root.animationDuration * 0.75 // Slightly faster hide
      easing.type: Easing.InCubic
    }

    PropertyAnimation {
      target: tooltipContainer
      property: "scale"
      from: 1.0
      to: root.animationScale
      duration: root.animationDuration * 0.75
      easing.type: Easing.InCubic
    }

    onFinished: {
      root.completeHide();
    }
  }

  // Function to show tooltip (content can be string or array of arrays for grid mode)
  function show(target, content, customDirection, showDelay, fontFamily) {
    if (!target || !content || content === "" || (Array.isArray(content) && content.length === 0))
      return;

    root.delay = showDelay;

    // Stop any running timers and animations
    hideTimer.stop();
    showTimer.stop();
    hideAnimation.stop();
    animatingOut = false;

    // If we're already showing for a different target, hide immediately
    if (visible && targetItem !== target) {
      hideImmediately();
    }

    // Set content based on type
    if (Array.isArray(content)) {
      rows = content;
      text = "";
    } else {
      // Convert \n to <br> for RichText format
      const processedText = content.replace(/\n/g, '<br>');
      text = processedText;
      rows = null;
    }

    targetItem = target;

    // Find the correct screen dimensions based on target's global position
    // Respect all screens positionning
    const targetGlobal = target.mapToGlobal(target.width / 2, target.height / 2);
    let foundScreen = false;
    for (let i = 0; i < Quickshell.screens.length; i++) {
      const s = Quickshell.screens[i];
      if (targetGlobal.x >= s.x && targetGlobal.x < s.x + s.width && targetGlobal.y >= s.y && targetGlobal.y < s.y + s.height) {
        screenWidth = s.width;
        screenHeight = s.height;
        screenX = s.x;
        screenY = s.y;
        foundScreen = true;
        break;
      }
    }
    if (!foundScreen) {
      Logger.w("Tooltip", "No screen found for target position!");
    }

    // Initialize animation state (hidden)
    tooltipContainer.opacity = 0.0;
    tooltipContainer.scale = root.animationScale;

    // Start show timer (will position and then make visible)
    showTimer.start();

    if (customDirection !== undefined) {
      direction = customDirection;
    } else {
      direction = "auto";
    }

    tooltipText.family = fontFamily ? fontFamily : Settings.data.ui.fontDefault;
  }

  // Calculate grid dimensions using measurements that match actual NText rendering
  function calculateGridSize() {
    if (!rows || rows.length === 0)
      return {
        width: 0,
        height: 0
      };

    const numCols = rows[0].length;
    const numRows = rows.length;
    let columnWidths = [];

    // Find max width for each column using TextMetrics
    for (let col = 0; col < numCols; col++) {
      let maxWidth = 0;
      for (let row = 0; row < numRows; row++) {
        cellMetrics.text = rows[row][col] || "";
        if (cellMetrics.width > maxWidth) {
          maxWidth = cellMetrics.width;
        }
      }
      columnWidths.push(maxWidth);
    }

    // Calculate total width: sum of columns + spacing between columns
    let totalWidth = 0;
    for (let i = 0; i < columnWidths.length; i++) {
      totalWidth += columnWidths[i];
    }
    totalWidth += (numCols - 1) * Style.marginM; // columnSpacing

    // Calculate total height using hidden NText for accurate row height
    const rowHeight = rowHeightMeasure.implicitHeight;
    const totalHeight = numRows * rowHeight;

    return {
      width: totalWidth,
      height: totalHeight
    };
  }

  // Function to position and display the tooltip
  function positionAndShow() {
    if (!targetItem || !targetItem.parent) {
      return;
    }

    // Calculate tooltip dimensions based on content mode
    let contentWidth, contentHeight;
    if (isGridMode) {
      const gridSize = calculateGridSize();
      contentWidth = gridSize.width;
      contentHeight = gridSize.height;
    } else {
      contentWidth = tooltipText.implicitWidth;
      contentHeight = tooltipText.implicitHeight;
    }

    const extraPad = isGridMode ? gridPaddingVertical : 0;
    const tipWidth = Math.ceil(Math.min(contentWidth + ((padding + extraPad) * 2), maxWidth));
    root.implicitWidth = tipWidth;

    const tipHeight = Math.ceil(contentHeight + ((padding + extraPad) * 2));
    root.implicitHeight = tipHeight;

    // Get target's global position and convert to screen-relative
    var targetGlobalAbs = targetItem.mapToGlobal(0, 0);
    var targetGlobal = {
      "x": targetGlobalAbs.x - screenX,
      "y": targetGlobalAbs.y - screenY
    };
    const targetWidth = targetItem.width;
    const targetHeight = targetItem.height;

    var newAnchorX = 0;
    var newAnchorY = 0;

    if (direction === "auto") {
      // Calculate available space in each direction (screen-relative)
      const spaceLeft = targetGlobal.x;
      const spaceRight = screenWidth - (targetGlobal.x + targetWidth);
      const spaceTop = targetGlobal.y;
      const spaceBottom = screenHeight - (targetGlobal.y + targetHeight);

      // Try positions in order of available space
      const positions = [
              {
                "dir": "bottom",
                "space": spaceBottom,
                "x": (targetWidth - tipWidth) / 2,
                "y": targetHeight + margin,
                "fits": spaceBottom >= tipHeight + margin
              },
              {
                "dir": "top",
                "space": spaceTop,
                "x": (targetWidth - tipWidth) / 2,
                "y": -tipHeight - margin,
                "fits": spaceTop >= tipHeight + margin
              },
              {
                "dir": "right",
                "space": spaceRight,
                "x": targetWidth + margin,
                "y": (targetHeight - tipHeight) / 2,
                "fits": spaceRight >= tipWidth + margin
              },
              {
                "dir": "left",
                "space": spaceLeft,
                "x": -tipWidth - margin,
                "y": (targetHeight - tipHeight) / 2,
                "fits": spaceLeft >= tipWidth + margin
              }
            ];

      // Find first position that fits
      var selectedPosition = null;
      for (var i = 0; i < positions.length; i++) {
        if (positions[i].fits) {
          selectedPosition = positions[i];
          break;
        }
      }

      // If none fit perfectly
      if (!selectedPosition) {
        // Sort by available space and use position with most space
        positions.sort(function (a, b) {
          return b.space - a.space;
        });
        selectedPosition = positions[0];
      }

      newAnchorX = selectedPosition.x;
      newAnchorY = selectedPosition.y;
    } else {
      // Manual direction positioning
      switch (direction) {
      case "left":
        newAnchorX = -tipWidth - margin;
        newAnchorY = (targetHeight - tipHeight) / 2;
        break;
      case "right":
        newAnchorX = targetWidth + margin;
        newAnchorY = (targetHeight - tipHeight) / 2;
        break;
      case "top":
        newAnchorX = (targetWidth - tipWidth) / 2;
        newAnchorY = -tipHeight - margin;
        break;
      case "bottom":
        newAnchorX = (targetWidth - tipWidth) / 2;
        newAnchorY = targetHeight + margin;
        break;
      }
    }

    // Adjust horizontal position to keep tooltip on screen.
    // For top/bottom tooltips, always adjust horizontally (they don't overlap horizontally).
    // For left/right tooltips, check for overlap before adjusting to avoid landing on the target.
    const globalX = targetGlobal.x + newAnchorX;
    const effectiveDirection = direction === "auto" ? selectedPosition.dir : direction;
    const isHorizontalTooltip = (effectiveDirection === "top" || effectiveDirection === "bottom");

    if (globalX < 0) {
      const adjustedX = -targetGlobal.x + margin;
      if (isHorizontalTooltip) {
        newAnchorX = adjustedX;
      } else {
        const wouldOverlap = adjustedX < targetWidth && adjustedX + tipWidth > 0;
        if (!wouldOverlap)
          newAnchorX = adjustedX;
      }
    } else if (globalX + tipWidth > screenWidth) {
      const adjustedX = screenWidth - targetGlobal.x - tipWidth - margin;
      if (isHorizontalTooltip) {
        newAnchorX = adjustedX;
      } else {
        const wouldOverlap = adjustedX < targetWidth && adjustedX + tipWidth > 0;
        if (!wouldOverlap)
          newAnchorX = adjustedX;
      }
    }

    // Adjust vertical position to keep tooltip on screen.
    // For left/right tooltips, always adjust vertically (they don't overlap vertically).
    // For top/bottom tooltips, check for overlap before adjusting to avoid landing on the target.
    const globalY = targetGlobal.y + newAnchorY;
    const isVerticalTooltip = (effectiveDirection === "left" || effectiveDirection === "right");

    if (globalY < 0) {
      const adjustedY = -targetGlobal.y + margin;
      if (isVerticalTooltip) {
        newAnchorY = adjustedY;
      } else {
        const wouldOverlap = adjustedY < targetHeight && adjustedY + tipHeight > 0;
        if (!wouldOverlap)
          newAnchorY = adjustedY;
      }
    } else if (globalY + tipHeight > screenHeight) {
      const adjustedY = screenHeight - targetGlobal.y - tipHeight - margin;
      if (isVerticalTooltip) {
        newAnchorY = adjustedY;
      } else {
        const wouldOverlap = adjustedY < targetHeight && adjustedY + tipHeight > 0;
        if (!wouldOverlap)
          newAnchorY = adjustedY;
      }
    }

    // Apply position first (before making visible)
    // Use floor for negative values to push tooltip away from target
    anchorX = newAnchorX < 0 ? Math.floor(newAnchorX) : Math.round(newAnchorX);
    anchorY = newAnchorY < 0 ? Math.floor(newAnchorY) : Math.round(newAnchorY);
    isPositioned = true;

    // Now make visible and start animation
    root.visible = true;
    showAnimation.start();
  }

  // Function to hide tooltip
  function hide() {
    // Stop show timer if it's running
    showTimer.stop();

    // Stop hide timer if it's running
    hideTimer.stop();

    if (hideDelay > 0 && visible && !animatingOut) {
      hideTimer.start();
    } else {
      startHideAnimation();
    }
  }

  function startHideAnimation() {
    if (!visible || animatingOut)
      return;
    animatingOut = true;
    showAnimation.stop(); // Stop show animation if running
    hideAnimation.start();
  }

  function completeHide() {
    visible = false;
    animatingOut = false;
    text = "";
    rows = null;
    isPositioned = false;
    tooltipContainer.opacity = 1.0;
    tooltipContainer.scale = 1.0;
  }

  // Quick hide without delay or animation
  function hideImmediately() {
    showTimer.stop();
    hideTimer.stop();
    showAnimation.stop();
    hideAnimation.stop();
    animatingOut = false;
    completeHide();
  }

  // Update content function (supports both text and rows)
  function updateContent(newContent) {
    if (visible && targetItem) {
      if (Array.isArray(newContent)) {
        rows = newContent;
        text = "";
      } else {
        // Convert \n to <br> for RichText format
        const processedText = newContent.replace(/\n/g, '<br>');
        text = processedText;
        rows = null;
      }

      // Defer dimension recalculation to allow GridLayout to update
      Qt.callLater(updateContentDeferred);
    }
  }

  function updateContentDeferred() {
    if (!visible || !targetItem) {
      return;
    }

    // Recalculate dimensions based on content mode
    // Use calculateGridSize() for consistency with initial show
    let contentWidth, contentHeight;
    if (isGridMode) {
      const gridSize = calculateGridSize();
      contentWidth = gridSize.width;
      contentHeight = gridSize.height;
    } else {
      contentWidth = tooltipText.implicitWidth;
      contentHeight = tooltipText.implicitHeight;
    }

    const extraPad = isGridMode ? gridPaddingVertical : 0;
    const tipWidth = Math.ceil(Math.min(contentWidth + ((padding + extraPad) * 2), maxWidth));
    root.implicitWidth = tipWidth;

    const tipHeight = Math.ceil(contentHeight + ((padding + extraPad) * 2));
    root.implicitHeight = tipHeight;

    // Reposition based on current direction (screen-relative)
    // Round all values to avoid sub-pixel positioning issues with fractional scaling
    var targetGlobalAbs = targetItem.mapToGlobal(0, 0);
    var targetGlobal = {
      "x": targetGlobalAbs.x - screenX,
      "y": targetGlobalAbs.y - screenY
    };
    const targetWidth = targetItem.width;
    const targetHeight = targetItem.height;

    // Recalculate base anchor position (center on target for top/bottom, etc.)
    var newAnchorX = anchorX;
    var newAnchorY = anchorY;

    // Determine which direction the tooltip is currently positioned
    // and recalculate the centering for that direction
    var isHorizontalTooltip = false;
    var isVerticalTooltip = false;
    if (anchorY > targetHeight / 2) {
      // Tooltip is below target
      newAnchorX = (targetWidth - tipWidth) / 2;
      isHorizontalTooltip = true;
    } else if (anchorY < -tipHeight / 2) {
      // Tooltip is above target
      newAnchorX = (targetWidth - tipWidth) / 2;
      isHorizontalTooltip = true;
    } else if (anchorX > targetWidth / 2) {
      // Tooltip is to the right
      newAnchorY = (targetHeight - tipHeight) / 2;
      isVerticalTooltip = true;
    } else if (anchorX < -tipWidth / 2) {
      // Tooltip is to the left
      newAnchorY = (targetHeight - tipHeight) / 2;
      isVerticalTooltip = true;
    }

    // Adjust horizontal position to keep tooltip on screen if needed.
    const globalX = targetGlobal.x + newAnchorX;
    if (globalX < 0) {
      const adjustedX = -targetGlobal.x + margin;
      if (isHorizontalTooltip) {
        newAnchorX = adjustedX;
      } else {
        const wouldOverlap = adjustedX < targetWidth && adjustedX + tipWidth > 0;
        if (!wouldOverlap)
          newAnchorX = adjustedX;
      }
    } else if (globalX + tipWidth > screenWidth) {
      const adjustedX = screenWidth - targetGlobal.x - tipWidth - margin;
      if (isHorizontalTooltip) {
        newAnchorX = adjustedX;
      } else {
        const wouldOverlap = adjustedX < targetWidth && adjustedX + tipWidth > 0;
        if (!wouldOverlap)
          newAnchorX = adjustedX;
      }
    }

    // Adjust vertical position to keep tooltip on screen if needed.
    const globalY = targetGlobal.y + newAnchorY;
    if (globalY < 0) {
      const adjustedY = -targetGlobal.y + margin;
      if (isVerticalTooltip) {
        newAnchorY = adjustedY;
      } else {
        const wouldOverlap = adjustedY < targetHeight && adjustedY + tipHeight > 0;
        if (!wouldOverlap)
          newAnchorY = adjustedY;
      }
    } else if (globalY + tipHeight > screenHeight) {
      const adjustedY = screenHeight - targetGlobal.y - tipHeight - margin;
      if (isVerticalTooltip) {
        newAnchorY = adjustedY;
      } else {
        const wouldOverlap = adjustedY < targetHeight && adjustedY + tipHeight > 0;
        if (!wouldOverlap)
          newAnchorY = adjustedY;
      }
    }

    // Apply the new anchor positions
    // Use floor for negative values to push tooltip away from target
    anchorX = newAnchorX < 0 ? Math.floor(newAnchorX) : Math.round(newAnchorX);
    anchorY = newAnchorY < 0 ? Math.floor(newAnchorY) : Math.round(newAnchorY);

    // Force anchor update
    Qt.callLater(() => {
                   if (root.anchor && root.visible) {
                     root.anchor.updateAnchor();
                   }
                 });
  }

  // Backward compatibility alias
  function updateText(newText) {
    updateContent(newText);
  }

  // Reset function to clean up state
  function reset() {
    // Stop all timers and animations
    showTimer.stop();
    hideTimer.stop();
    showAnimation.stop();
    hideAnimation.stop();

    // Clear all state
    visible = false;
    animatingOut = false;
    text = "";
    rows = null;
    isPositioned = false;

    // Reset to defaults
    direction = "auto";
    delay = 0;
    hideDelay = 0;

    // Reset container state
    tooltipContainer.opacity = 1.0;
    tooltipContainer.scale = 1.0;
  }

  // Tooltip content container for animations
  Item {
    id: tooltipContainer
    anchors.fill: parent

    // Animation properties
    opacity: 1.0
    scale: 1.0
    transformOrigin: Item.Center

    Rectangle {
      anchors.fill: parent
      anchors.margins: border.width
      color: Color.mSurface
      border.color: Color.mOutline
      border.width: Style.borderS
      radius: Math.min(Style.radiusS, Math.min(width, height) / 3)

      // Only show content when we have content
      visible: root.text !== "" || root.isGridMode

      // Text content (default mode)
      NText {
        id: tooltipText
        visible: !root.isGridMode
        anchors.centerIn: parent
        anchors.margins: root.padding
        text: root.text
        pointSize: Style.fontSizeS
        family: Settings.data.ui.fontFixed
        color: Color.mOnSurfaceVariant
        horizontalAlignment: Text.AlignHCenter
        verticalAlignment: Text.AlignVCenter
        wrapMode: Text.WordWrap
        width: Math.min(implicitWidth, root.maxWidth - (root.padding * 2))
        richTextEnabled: true
      }

      // Grid content (grid mode)
      GridLayout {
        id: gridContent
        visible: root.isGridMode
        anchors.centerIn: parent
        columns: root.columnCount
        rowSpacing: 0
        columnSpacing: Style.marginM

        Repeater {
          model: root.isGridMode ? [].concat.apply([], root.rows) : []

          NText {
            text: modelData
            pointSize: Style.fontSizeS
            family: tooltipText.family
            color: Color.mOnSurfaceVariant
            Layout.preferredHeight: rowHeightMeasure.implicitHeight
          }
        }
      }
    }
  }

  Component.onCompleted: {
    reset();
  }

  Component.onDestruction: {
    reset();
  }
}
