import { api } from "../../scripts/api.js";
import { app } from "../../scripts/app.js";
import { createMenuItem, createDropIndicator } from "./loras_widget_components.js";
import { parseLoraValue, formatLoraValue, syncClipStrengthIfCollapsed, saveRecipeDirectly, copyToClipboard, showToast, moveLoraByDirection, getDropTargetIndex } from "./loras_widget_utils.js";

// Function to handle strength adjustment via dragging
export function handleStrengthDrag(name, initialStrength, initialX, event, widget, isClipStrength = false, updateWidget = true) {
  // Calculate drag sensitivity (how much the strength changes per pixel)
  // Using 0.01 per 10 pixels of movement
  const sensitivity = 0.001;
  
  // Get the current mouse position
  const currentX = event.clientX;
  
  // Calculate the distance moved
  const deltaX = currentX - initialX;
  
  // Calculate the new strength value based on movement
  // Moving right increases, moving left decreases
  let newStrength = Number(initialStrength) + (deltaX * sensitivity);
  
  // Limit the strength to reasonable bounds (now between -10 and 10)
  newStrength = Math.max(-10, Math.min(10, newStrength));
  newStrength = Number(newStrength.toFixed(2));
  
  // Update the lora data
  const lorasData = parseLoraValue(widget.value);
  const loraIndex = lorasData.findIndex(l => l.name === name);
  
  if (loraIndex >= 0) {
    // Update the appropriate strength property based on isClipStrength flag
    if (isClipStrength) {
      lorasData[loraIndex].clipStrength = newStrength;
    } else {
      lorasData[loraIndex].strength = newStrength;
      // Sync clipStrength if collapsed
      syncClipStrengthIfCollapsed(lorasData[loraIndex]);
    }
    
    // Always write back to widget.value to persist the mutation.
    // During drag (updateWidget=false), setValue skips renderLoras via __dragActive flag,
    // so the DOM survives and pointer capture is preserved.
    widget.value = formatLoraValue(lorasData);
    
    // Only fire callback on the final commit, not during drag
    if (updateWidget && widget.callback) {
      widget.callback(widget.value);
    }
  }
  
  return newStrength;
}

// Function to handle proportional strength adjustment for all LoRAs via header dragging
export function handleAllStrengthsDrag(initialStrengths, initialX, event, widget, updateWidget = true) {
  // Define sensitivity (less sensitive than individual adjustment)
  const sensitivity = 0.0005;
  
  // Get current mouse position
  const currentX = event.clientX;
  
  // Calculate the distance moved
  const deltaX = currentX - initialX;
  
  // Calculate adjustment factor (1.0 means no change, >1.0 means increase, <1.0 means decrease)
  // For positive deltaX, we want to increase strengths, for negative we want to decrease
  const adjustmentFactor = 1.0 + (deltaX * sensitivity);
  
  // Ensure adjustment factor is reasonable (prevent extreme changes)
  const limitedFactor = Math.max(0.01, Math.min(3.0, adjustmentFactor));
  
  // Get current loras data
  const lorasData = parseLoraValue(widget.value);
  
  // Apply the adjustment factor to each LoRA's strengths
  lorasData.forEach((loraData, index) => {
    // Get initial strengths for this LoRA
    const initialModelStrength = initialStrengths[index].modelStrength;
    const initialClipStrength = initialStrengths[index].clipStrength;
    
    // Apply the adjustment factor to both strengths
    let newModelStrength = (initialModelStrength * limitedFactor).toFixed(2);
    let newClipStrength = (initialClipStrength * limitedFactor).toFixed(2);
    
    // Limit the values to reasonable bounds (-10 to 10)
    newModelStrength = Math.max(-10, Math.min(10, newModelStrength));
    newClipStrength = Math.max(-10, Math.min(10, newClipStrength));
    
    // Update strengths
    lorasData[index].strength = Number(newModelStrength);
    lorasData[index].clipStrength = Number(newClipStrength);
  });
  
  // Always write back to widget.value to persist mutations.
  // During drag (updateWidget=false), setValue skips renderLoras via __dragActive flag.
  widget.value = formatLoraValue(lorasData);
  
  // Only fire callback on the final commit, not during drag
  if (updateWidget && widget.callback) {
    widget.callback(widget.value);
  }
}

