// ===================================================================
// UTILITY FUNCTIONS
// ===================================================================

// ===================================================================
// MEMOIZATION UTILITY - Generic pattern for initialization functions
// ===================================================================

/**
 * Creates a memoized version of an initialization function with debouncing.
 * Returns an object with the memoized function and a reset function.
 *
 * @param {Function} fn - The initialization function to memoize
 * @param {number} debounceMs - Debounce delay in milliseconds (default: 300)
 * @param {string} name - Name for logging purposes
 * @returns {Object} Object with { init, debouncedInit, reset } functions
 */
export function createMemoizedInit(fn, debounceMs = 300, name = "Init") {
  // Closure variables (private state)
  let initialized = false;
  let initializing = false;
  let debounceTimeout = null;

  /**
   * Memoized initialization function with guards and debouncing
   */
  const memoizedInit = function (...args) {
    // Guard: Prevent re-initialization if already initialized
    if (initialized) {
      console.log(`✓ ${name} already initialized, skipping...`);
      return Promise.resolve();
    }

    // Guard: Prevent concurrent initialization
    if (initializing) {
      console.log(`⏳ ${name} initialization already in progress, skipping...`);
      return Promise.resolve();
    }

    // Clear any pending debounced call
    if (debounceTimeout) {
      clearTimeout(debounceTimeout);
      debounceTimeout = null;
    }

    // Mark as initializing
    initializing = true;
    console.log(`🔍 Initializing ${name}...`);

    try {
      // Call the actual initialization function
      const result = fn.apply(this, args);

      // Mark as initialized
      initialized = true;
      console.log(`✅ ${name} initialization complete`);

      return Promise.resolve(result);
    } catch (error) {
      console.error(`❌ Error initializing ${name}:`, error);
      // Don't mark as initialized on error, allow retry
      return Promise.reject(error);
    } finally {
      initializing = false;
    }
  };

  /**
   * Debounced version of the memoized init function
   */
  const debouncedInit = function (...args) {
    // Clear any existing timeout
    if (debounceTimeout) {
      clearTimeout(debounceTimeout);
    }

    // Set new timeout
    debounceTimeout = setTimeout(() => {
      memoizedInit.apply(this, args);
      debounceTimeout = null;
    }, debounceMs);
  };

  /**
   * Reset the initialization state
   * Call this when you need to re-initialize (e.g., after destroying elements)
   */
  const reset = function () {
    // Clear any pending debounced call
    if (debounceTimeout) {
      clearTimeout(debounceTimeout);
      debounceTimeout = null;
    }

    initialized = false;
    initializing = false;
    console.log(`🔄 ${name} state reset`);
  };

  return {
    init: memoizedInit,
    debouncedInit,
    reset,
  };
}

// Safe element getter with logging
export function safeGetElement(id, suppressWarning = false) {
  try {
    const element = document.getElementById(id);
    if (!element && !suppressWarning) {
      console.warn(`Element with id "${id}" not found`);
    }
    return element;
  } catch (error) {
    console.error(`Error getting element "${id}":`, error);
    return null;
  }
}

export function safeSetValue(id, val) {
  const el = safeGetElement(id);
  if (el) {
    el.value = val;
  }
}

// Check for inactive items
export function isInactiveChecked(type) {
  const checkbox = safeGetElement(`show-inactive-${type}`);
  return checkbox ? checkbox.checked : false;
}

