---
import '../css/search.css';
import { metadata, searchOptions } from '../include/SiteConfig.js';
---

<div id="search-page">
  <div id="search-parameters">
    <input
      name="searchText"
      id="search-text"
      placeholder="Search"
      autofocus=""
      value=""
    />
    <span id="checkbox">
      <input type="checkbox" id="is-case-sensitive" />
      <label for="is-case-sensitive">Case sensitive</label>
    </span>
  </div>
  <div id="search-results">
    <p>No results</p>
  </div>
</div>

<script
  id="astro-metadata"
  data-prefix={metadata.version}
  data-file={searchOptions.indexFile}
>
  const logTimings = true;
  let t0, t1, t2;

  // Passed in from the Astro build via the `data-prefix` attribute
  const { prefix, file } = document.getElementById('astro-metadata').dataset;

  // Location of the search index file on the server
  const searchIndexFile = `/${prefix}/${file}`;

  // Save the search index in a module-scoped variable
  let searchIndex = null;

  // Load search index from the server if necessary
  async function loadSearchIndex() {
    if (searchIndex) {
      return searchIndex;
    }

    try {
      const cachedIndex = sessionStorage.getItem('searchIndex');
      if (cachedIndex) {
        searchIndex = JSON.parse(cachedIndex);
        return searchIndex;
      }
    } catch (error) {
      console.log('Failed read index from session storage');
    }

    try {
      if (logTimings) {
        t0 = Date.now();
      }

      const response = await fetch(searchIndexFile);
      if (logTimings) {
        t1 = Date.now();
        console.log('Fetched search index in ' + (t1 - t0) + 'ms');
      }

      searchIndex = await response.json();
      if (logTimings) {
        t2 = Date.now();
        console.log('Deserialised index in ' + (t2 - t1) + 'ms');
      }

      try {
        sessionStorage.setItem('searchIndex', JSON.stringify(searchIndex));
      } catch (error) {
        console.log('Failed to save index to session storage');
      }
      return searchIndex;
    } catch (error) {
      console.error('Failed to load search index:', error);
      return null;
    }
  }

  const escapeRegExp = (string) => {
    return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  };

  async function getSearchResults(query, index) {
    if (query.searchText.length < 3) {
      return [];
    }

    // Match the starts of words only. The "d" flag gives us the matching indices.
    const regex = RegExp(
      '(^|\\W|_)(' + escapeRegExp(query.searchText) + ')',
      'gd' + (query.isCaseSensitive ? '' : 'i'),
    );

    const result = index.map((node) => {
      let pageScore = 0;
      const matches = [];
      for (let i = 0; i < node.chunks?.length; i++) {
        let chunk = node.chunks[i];
        let match;
        const indices = [];
        while ((match = regex.exec(chunk.text)) !== null) {
          indices.push([match.indices[2][0], match.indices[2][1]]);
        }
        if (indices.length > 0) {
          matches.push({
            type: chunk.type,
            label: chunk.label,
            id: chunk.id,
            text: chunk.text,
            indices: indices,
          });
          pageScore += chunk.weight * indices.length;
        }
      }

      return matches.length === 0
        ? null
        : {
            url: node.frontmatter.path,
            title: node.frontmatter.titles.join(' | '),
            matches: matches,
            score: pageScore,
          };
    });

    return result.filter((x) => x).sort((a, b) => b.score - a.score);
  }

  async function updateSearchResults(query, index) {
    if (!index) {
      return;
    }

    const results = await getSearchResults(query, index);

    const pages = results
      .map((result) => {
        const chunks = result.matches
          .map((match) => {
            const matches = match.indices
              .map((indices, i) => {
                return (
                  match.text.substring(
                    i === 0 ? 0 : match.indices[i - 1][1],
                    indices[0],
                  ) +
                  `<span class='match-text'>${match.text.substring(indices[0], indices[1])}</span>` +
                  (i === match.indices.length - 1
                    ? match.text.substring(indices[1])
                    : '')
                );
              })
              .join('');
            return `<li>
            <a href=/${prefix}${result.url + '#' + match.id} class="label" target="_blank">${match.label}</a>
            <span class="chunk-text ${match.type}">${matches}</span>
          </li>`;
          })
          .join('');
        return `<li>
          <a href=/${prefix}${result.url} target="_blank">${result.title}</a>
          <ul>${chunks}</ul>
        </li>`;
      })
      .join('');

    const resultsDiv = document.getElementById('search-results');
    resultsDiv.innerHTML = results.length
      ? '<ul>' + pages + '</ul>'
      : '<p>No results</p>';
  }

  const searchTextInput = document.getElementById('search-text');
  const caseSensitiveCheckbox = document.getElementById('is-case-sensitive');

  // Clear the inputs on page reload
  searchTextInput.value = '';
  caseSensitiveCheckbox.checked = false;

  async function doSearch() {
    const query = {
      searchText: searchTextInput.value,
      isCaseSensitive: caseSensitiveCheckbox.checked,
    };
    const index = await loadSearchIndex();
    updateSearchResults(query, index);
  }

  searchTextInput.addEventListener('input', async () => {
    doSearch();
  });

  caseSensitiveCheckbox.addEventListener('change', async () => {
    doSearch();
  });

  // Pre-fetch and cache the search index
  document.addEventListener('DOMContentLoaded', () => {
    searchIndex = loadSearchIndex();
  });
</script>