// Function to initialize drag operation
export function initDrag(
  dragEl,
  name,
  widget,
  isClipStrength = false,
  previewTooltip,
  renderFunction,
  dragCallbacks = {}
) {
  let isDragging = false;
  let activePointerId = null;
  let initialX = 0;
  let initialStrength = 0;
  let currentDragElement = null;
  let hasMoved = false;
  const { onDragStart, onDragEnd } = dragCallbacks;
  
  // Create a drag handler using pointer events for Vue DOM render mode compatibility
  dragEl.addEventListener('pointerdown', (e) => {
    // Skip if clicking on toggle or strength control areas
    if (e.target.closest('.lm-lora-toggle') ||
        e.target.closest('input') ||
        e.target.closest('.lm-lora-arrow') ||
        e.target.closest('.lm-lora-drag-handle') ||
        e.target.closest('.lm-lora-lock-button') ||
        e.target.closest('.lm-lora-expand-button')) {
      return;
    }

    // Only handle left mouse button (allow middle button for canvas drag, right button for context menu)
    if (e.button !== 0) {
      return;
    }

    // Store initial values
    const lorasData = parseLoraValue(widget.value);
    const loraData = lorasData.find(l => l.name === name);
    
    if (!loraData) return;
    
    initialX = e.clientX;
    initialStrength = isClipStrength ? loraData.clipStrength : loraData.strength;
    isDragging = true;
    hasMoved = false;
    activePointerId = e.pointerId;
    currentDragElement = e.currentTarget;

    // Suppress renderLoras in setValue during drag so the DOM survives.
    // The getter creates a new array on every read, so mutations to a
    // parsed copy are lost unless we write back through widget.value.
    // Writing back would normally trigger a full DOM re-render via setValue,
    // destroying pointer capture. __dragActive tells setValue to skip the render.
    widget.__dragActive = true;

    // Capture pointer to receive all subsequent events regardless of stopPropagation
    const target = e.currentTarget;
    target.setPointerCapture(e.pointerId);

    // Prevent text selection
    e.preventDefault();
  });
  
  dragEl.addEventListener('pointermove', (e) => {
    if (!isDragging) return;

    // Track if pointer moved significantly (more than 3 pixels)
    if (Math.abs(e.clientX - initialX) > 3) {
      hasMoved = true;
    }

    // Only stop propagation if we've started dragging (moved beyond threshold)
    if (hasMoved) {
      e.stopPropagation();
    }
    
    // Only process drag if we've moved beyond threshold
    if (!hasMoved) return;

    // Add class to body to enforce cursor style globally only after drag starts
    document.body.classList.add('lm-lora-strength-dragging');

    if (typeof onDragStart === 'function') {
      onDragStart();
    }

    // Call the strength adjustment function without updating widget.value during drag
    const newStrength = handleStrengthDrag(name, initialStrength, initialX, e, widget, isClipStrength, false);
    
    // Update strength input directly instead of re-rendering to avoid losing event listeners
    const strengthInput = currentDragElement.querySelector('.lm-lora-strength-input');
    if (strengthInput && typeof newStrength === 'number') {
      strengthInput.value = newStrength.toFixed(2);
    }
    
    // Prevent showing the preview tooltip during drag
    if (previewTooltip) {
      previewTooltip.hide();
    }
  });
  
  const endDrag = (e) => {
    if (!isDragging) return;

    // Only stop propagation if we actually dragged
    if (hasMoved) {
      e.stopPropagation();
    }

    // Release pointer capture if still have the element
    if (currentDragElement && activePointerId !== null) {
      try {
        currentDragElement.releasePointerCapture(activePointerId);
      } catch (err) {
        // Ignore errors if element is no longer in DOM
      }
    }

    const wasDragging = hasMoved;
    isDragging = false;
    hasMoved = false;
    activePointerId = null;
    currentDragElement = null;

    // Remove the class to restore normal cursor behavior
    document.body.classList.remove('lm-lora-strength-dragging');

    // Only call onDragEnd and re-render if we actually dragged.
    // try-finally guarantees __dragActive is always cleared, preventing a
    // permanent UI freeze if onDragEnd or setValue throws during cleanup.
    try {
      if (wasDragging) {
        if (typeof onDragEnd === 'function') {
          onDragEnd();
        }

        // Re-enable renderLoras in setValue and flush final value through setter.
        // The last handleStrengthDrag call already wrote the final strength to
        // widgetValue via setValue (with render suppressed). widget.value = widget.value
        // triggers setValue again, which now calls renderLoras since __dragActive is false.
        widget.__dragActive = false;
        widget.value = widget.value;
        if (typeof widget.callback === 'function') {
          widget.callback(widget.value);
        }
      }
    } finally {
      widget.__dragActive = false;
    }
  };

  dragEl.addEventListener('pointerup', endDrag);
  dragEl.addEventListener('pointercancel', endDrag);
}