// Enhanced fetch with timeout and better error handling
export async function fetchWithTimeout(
  url,
  options = {},
  timeout = window.MCPGATEWAY_UI_TOOL_TEST_TIMEOUT || 60000
) {
  // Use configurable timeout from window.MCPGATEWAY_UI_TOOL_TEST_TIMEOUT
  const controller = new AbortController();
  const timeoutId = setTimeout(() => {
    console.warn(`Request to ${url} timed out after ${timeout}ms`);
    controller.abort();
  }, timeout);

  return fetch(url, {
    ...options,
    signal: controller.signal,
    // Add cache busting to prevent stale responses
    headers: {
      ...options.headers,
      "Cache-Control": "no-cache",
      Pragma: "no-cache",
      ...(getCookie("mcpgateway_csrf_token")
        ? { "X-CSRF-Token": getCookie("mcpgateway_csrf_token") }
        : {}),
    },
  })
    .then((response) => {
      clearTimeout(timeoutId);

      // FIX: Better handling of empty responses
      if (response.status === 0) {
        // Status 0 often indicates a network error or CORS issue
        throw new Error(
          "Network error or server is not responding. Please ensure the server is running and accessible."
        );
      }

      if (response.ok && response.status === 200) {
        const contentLength = response.headers.get("content-length");

        // Check Content-Length if present
        if (contentLength !== null && parseInt(contentLength, 10) === 0) {
          console.warn(`Empty response from ${url} (Content-Length: 0)`);
          // Don't throw error for intentionally empty responses
          return response;
        }

        // For responses without Content-Length, clone and check
        const cloned = response.clone();
        return cloned.text().then((text) => {
          if (!text || !text.trim()) {
            console.warn(`Empty response body from ${url}`);
            // Return the original response anyway
          }
          return response;
        });
      }
      return response;
    })
    .catch((error) => {
      clearTimeout(timeoutId);

      // Improve error messages for common issues
      if (error.name === "AbortError") {
        throw new Error(
          `Request timed out after ${timeout / 1000} seconds. The server may be slow or unresponsive.`
        );
      } else if (
        error.message.includes("Failed to fetch") ||
        error.message.includes("NetworkError")
      ) {
        throw new Error(
          "Unable to connect to server. Please check if the server is running on the correct port."
        );
      } else if (
        error.message.includes("empty response") ||
        error.message.includes("ERR_EMPTY_RESPONSE")
      ) {
        throw new Error(
          "Server returned an empty response. This endpoint may not be implemented yet or the server crashed."
        );
      }

      throw error;
    });
}

// Enhanced error handler for fetch operations
export function handleFetchError(error, operation = "operation") {
  console.error(`Error during ${operation}:`, error);

  if (error.name === "AbortError") {
    return `Request timed out while trying to ${operation}. Please try again.`;
  } else if (error.message.includes("HTTP")) {
    return `Server error during ${operation}: ${error.message}`;
  } else if (
    error.message.includes("NetworkError") ||
    error.message.includes("Failed to fetch")
  ) {
    return `Network error during ${operation}. Please check your connection and try again.`;
  } else {
    return `Failed to ${operation}: ${error.message}`;
  }
}

// Show user-friendly error messages
export function showErrorMessage(message, elementId = null) {
  console.error("Error:", message);

  if (elementId) {
    const element = safeGetElement(elementId);
    if (element) {
      element.textContent = message;
      element.classList.add("error-message", "text-red-600", "mt-2");
    }
  } else {
    // Show global error notification
    const errorDiv = document.createElement("div");
    errorDiv.className =
      "fixed top-4 right-4 bg-red-600 text-white px-4 py-2 rounded shadow-lg z-50";
    errorDiv.textContent = message;
    document.body.appendChild(errorDiv);

    setTimeout(() => {
      if (errorDiv.parentNode) {
        errorDiv.parentNode.removeChild(errorDiv);
      }
    }, 5000);
  }
}

// Show success messages
export function showSuccessMessage(message) {
  const successDiv = document.createElement("div");
  successDiv.className =
    "fixed top-4 right-4 bg-green-600 text-white px-4 py-2 rounded shadow-lg z-50";
  successDiv.textContent = message;
  document.body.appendChild(successDiv);

  setTimeout(() => {
    if (successDiv.parentNode) {
      successDiv.parentNode.removeChild(successDiv);
    }
  }, 3000);
}

