summaryInclude = 60;

const fuseOptions = {
  shouldSort: true,
  includeMatches: true,
  threshold: 0.0,
  tokenize: true,
  location: 0,
  distance: 100,
  maxPatternLength: 32,
  minMatchCharLength: 1,
  keys: [
    { name: "title", weight: 0.9 },
    { name: "contents", weight: 0.5 },
    { name: "tags", weight: 0.3 },
    { name: "categories", weight: 0.3 },
  ],
};

// Display error message in the search results container
function displayError(message) {
  const searchResultsElement = document.getElementById("search-results");
  if (searchResultsElement) {
    const sanitizedMessage = DOMPurify.sanitize(message);
    searchResultsElement.innerHTML = `<div class="alert alert-danger">${sanitizedMessage}</div>`;
  } else {
    console.error("Search results container not found");
  }
}

// Safely get DOM element with error handling
function getElement(id) {
  const element = document.getElementById(id);
  if (!element) {
    console.error(`Element with ID '${id}' not found`);
  }
  return element;
}

// Debounce function to prevent excessive search calls
function debounce(func, wait) {
  let timeout;
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}

// Function to update URL with search query
function updateURL(query) {
  try {
    // Create a URL object from the current URL
    const url = new URL(window.location.href);

    if (query && query.length >= 2) {
      // Set or update the 's' parameter
      url.searchParams.set("s", query);
    } else {
      // Remove the 's' parameter if query is empty or too short
      url.searchParams.delete("s");
    }

    // Update the URL without reloading the page
    window.history.replaceState({}, "", url.toString());
  } catch (error) {
    console.error("Error updating URL:", error);
    // Continue without URL update - non-critical error
  }
}

// Safe parameter extraction with error handling
function param(name) {
  try {
    const paramValue = (location.search.split(`${name}=`)[1] || "").split("&")[0];
    return paramValue ? decodeURIComponent(paramValue).replace(/\+/g, " ") : "";
  } catch (error) {
    console.error(`Error parsing URL parameter '${name}':`, error);
    return "";
  }
}

// Get search query from URL parameter
const searchQuery = param("s");
try {
  const searchInput = getElement("search-query");
  const searchResults = getElement("search-results");

  if (searchInput && searchQuery) {
    searchInput.value = searchQuery;
    executeSearch(searchQuery);
  } else if (searchResults) {
    searchResults.innerHTML =
      "<div class='alert'>Please enter at least 2 characters to search</div>";
  }
} catch (error) {
  console.error("Error initializing search:", error);
  displayError(
    "There was a problem initializing the search. Please try again later.",
  );
}

// Add event listener for real-time searching
document.addEventListener("DOMContentLoaded", () => {
  try {
    const searchInput = getElement("search-query");
    if (!searchInput) {
      throw new Error("Search input not found");
    }

    // Create debounced search function - 300ms is a good balance
    const debouncedSearch = debounce((query) => {
      // Update URL with current search query
      updateURL(query);

      const searchResults = getElement("search-results");
      if (!searchResults) {
        throw new Error("Search results container not found");
      }

      if (query.length >= 2) {
        executeSearch(query);
      } else if (query.length === 0 || query.length === 1) {
        searchResults.innerHTML =
          "<div class='alert'>Please enter at least 2 characters to search</div>";
      }
    }, 300);

    // Set up input event for real-time searching
    searchInput.addEventListener("input", function () {
      const query = this.value.trim();
      debouncedSearch(query);
    });

    // Handle form submission to prevent page reload
    const searchForm = searchInput.closest("form");
    if (searchForm) {
      searchForm.addEventListener("submit", (e) => {
        e.preventDefault();
        const query = searchInput.value.trim();
        if (query.length >= 2) {
          updateURL(query);
          executeSearch(query);
        }
      });
    }
  } catch (error) {
    console.error("Error setting up search event listeners:", error);
    displayError(
      "There was a problem setting up the search functionality. Please try reloading the page.",
    );
  }
});

function executeSearch(searchQuery) {
  try {
    if (!searchQuery || typeof searchQuery !== "string") {
      throw new Error("Invalid search query");
    }

    const searchResults = getElement("search-results");
    if (!searchResults) {
      throw new Error("Search results container not found");
    }

    // Show loading indicator
    searchResults.innerHTML =
      '<div class="spinner-border text-primary" role="status"><span class="visually-hidden">Loading...</span></div>';

    fetch("/index.json")
      .then((response) => {
        if (!response.ok) {
          throw new Error(
            `Network response was not ok: ${response.status} ${response.statusText}`,
          );
        }
        return response.json();
      })
      .then((data) => {
        if (!Array.isArray(data)) {
          throw new Error("Received invalid data format from server");
        }

        const pages = data;
        const fuse = new Fuse(pages, fuseOptions);
        const result = fuse.search(searchQuery);

        // Clear previous results only when we have new results to show
        searchResults.innerHTML = "";

        if (result.length > 0) {
          populateResults(result);
        } else {
          searchResults.insertAdjacentHTML(
            "beforeend",
            "<div class='alert'>No matches found</div>",
          );
        }
      })
      .catch((error) => {
        console.error("Error executing search:", error);
        displayError(`Failed to search: ${error.message}`);
      });
  } catch (error) {
    console.error("Error in search execution:", error);
    displayError("There was a problem with the search. Please try again later.");
  }
}