// Function to initialize header drag for proportional strength adjustment
export function initHeaderDrag(headerEl, widget, renderFunction) {
  let isDragging = false;
  let activePointerId = null;
  let initialX = 0;
  let initialStrengths = [];
  let currentHeaderElement = null;
  let hasMoved = false;
  
  // Add cursor style to indicate draggable
  // Create a drag handler using pointer events for Vue DOM render mode compatibility
  headerEl.addEventListener('pointerdown', (e) => {
    // Skip if clicking on toggle or other interactive elements
    if (e.target.closest('.lm-lora-toggle') ||
        e.target.closest('input')) {
      return;
    }

    // Only handle left mouse button (allow middle button for canvas drag, right button for context menu)
    if (e.button !== 0) {
      return;
    }

    // Store initial X position
    initialX = e.clientX;
    
    // Store initial strengths of all LoRAs
    const lorasData = parseLoraValue(widget.value);
    initialStrengths = lorasData.map(lora => ({
      modelStrength: Number(lora.strength),
      clipStrength: Number(lora.clipStrength)
    }));
    
    isDragging = true;
    hasMoved = false;
    activePointerId = e.pointerId;
    currentHeaderElement = e.currentTarget;

    // Suppress renderLoras in setValue during drag (see initDrag for rationale)
    widget.__dragActive = true;

    // Capture pointer to receive all subsequent events regardless of stopPropagation
    const target = e.currentTarget;
    target.setPointerCapture(e.pointerId);
    
    // Prevent text selection
    e.preventDefault();
  });
  
  // Handle pointer move for dragging
  headerEl.addEventListener('pointermove', (e) => {
    if (!isDragging) return;

    // Track if pointer moved significantly (more than 3 pixels)
    if (Math.abs(e.clientX - initialX) > 3) {
      hasMoved = true;
    }

    // Only stop propagation if we've started dragging (moved beyond threshold)
    if (hasMoved) {
      e.stopPropagation();
    }

    // Only process drag if we've moved beyond threshold
    if (!hasMoved) return;

    // Add class to body to enforce cursor style globally only after drag starts
    document.body.classList.add('lm-lora-strength-dragging');

    // Call the strength adjustment function without updating widget.value during drag
    handleAllStrengthsDrag(initialStrengths, initialX, e, widget, false);
    
    // Update strength inputs directly instead of re-rendering to avoid losing event listeners
    const strengthInputs = currentHeaderElement.parentElement.querySelectorAll('.lm-lora-strength-input');
    const lorasData = parseLoraValue(widget.value);
    strengthInputs.forEach((input, index) => {
      if (lorasData[index]) {
        input.value = lorasData[index].strength.toFixed(2);
      }
    });
  });
  
  const endDrag = (e) => {
    if (!isDragging) return;

    // Only stop propagation if we actually dragged
    if (hasMoved) {
      e.stopPropagation();
    }

    // Release pointer capture if still have the element
    if (currentHeaderElement && activePointerId !== null) {
      try {
        currentHeaderElement.releasePointerCapture(activePointerId);
      } catch (err) {
        // Ignore errors if element is no longer in DOM
      }
    }

    const wasDragging = hasMoved;
    isDragging = false;
    hasMoved = false;
    activePointerId = null;
    currentHeaderElement = null;
    
    // Remove the class to restore normal cursor behavior
    document.body.classList.remove('lm-lora-strength-dragging');

    // Only re-render if we actually dragged.
    // try-finally guarantees __dragActive is always cleared, preventing a
    // permanent UI freeze if setValue throws during cleanup.
    try {
      if (wasDragging) {
        // Re-enable renderLoras in setValue and flush final value through setter
        widget.__dragActive = false;
        widget.value = widget.value;
        if (typeof widget.callback === 'function') {
          widget.callback(widget.value);
        }
      }
    } finally {
      widget.__dragActive = false;
    }
  };

  // Handle pointer up to end dragging
  headerEl.addEventListener('pointerup', endDrag);
  
  // Handle pointer cancel to end dragging
  headerEl.addEventListener('pointercancel', endDrag);
}