// Show a persistent modal warning listing tools that were skipped during gateway import.
// Returns a Promise that resolves when the user dismisses the modal.
export function showSkippedToolsWarning(skippedTools) {
  return new Promise((resolve) => {
    const overlay = document.createElement("div");
    overlay.className = "fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50";

    const modal = document.createElement("div");
    modal.className = "bg-white rounded-lg shadow-xl p-6 max-w-2xl w-full mx-4";

    const title = document.createElement("h2");
    title.className = "text-lg font-semibold text-yellow-700 mb-3";
    title.textContent = "Some tools were skipped";

    const intro = document.createElement("p");
    intro.className = "text-sm text-gray-600 mb-3";
    intro.textContent = "The following tools could not be imported due to validation errors:";

    const tableWrapper = document.createElement("div");
    tableWrapper.className = "overflow-auto max-h-64 mb-4 border border-gray-200 rounded";

    const table = document.createElement("table");
    table.className = "w-full text-sm border-collapse";

    const thead = document.createElement("thead");
    thead.innerHTML = `<tr class="bg-gray-100 text-left">
      <th class="px-3 py-2 font-semibold text-gray-700 border-b border-gray-200 w-1/2">Tool name</th>
      <th class="px-3 py-2 font-semibold text-gray-700 border-b border-gray-200">Reason</th>
    </tr>`;

    const tbody = document.createElement("tbody");
    for (const entry of skippedTools) {
      const colonIdx = entry.indexOf(": ");
      const toolName = colonIdx !== -1 ? entry.slice(0, colonIdx) : entry;
      const reason = colonIdx !== -1 ? entry.slice(colonIdx + 2) : "Unknown error";
      const tr = document.createElement("tr");
      tr.className = "border-b border-gray-100 last:border-0";
      const toolNameCell = document.createElement("td");
      toolNameCell.className = "px-3 py-2 font-mono text-xs text-gray-800 align-top break-all";
      toolNameCell.textContent = toolName;
      const reasonCell = document.createElement("td");
      reasonCell.className = "px-3 py-2 text-gray-700 align-top";
      reasonCell.textContent = reason;
      tr.appendChild(toolNameCell);
      tr.appendChild(reasonCell);
      tbody.appendChild(tr);
    }

    table.appendChild(thead);
    table.appendChild(tbody);
    tableWrapper.appendChild(table);

    const dismissBtn = document.createElement("button");
    dismissBtn.className = "bg-yellow-600 hover:bg-yellow-700 text-white px-4 py-2 rounded text-sm font-medium";
    dismissBtn.textContent = "Dismiss";
    dismissBtn.addEventListener("click", () => {
      if (overlay.parentNode) {
        overlay.parentNode.removeChild(overlay);
      }
      resolve();
    });

    modal.appendChild(title);
    modal.appendChild(intro);
    modal.appendChild(tableWrapper);
    modal.appendChild(dismissBtn);
    overlay.appendChild(modal);
    document.body.appendChild(overlay);
  });
}

// Handle HTMX after-request for user delete — extracts plain text from the
// HTML error response and surfaces it via the toast notification system.
// Exposed on window so inline hx-on::after-request handlers can call it.
export function handleDeleteUserError(event) {
  if (!event.detail.successful) {
    const d = document.createElement("div");
    d.innerHTML = event.detail.xhr.responseText;
    showErrorMessage(d.textContent.trim() || "Error deleting user");
  }
}

// ----- URI Template Parsing -------------- //
export function parseUriTemplate(template) {
  const regex = /{([^}]+)}/g;
  const fields = [];
  let match;

  while ((match = regex.exec(template)) !== null) {
    fields.push(match[1]); // capture inside {}
  }
  return fields;
}

export const isAdminUser = function () {
  return Boolean(window.IS_ADMIN);
};

/**
 * Copy text to clipboard
 */
export const copyToClipboard = async function (elementId) {
  const element = document.getElementById(elementId);
  if (!element) {
    return;
  }

  const textToCopy =
    typeof element.value === "string"
      ? element.value
      : (element.textContent || "").trim();

  const fallbackCopy = () => {
    if (typeof element.focus === "function") {
      element.focus();
    }
    if (typeof element.select === "function") {
      element.select();
    }
    if (typeof element.setSelectionRange === "function") {
      element.setSelectionRange(0, textToCopy.length);
    }

    if (typeof document.execCommand !== "function") {
      return false;
    }
    try {
      return document.execCommand("copy");
    } catch (error) {
      console.warn("Fallback copy failed", error);
      return false;
    }
  };

  if (!textToCopy) {
    showNotification("No token available to copy", "error");
    return;
  }

  const hasClipboardApi =
    typeof navigator !== "undefined" &&
    navigator.clipboard &&
    typeof navigator.clipboard.writeText === "function";

  if (hasClipboardApi) {
    try {
      await navigator.clipboard.writeText(textToCopy);
      showNotification("Token copied to clipboard", "success");
      return;
    } catch (error) {
      console.warn("Clipboard API copy failed, trying fallback", error);
    }
  }

  if (fallbackCopy()) {
    showNotification("Token copied to clipboard", "success");
    return;
  }

  showNotification("Failed to copy token. Please copy it manually.", "error");
};