function populateResults(result) {
  try {
    if (!Array.isArray(result)) {
      throw new Error("Invalid search results");
    }

    const searchResults = getElement("search-results");
    if (!searchResults) {
      throw new Error("Search results container not found");
    }

    const templateElement = document.getElementById("search-result-template");
    if (!templateElement) {
      throw new Error("Search result template not found");
    }
    const templateDefinition = templateElement.innerHTML;

    for (const [key, value] of result.entries()) {
      if (!value || !value.item) {
        console.warn("Skipping invalid search result item", value);
        continue;
      }

      const contents = value.item.contents || "";
      let snippet = "";
      const snippetHighlights = [];
      let start;
      let end;

      if (fuseOptions.tokenize) {
        snippetHighlights.push(searchQuery);
      } else {
        if (value.matches) {
          for (const mvalue of value.matches) {
            if (!mvalue || typeof mvalue.key !== "string") {
              continue;
            }

            if (
              mvalue.key === "tags" ||
              mvalue.key === "categories"
            ) {
              snippetHighlights.push(mvalue.value);
            } else if (
              mvalue.key === "contents" &&
              Array.isArray(mvalue.indices) &&
              mvalue.indices.length > 0
            ) {
              try {
                start =
                  mvalue.indices[0][0] - summaryInclude > 0
                    ? mvalue.indices[0][0] - summaryInclude
                    : 0;
                end =
                  mvalue.indices[0][1] + summaryInclude < contents.length
                    ? mvalue.indices[0][1] + summaryInclude
                    : contents.length;
                snippet += contents.substring(start, end);

                if (typeof mvalue.value === "string") {
                  const highlightValue =
                    mvalue.indices[0][1] - mvalue.indices[0][0] + 1;
                  if (
                    highlightValue > 0 &&
                    mvalue.indices[0][0] < mvalue.value.length
                  ) {
                    snippetHighlights.push(
                      mvalue.value.substring(
                        mvalue.indices[0][0],
                        mvalue.indices[0][0] + highlightValue,
                      ),
                    );
                  }
                }
              } catch (e) {
                console.warn("Error processing match indices", e);
              }
            }
          }
        }
      }

      if (snippet.length < 1 && contents) {
        snippet += contents.substring(0, summaryInclude * 2);
      }

      try {
        // Insert the templated result
        const output = render(templateDefinition, {
          key: key,
          title: value.item.title || "Untitled",
          link: value.item.permalink || "#",
          tags: value.item.tags || "",
          categories: value.item.categories || "",
          snippet: snippet || "No preview available",
        });
        searchResults.insertAdjacentHTML("beforeend", output);

        // Add highlighting after insertion
        for (const snipvalue of snippetHighlights) {
          if (!snipvalue) continue;

          const summaryElem = document.getElementById(`summary-${key}`);
          if (summaryElem && typeof Mark !== "undefined") {
            try {
              const markInstance = new Mark(summaryElem);
              markInstance.mark(snipvalue);
            } catch (e) {
              console.warn("Error highlighting text:", e);
            }
          }
        }
      } catch (error) {
        console.error("Error rendering search result:", error);
      }
    }
  } catch (error) {
    console.error("Error populating results:", error);
    displayError("There was a problem displaying search results.");
  }
}

function render(templateString, data) {
  try {
    if (!templateString || !data) {
      throw new Error("Invalid template or data");
    }

    let conditionalMatches, conditionalPattern, copy;
    conditionalPattern = /\$\{\s*isset ([a-zA-Z]*) \s*\}(.*)\$\{\s*end\s*}/g;

    // Since loop below depends on re.lastIndex, we use a copy to capture any manipulations
    copy = templateString;

    while ((conditionalMatches = conditionalPattern.exec(templateString)) !== null) {
      if (conditionalMatches.length < 3) continue;

      if (data[conditionalMatches[1]]) {
        // Valid key, remove conditionals, leave contents
        copy = copy.replace(conditionalMatches[0], conditionalMatches[2]);
      } else {
        // Not valid, remove entire section
        copy = copy.replace(conditionalMatches[0], "");
      }
    }

    let result = copy;

    // Now any conditionals removed we can do simple substitution
    for (const key in data) {
      if (Object.prototype.hasOwnProperty.call(data, key)) {
        const value = data[key];
        if (value !== undefined && value !== null) {
          const find = `\\$\\{\\s*${key}\\s*\\}`;
          const re = new RegExp(find, "g");
          result = result.replace(re, value);
        }
      }
    }

    return result;
  } catch (error) {
    console.error("Error rendering template:", error);
    return '<div class="alert alert-danger">Error rendering result</div>';
  }
}