// Function to initialize drag-and-drop for reordering
export function initReorderDrag(dragHandle, loraName, widget, renderFunction) {
  let isDragging = false;
  let activePointerId = null;
  let draggedElement = null;
  let dropIndicator = null;
  let container = null;
  let scale = 1;
  let hasMoved = false;
  
  dragHandle.addEventListener('pointerdown', (e) => {
    e.preventDefault();
    
    isDragging = true;
    hasMoved = false;
    activePointerId = e.pointerId;
    draggedElement = dragHandle.closest('.lm-lora-entry');
    container = draggedElement.parentElement;
    
    // Capture pointer to receive all subsequent events regardless of stopPropagation
    const target = e.currentTarget;
    target.setPointerCapture(e.pointerId);
  });

  dragHandle.addEventListener('pointermove', (e) => {
    if (!isDragging || !draggedElement) return;

    // Track if pointer moved significantly (more than 3 pixels vertically)
    if (Math.abs(e.movementY) > 3) {
      hasMoved = true;
    }

    // Only stop propagation and process drag if we've moved beyond threshold
    if (!hasMoved) return;

    // Stop propagation and start drag visuals
    e.stopPropagation();

    // Add dragging class and visual feedback only after drag starts
    if (!dropIndicator) {
      draggedElement.classList.add('lm-lora-entry--dragging');

      // Create single drop indicator with absolute positioning
      dropIndicator = createDropIndicator();
      
      // Make container relatively positioned for absolute indicator
      const originalPosition = container.style.position;
      container.style.position = 'relative';
      container.appendChild(dropIndicator);
      
      // Store original position for cleanup
      container._originalPosition = originalPosition;
      
      // Add global cursor style
      document.body.classList.add('lm-lora-reordering');

      // Store workflow scale for accurate positioning
      scale = app.canvas.ds.scale;
    }
    
    const targetIndex = getDropTargetIndex(container, e.clientY);
    const entries = container.querySelectorAll('.lm-lora-entry, .lm-lora-clip-entry');
    
    if (targetIndex === 0) {
      // Show at top
      const firstEntry = entries[0];
      if (firstEntry) {
        const rect = firstEntry.getBoundingClientRect();
        const containerRect = container.getBoundingClientRect();
        // Convert GBCR visual offset to container-local space (rect/containerRect are post-scale,
        // scrollTop is pre-scale), so only the visual-diff portion is divided by scale
        dropIndicator.style.top = `${(rect.top - containerRect.top) / scale + container.scrollTop - 2}px`;
        dropIndicator.style.opacity = '1';
      }
    } else if (targetIndex < entries.length) {
      // Show between entries
      const targetEntry = entries[targetIndex];
      if (targetEntry) {
        const rect = targetEntry.getBoundingClientRect();
        const containerRect = container.getBoundingClientRect();
        dropIndicator.style.top = `${(rect.top - containerRect.top) / scale + container.scrollTop - 2}px`;
        dropIndicator.style.opacity = '1';
      }
    } else {
      // Show at bottom
      const lastEntry = entries[entries.length - 1];
      if (lastEntry) {
        const rect = lastEntry.getBoundingClientRect();
        const containerRect = container.getBoundingClientRect();
        dropIndicator.style.top = `${(rect.bottom - containerRect.top) / scale + container.scrollTop + 2}px`;
        dropIndicator.style.opacity = '1';
      }
    }
  });
  
  dragHandle.addEventListener('pointerup', (e) => {
    // Only stop propagation if we actually dragged
    if (hasMoved) {
      e.stopPropagation();
    }

    // Always reset cursor regardless of isDragging state
    document.body.classList.remove('lm-lora-reordering');

    if (!isDragging || !draggedElement) {
      // Release pointer capture even if not dragging
      const target = e.currentTarget;
      if (activePointerId !== null) {
        target.releasePointerCapture(activePointerId);
      }
      isDragging = false;
      hasMoved = false;
      activePointerId = null;
      return;
    }

    // Release pointer capture
    const target = e.currentTarget;
    if (activePointerId !== null) {
      target.releasePointerCapture(activePointerId);
    }

    const wasDragging = hasMoved;
    isDragging = false;
    hasMoved = false;
    activePointerId = null;

    // Only process reordering if we actually dragged
    if (wasDragging) {
      const targetIndex = getDropTargetIndex(container, e.clientY);

      // Get current LoRA data
      const lorasData = parseLoraValue(widget.value);
      const currentIndex = lorasData.findIndex(l => l.name === loraName);

      if (currentIndex !== -1 && currentIndex !== targetIndex) {
        // Calculate actual target index (excluding clip entries from count)
        const loraEntries = container.querySelectorAll('.lm-lora-entry');
        let actualTargetIndex = targetIndex;

        // Adjust target index if it's beyond the number of actual LoRA entries
        if (actualTargetIndex > loraEntries.length) {
          actualTargetIndex = loraEntries.length;
        }

        // Move the LoRA
        const newLoras = [...lorasData];
        const [moved] = newLoras.splice(currentIndex, 1);
        newLoras.splice(actualTargetIndex > currentIndex ? actualTargetIndex - 1 : actualTargetIndex, 0, moved);

        widget.value = formatLoraValue(newLoras);

        if (widget.callback) {
          widget.callback(widget.value);
        }

        // Re-render
        if (renderFunction) {
          renderFunction(widget.value, widget);
        }
      }
    }

    // Cleanup
    if (draggedElement) {
      draggedElement.classList.remove('lm-lora-entry--dragging');
      draggedElement = null;
    }

    if (dropIndicator && container) {
      container.removeChild(dropIndicator);
      // Restore original position
      container.style.position = container._originalPosition || '';
      delete container._originalPosition;
      dropIndicator = null;
    }

    container = null;
  });

  dragHandle.addEventListener('pointercancel', (e) => {
    // Only stop propagation if we actually dragged
    if (hasMoved) {
      e.stopPropagation();
    }

    // Always reset cursor regardless of isDragging state
    document.body.classList.remove('lm-lora-reordering');

    if (!isDragging || !draggedElement) {
      // Release pointer capture even if not dragging
      const target = e.currentTarget;
      if (activePointerId !== null) {
        target.releasePointerCapture(activePointerId);
      }
      isDragging = false;
      hasMoved = false;
      activePointerId = null;
      return;
    }

    // Release pointer capture
    const target = e.currentTarget;
    if (activePointerId !== null) {
      target.releasePointerCapture(activePointerId);
    }

    isDragging = false;
    hasMoved = false;
    activePointerId = null;

    // Cleanup without reordering
    if (draggedElement) {
      draggedElement.classList.remove('lm-lora-entry--dragging');
      draggedElement = null;
    }

    if (dropIndicator && container) {
      container.removeChild(dropIndicator);
      // Restore original position
      container.style.position = container._originalPosition || '';
      delete container._originalPosition;
      dropIndicator = null;
    }

    container = null;
  });
}