export const copyJsonToClipboard = function (sourceId) {
  const el = safeGetElement(sourceId);
  if (!el) {
    console.warn(
      `[copyJsonToClipboard] Source element "${sourceId}" not found.`
    );
    return;
  }

  const text = "value" in el ? el.value : el.textContent;

  navigator.clipboard.writeText(text).then(
    () => {
      console.info("JSON copied to clipboard ✔️");
      if (el.dataset.toast !== "off") {
        showSuccessMessage("Copied!");
      }
    },
    (err) => {
      console.error("Clipboard write failed:", err);
      showErrorMessage("Unable to copy to clipboard");
    }
  );
};

/**
 * Utility function to get cookie value
 */
export const getCookie = function (name) {
  const value = `; ${document.cookie}`;
  const parts = value.split(`; ${name}=`);
  if (parts.length === 2) {
    return parts.pop().split(";").shift();
  }
  return "";
};

/**
 * Get the currently selected team ID from the team selector
 */
export const getCurrentTeamId = function () {
  const isKnownTeamId = (teamId) => {
    if (!teamId) {
      return false;
    }

    const teamsData = Array.isArray(window.USER_TEAMS_DATA)
      ? window.USER_TEAMS_DATA
      : Array.isArray(window.USER_TEAMS)
        ? window.USER_TEAMS
        : [];

    // If team data is unavailable, do not block existing behavior.
    if (teamsData.length === 0) {
      return true;
    }

    return teamsData.some((team) => team && team.id === teamId);
  };
  // First, try to get from Alpine.js component (most reliable)
  const teamSelector = document.querySelector('[x-data*="selectedTeam"]');
  if (
    teamSelector &&
    teamSelector._x_dataStack &&
    teamSelector._x_dataStack[0]
  ) {
    const alpineData = teamSelector._x_dataStack[0];
    const selectedTeam = alpineData.selectedTeam;

    // Return null if empty string or falsy (means "All Teams")
    if (!selectedTeam || selectedTeam === "" || selectedTeam === "all") {
      return null;
    }

    return isKnownTeamId(selectedTeam) ? selectedTeam : null;
  }

  // Fallback: check URL parameters
  const urlParams = new URLSearchParams(window.location.search);
  const teamId = urlParams.get("team_id");

  if (!teamId || teamId === "" || teamId === "all") {
    return null;
  }

  return isKnownTeamId(teamId) ? teamId : null;
};

/**
 * Get the currently selected team name from Alpine.js team selector
 * @returns {string|null} Team name or null if not found
 */
export const getCurrentTeamName = function () {
  const currentTeamId = getCurrentTeamId();

  if (!currentTeamId) {
    return null;
  }

  // Method 1: Try from window.USERTEAMSDATA (most reliable)
  if (window.USERTEAMSDATA && Array.isArray(window.USERTEAMSDATA)) {
    const teamObj = window.USERTEAMSDATA.find((t) => t.id === currentTeamId);
    if (teamObj) {
      // Return the personal team name format if it's a personal team
      return teamObj.ispersonal ? `${teamObj.name}` : teamObj.name;
    }
  }

  // Method 2: Try from Alpine.js component
  const teamSelector = document.querySelector('[x-data*="selectedTeam"]');
  if (
    teamSelector &&
    teamSelector._x_dataStack &&
    teamSelector._x_dataStack[0]
  ) {
    const alpineData = teamSelector._x_dataStack[0];

    // Get the selected team name directly from Alpine
    if (
      alpineData.selectedTeamName &&
      alpineData.selectedTeamName !== "All Teams"
    ) {
      return alpineData.selectedTeamName;
    }

    // Try to find in teams array
    if (alpineData.teams && Array.isArray(alpineData.teams)) {
      const selectedTeamObj = alpineData.teams.find(
        (t) => t.id === currentTeamId
      );
      if (selectedTeamObj) {
        return selectedTeamObj.ispersonal
          ? `${selectedTeamObj.name}`
          : selectedTeamObj.name;
      }
    }
  }

  // Fallback: return the team ID if name not found
  return currentTeamId;
};

// Make URL field read-only for integration type MCP
export const updateEditToolUrl = function () {
  const editTypeField = safeGetElement("edit-tool-type");
  const editurlField = safeGetElement("edit-tool-url");
  if (editTypeField && editurlField) {
    if (editTypeField.value === "MCP") {
      editurlField.readOnly = true;
    } else {
      editurlField.readOnly = false;
    }
  }
};

/**
 * Format timestamp for display
 */
export const formatTimestamp = function (timestamp) {
  const date = new Date(timestamp);
  return date.toLocaleString("en-US", {
    month: "short",
    day: "numeric",
    hour: "2-digit",
    minute: "2-digit",
    second: "2-digit",
  });
};

/**
 * Handle keydown event when Enter or Space key is pressed
 *
 * @param {KeyboardEvent} event - the keyboard event triggered
 * @param {function} callback - the function to call when Enter or Space is pressed
 */
export const handleKeydown = (event, callback) => {
  if (event.key === "Enter" || event.key === " ") {
    event.preventDefault();
    callback(event);
  }
};

/**
 * Get root path for API calls
 */
export const getRootPath = function () {
  return window.ROOT_PATH || "";
};

/**
 * Show toast notification
 */
export const showToast = function (message, type = "info") {
  // Check if showMessage function exists (from existing admin.js)
  showNotification(message, type === "error" ? "danger" : type);
};

/**
 * Show notification (simple implementation)
 */
export const showNotification = function (message, type = "info") {
  console.log(`${type.toUpperCase()}: ${message}`);

  // Create a simple toast notification
  const toast = document.createElement("div");
  toast.className = `fixed top-4 right-4 z-50 px-4 py-3 rounded-md text-sm font-medium max-w-sm ${
    type === "success"
      ? "bg-green-100 text-green-800 border border-green-400"
      : type === "error"
        ? "bg-red-100 text-red-800 border border-red-400"
        : "bg-blue-100 text-blue-800 border border-blue-400"
  }`;
  toast.textContent = message;

  document.body.appendChild(toast);

  // Auto-remove after 5 seconds
  setTimeout(() => {
    if (toast.parentNode) {
      toast.parentNode.removeChild(toast);
    }
  }, 5000);
};

/**
 * Check if string is valid base64
 * @param {string} str - The string to validate
 * @returns {boolean} - True if valid base64
 */
export const isValidBase64 = function (str) {
  if (str.length === 0) {
    return false;
  }

  // Base64 regex pattern
  const base64Pattern = /^[A-Za-z0-9+/]*={0,2}$/;
  return base64Pattern.test(str);
};

// Logs refresh function
export const refreshLogs = function () {
  const logsSection = safeGetElement("logs");
  if (logsSection && typeof window.htmx !== "undefined") {
    // Trigger HTMX refresh on the logs section
    window.htmx.trigger(logsSection, "refresh");
  }
};

/**
 * Truncate text with ellipsis
 */
export const truncateText = function (text, maxLength) {
  if (!text) {
    return "";
  }
  return text.length > maxLength ? text.substring(0, maxLength) + "..." : text;
};

/**
 * Decode HTML entities back to their original characters.
 * Used when populating form fields to prevent double-encoding.
 * @param {string} html - The HTML-encoded string
 * @returns {string} Decoded string
 */
export const decodeHtml = function (html) {
  if (html === null || html === undefined) {
    return "";
  }
  const txt = document.createElement("textarea");
  txt.innerHTML = html;
  return txt.value;
};

// Helper function to get pagination parameters from URL (namespaced per table)
export const getPaginationParams = function (tableName) {
  const urlParams = new URLSearchParams(window.location.search);
  const prefix = tableName + "_";
  return {
    page: Math.max(1, parseInt(urlParams.get(prefix + "page"), 10) || 1),
    perPage: Math.max(1, parseInt(urlParams.get(prefix + "size"), 10) || 10),
    includeInactive: urlParams.get(prefix + "inactive"),
  };
};