// Function to handle keyboard navigation
export function handleKeyboardNavigation(event, selectedLora, widget, renderFunction, selectLora) {
  if (!selectedLora) return false;

  const lorasData = parseLoraValue(widget.value);
  let handled = false;
  const isStrengthInputFocused =
    event?.target?.classList?.contains('lm-lora-strength-input') ?? false;
  
  // Check for Ctrl/Cmd modifier for reordering
  if (event.ctrlKey || event.metaKey) {
    switch (event.key) {
      case 'ArrowUp':
        event.preventDefault();
        const newLorasUp = moveLoraByDirection(lorasData, selectedLora, 'up');
        widget.value = formatLoraValue(newLorasUp);
        if (widget.callback) widget.callback(widget.value);
        if (renderFunction) renderFunction(widget.value, widget);
        handled = true;
        break;
        
      case 'ArrowDown':
        event.preventDefault();
        const newLorasDown = moveLoraByDirection(lorasData, selectedLora, 'down');
        widget.value = formatLoraValue(newLorasDown);
        if (widget.callback) widget.callback(widget.value);
        if (renderFunction) renderFunction(widget.value, widget);
        handled = true;
        break;
        
      case 'Home':
        event.preventDefault();
        const newLorasTop = moveLoraByDirection(lorasData, selectedLora, 'top');
        widget.value = formatLoraValue(newLorasTop);
        if (widget.callback) widget.callback(widget.value);
        if (renderFunction) renderFunction(widget.value, widget);
        handled = true;
        break;
        
      case 'End':
        event.preventDefault();
        const newLorasBottom = moveLoraByDirection(lorasData, selectedLora, 'bottom');
        widget.value = formatLoraValue(newLorasBottom);
        if (widget.callback) widget.callback(widget.value);
        if (renderFunction) renderFunction(widget.value, widget);
        handled = true;
        break;
    }
  } else {
    // Normal navigation without Ctrl/Cmd
    switch (event.key) {
      case 'ArrowUp':
        event.preventDefault();
        const currentIndex = lorasData.findIndex(l => l.name === selectedLora);
        if (currentIndex > 0) {
          selectLora(lorasData[currentIndex - 1].name);
        }
        handled = true;
        break;
        
      case 'ArrowDown':
        event.preventDefault();
        const currentIndexDown = lorasData.findIndex(l => l.name === selectedLora);
        if (currentIndexDown < lorasData.length - 1) {
          selectLora(lorasData[currentIndexDown + 1].name);
        }
        handled = true;
        break;
        
      case 'Delete':
      case 'Backspace':
        if (isStrengthInputFocused) {
          break;
        }
        event.preventDefault();
        const filtered = lorasData.filter(l => l.name !== selectedLora);
        widget.value = formatLoraValue(filtered);
        if (widget.callback) widget.callback(widget.value);
        if (renderFunction) renderFunction(widget.value, widget);
        selectLora(null); // Clear selection
        handled = true;
        break;
    }
  }
  
  return handled;
}

// Function to create context menu
export function createContextMenu(x, y, loraName, widget, previewTooltip, renderFunction) {
  // Hide preview tooltip first
  if (previewTooltip) {
    previewTooltip.hide();
  }

  // Remove existing context menu if any
  const existingMenu = document.querySelector('.lm-lora-context-menu');
  if (existingMenu) {
    existingMenu.remove();
  }

  const menu = document.createElement('div');
  menu.className = 'lm-lora-context-menu';
  menu.style.left = `${x}px`;
  menu.style.top = `${y}px`;
  // Render off-screen until measured so we can adjust for viewport edges
  menu.style.visibility = 'hidden';

  // View on Civitai option with globe icon
  const viewOnCivitaiOption = createMenuItem(
    'View on Civitai',
    '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><line x1="2" y1="12" x2="22" y2="12"></line><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></svg>',
    async () => {
      menu.remove();
      document.removeEventListener('click', closeMenu);
      
      try {
        // Get Civitai URL from API
        const response = await api.fetchApi(`/lm/loras/civitai-url?name=${encodeURIComponent(loraName)}`, {
          method: 'GET'
        });
        
        if (!response.ok) {
          const errorText = await response.text();
          throw new Error(errorText || 'Failed to get Civitai URL');
        }
        
        const data = await response.json();
        if (data.success && data.civitai_url) {
          // Open the URL in a new tab
          window.open(data.civitai_url, '_blank');
        } else {
          // Show error message if no Civitai URL
          showToast('This LoRA has no associated Civitai URL', 'warning');
        }
      } catch (error) {
        console.error('Error getting Civitai URL:', error);
        showToast(error.message || 'Failed to get Civitai URL', 'error');
      }
    }
  );

  // Delete option with trash icon
  const deleteOption = createMenuItem(
    'Delete', 
    '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18m-2 0v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6m3 0V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path></svg>',
    () => {
      menu.remove();
      document.removeEventListener('click', closeMenu);
      
      const lorasData = parseLoraValue(widget.value).filter(l => l.name !== loraName);
      widget.value = formatLoraValue(lorasData);

      if (widget.callback) {
        widget.callback(widget.value);
      }
      
      // Re-render
      if (renderFunction) {
        renderFunction(widget.value, widget);
      }
    }
  );

  // New option: Copy Notes with note icon
  const copyNotesOption = createMenuItem(
    'Copy Notes',
    '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>',
    async () => {
      menu.remove();
      document.removeEventListener('click', closeMenu);
      
      try {
        // Get notes from API
        const response = await api.fetchApi(`/lm/loras/get-notes?name=${encodeURIComponent(loraName)}`, {
          method: 'GET'
        });
        
        if (!response.ok) {
          const errorText = await response.text();
          throw new Error(errorText || 'Failed to get notes');
        }
        
        const data = await response.json();
        if (data.success) {
          const notes = data.notes || '';
          if (notes.trim()) {
            await copyToClipboard(notes, 'Notes copied to clipboard');
          } else {
            showToast('No notes available for this LoRA', 'info');
          }
        } else {
          throw new Error(data.error || 'Failed to get notes');
        }
      } catch (error) {
        console.error('Error getting notes:', error);
        showToast(error.message || 'Failed to get notes', 'error');
      }
    }
  );

  // New option: Copy Trigger Words with tag icon
  const copyTriggerWordsOption = createMenuItem(
    'Copy Trigger Words',
    '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"></path><line x1="7" y1="7" x2="7.01" y2="7"></line></svg>',
    async () => {
      menu.remove();
      document.removeEventListener('click', closeMenu);
      
      try {
        // Get trigger words from API
        const response = await api.fetchApi(`/lm/loras/get-trigger-words?name=${encodeURIComponent(loraName)}`, {
          method: 'GET'
        });
        
        if (!response.ok) {
          const errorText = await response.text();
          throw new Error(errorText || 'Failed to get trigger words');
        }
        
        const data = await response.json();
        if (data.success) {
          const triggerWords = data.trigger_words || [];
          if (triggerWords.length > 0) {
            // Join trigger words with commas
            const triggerWordsText = triggerWords.join(', ');
            await copyToClipboard(triggerWordsText, 'Trigger words copied to clipboard');
          } else {
            showToast('No trigger words available for this LoRA', 'info');
          }
        } else {
          throw new Error(data.error || 'Failed to get trigger words');
        }
      } catch (error) {
        console.error('Error getting trigger words:', error);
        showToast(error.message || 'Failed to get trigger words', 'error');
      }
    }
  );

  // Save recipe option with bookmark icon
  const saveOption = createMenuItem(
    'Save Recipe',
    '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"></path></svg>',
    () => {
      menu.remove();
      document.removeEventListener('click', closeMenu);
      saveRecipeDirectly();
    }
  );

  // Move Up option with arrow up icon
  const moveUpOption = createMenuItem(
    'Move Up',
    '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 15l-6-6-6 6"></path></svg>',
    () => {
      menu.remove();
      document.removeEventListener('click', closeMenu);
      
      const lorasData = parseLoraValue(widget.value);
      const newLoras = moveLoraByDirection(lorasData, loraName, 'up');
      widget.value = formatLoraValue(newLoras);
      
      if (widget.callback) {
        widget.callback(widget.value);
      }
      
      if (renderFunction) {
        renderFunction(widget.value, widget);
      }
    }
  );

  // Move Down option with arrow down icon
  const moveDownOption = createMenuItem(
    'Move Down',
    '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 9l6 6 6-6"></path></svg>',
    () => {
      menu.remove();
      document.removeEventListener('click', closeMenu);
      
      const lorasData = parseLoraValue(widget.value);
      const newLoras = moveLoraByDirection(lorasData, loraName, 'down');
      widget.value = formatLoraValue(newLoras);
      
      if (widget.callback) {
        widget.callback(widget.value);
      }
      
      if (renderFunction) {
        renderFunction(widget.value, widget);
      }
    }
  );

  // Move to Top option with chevrons up icon
  const moveTopOption = createMenuItem(
    'Move to Top',
    '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 11l-5-5-5 5M17 18l-5-5-5 5"></path></svg>',
    () => {
      menu.remove();
      document.removeEventListener('click', closeMenu);
      
      const lorasData = parseLoraValue(widget.value);
      const newLoras = moveLoraByDirection(lorasData, loraName, 'top');
      widget.value = formatLoraValue(newLoras);
      
      if (widget.callback) {
        widget.callback(widget.value);
      }
      
      if (renderFunction) {
        renderFunction(widget.value, widget);
      }
    }
  );

  // Move to Bottom option with chevrons down icon
  const moveBottomOption = createMenuItem(
    'Move to Bottom',
    '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M7 13l5 5 5-5M7 6l5 5 5-5"></path></svg>',
    () => {
      menu.remove();
      document.removeEventListener('click', closeMenu);
      
      const lorasData = parseLoraValue(widget.value);
      const newLoras = moveLoraByDirection(lorasData, loraName, 'bottom');
      widget.value = formatLoraValue(newLoras);
      
      if (widget.callback) {
        widget.callback(widget.value);
      }
      
      if (renderFunction) {
        renderFunction(widget.value, widget);
      }
    }
  );

  // Add separator
  const separator1 = document.createElement('hr');

  // Add second separator
  const separator2 = document.createElement('hr');

  // Add separator for order options
  const orderSeparator = document.createElement('hr');

  menu.appendChild(viewOnCivitaiOption);
  menu.appendChild(deleteOption);
  menu.appendChild(separator1);
  menu.appendChild(moveUpOption);
  menu.appendChild(moveDownOption);
  menu.appendChild(moveTopOption);
  menu.appendChild(moveBottomOption);
  menu.appendChild(orderSeparator);
  menu.appendChild(copyNotesOption);
  menu.appendChild(copyTriggerWordsOption);
  menu.appendChild(separator2);
  menu.appendChild(saveOption);
  
  document.body.appendChild(menu);

  // Flip/clamp position so the menu stays fully inside the viewport
  const VIEWPORT_MARGIN = 8;
  const menuRect = menu.getBoundingClientRect();
  let menuLeft = x;
  let menuTop = y;
  if (menuLeft + menuRect.width > window.innerWidth - VIEWPORT_MARGIN) {
    menuLeft = Math.max(VIEWPORT_MARGIN, window.innerWidth - menuRect.width - VIEWPORT_MARGIN);
  }
  if (menuTop + menuRect.height > window.innerHeight - VIEWPORT_MARGIN) {
    menuTop = Math.max(VIEWPORT_MARGIN, window.innerHeight - menuRect.height - VIEWPORT_MARGIN);
  }
  menu.style.left = `${menuLeft}px`;
  menu.style.top = `${menuTop}px`;
  menu.style.visibility = '';

  // Close menu when clicking outside
  const closeMenu = (e) => {
    if (!menu.contains(e.target)) {
      menu.remove();
      document.removeEventListener('click', closeMenu);
    }
  };
  setTimeout(() => document.addEventListener('click', closeMenu), 0);
}