// Helper function to build table URL with pagination params (namespaced per table)
// URL state takes precedence over checkbox state for shareability
export const buildTableUrl = function (
  tableName,
  baseUrl,
  additionalParams = {}
) {
  const params = getPaginationParams(tableName);
  const urlParams = new URLSearchParams(window.location.search);
  const prefix = tableName + "_";
  const url = new URL(baseUrl, window.location.origin);
  url.searchParams.set("page", params.page);
  url.searchParams.set("per_page", params.perPage);

  // Add additional parameters, but URL state takes precedence for include_inactive
  for (const [key, value] of Object.entries(additionalParams)) {
    if (key === "include_inactive" && params.includeInactive !== null) {
      // URL state overrides checkbox state for shareability
      url.searchParams.set("include_inactive", params.includeInactive);
    } else if (value !== null && value !== undefined && value !== "") {
      url.searchParams.set(key, value);
    }
  }

  // If include_inactive wasn't in additionalParams but is in URL, still use it
  if (
    !Object.hasOwn(additionalParams, "include_inactive") &&
    params.includeInactive !== null
  ) {
    url.searchParams.set("include_inactive", params.includeInactive);
  }

  // Preserve namespaced search state for shareable URLs
  const namespacedQuery = urlParams.get(prefix + "q");
  const namespacedTags = urlParams.get(prefix + "tags");
  if (namespacedQuery) {
    url.searchParams.set("q", namespacedQuery);
  }
  if (namespacedTags) {
    url.searchParams.set("tags", namespacedTags);
  }

  return url.pathname + url.search;
};

/**
 * Create a copy-to-clipboard button for an ID value.
 * Returns a <button> element that copies the given id string to the clipboard.
 * @param {string|number} id - The ID value to copy
 * @returns {HTMLButtonElement}
 */
export const makeCopyIdButton = function (id) {
  const btn = document.createElement("button");
  btn.type = "button";
  btn.title = "Copy ID to clipboard";
  btn.className =
    "ml-2 inline-flex items-center px-1.5 py-0.5 text-xs rounded border border-gray-300 dark:border-gray-600 text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors";
  btn.textContent = "📋 Copy";
  btn.addEventListener("click", () => {
    const idStr = String(id);
    const onSuccess = () => {
      btn.textContent = "✅ Copied!";
      setTimeout(() => {
        btn.textContent = "📋 Copy";
      }, 2000);
    };
    const onFailure = () => {
      btn.textContent = "❌ Failed";
      setTimeout(() => {
        btn.textContent = "📋 Copy";
      }, 2000);
    };
    if (
      navigator.clipboard &&
      typeof navigator.clipboard.writeText === "function"
    ) {
      navigator.clipboard.writeText(idStr).then(onSuccess).catch(onFailure);
    } else {
      try {
        const ta = document.createElement("textarea");
        ta.value = idStr;
        ta.style.position = "fixed";
        ta.style.opacity = "0";
        document.body.appendChild(ta);
        ta.select();
        document.execCommand("copy");
        document.body.removeChild(ta);
        onSuccess();
      } catch (_e) {
        onFailure();
      }
    }
  });
  return btn;
};

export const isUiResourceUri = function (uri) {
  return (
    typeof uri === "string" && uri.trim().toLowerCase().startsWith("ui://")
  );
};

export const bindMcpAppMimeHelper = function (
  uriInputId,
  mimeInputId,
  helperId
) {
  const uriField = safeGetElement(uriInputId, true);
  const mimeField = safeGetElement(mimeInputId, true);
  const helperText = safeGetElement(helperId, true);

  if (!uriField || !mimeField || !helperText) {
    return;
  }

  if (mimeField.dataset.mcpAppMimeHelperBound === "true") {
    return;
  }
  mimeField.dataset.mcpAppMimeHelperBound = "true";

  const updateHelperVisibility = () => {
    const shouldShow =
      document.activeElement === mimeField && isUiResourceUri(uriField.value);
    helperText.classList.toggle("hidden", !shouldShow);
  };

  uriField.addEventListener("input", updateHelperVisibility);
  mimeField.addEventListener("focus", updateHelperVisibility);
  mimeField.addEventListener("blur", () => {
    helperText.classList.add("hidden");
  });

  updateHelperVisibility();
};
