/*
 * Copyright (c) 2016-2020 Martin Donath <martin.donath@squidfunk.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

import "lunr"

import { Search, SearchIndexConfig } from "../../_"
import {
  SearchMessage,
  SearchMessageType
} from "../message"

/* ----------------------------------------------------------------------------
 * Types
 * ------------------------------------------------------------------------- */

/**
 * Add support for usage with `iframe-worker` polyfill
 *
 * While `importScripts` is synchronous when executed inside of a web worker,
 * it's not possible to provide a synchronous polyfilled implementation. The
 * cool thing is that awaiting a non-Promise is a noop, so extending the type
 * definition to return a `Promise` shouldn't break anything.
 *
 * @see https://bit.ly/2PjDnXi - GitHub comment
 */
declare global {
  function importScripts(...urls: string[]): Promise<void> | void
}

/* ----------------------------------------------------------------------------
 * Data
 * ------------------------------------------------------------------------- */

/**
 * Search index
 */
let index: Search

/* ----------------------------------------------------------------------------
 * Helper functions
 * ------------------------------------------------------------------------- */

/**
 * Fetch (= import) multi-language support through `lunr-languages`
 *
 * This function will automatically import the stemmers necessary to process
 * the languages which were given through the search index configuration.
 *
 * If the worker runs inside of an `iframe` (when using `iframe-worker` as
 * a shim), the base URL for the stemmers to be loaded must be determined by
 * searching for the first `script` element with a `src` attribute, which will
 * contain the contents of this script.
 *
 * @param config - Search index configuration
 *
 * @return Promise resolving with no result
 */
async function setupSearchLanguages(
  config: SearchIndexConfig
): Promise<void> {
  let base = "../lunr"

  /* Detect `iframe-worker` and fix base URL */
  if (typeof parent !== "undefined" && "IFrameWorker" in parent) {
    const worker = document.querySelector<HTMLScriptElement>("script[src]")!
    const [path] = worker.src.split("/worker")

    /* Prefix base with path */
    base = base.replace("..", path)
  }

  /* Add scripts for languages */
  const scripts = []
  for (const lang of config.lang) {
    if (lang === "ja") scripts.push(`${base}/tinyseg.min.js`)
    if (lang !== "en") scripts.push(`${base}/min/lunr.${lang}.min.js`)
  }

  /* Add multi-language support */
  if (config.lang.length > 1)
    scripts.push(`${base}/min/lunr.multi.min.js`)

  /* Load scripts synchronously */
  if (scripts.length)
    await importScripts(
      `${base}/min/lunr.stemmer.support.min.js`,
      ...scripts
    )
}

/* ----------------------------------------------------------------------------
 * Functions
 * ------------------------------------------------------------------------- */

/**
 * Message handler
 *
 * @param message - Source message
 *
 * @return Target message
 */
export async function handler(
  message: SearchMessage
): Promise<SearchMessage> {
  switch (message.type) {

    /* Search setup message */
    case SearchMessageType.SETUP:
      await setupSearchLanguages(message.data.config)
      index = new Search(message.data)
      return {
        type: SearchMessageType.READY
      }

    /* Search query message */
    case SearchMessageType.QUERY:
      return {
        type: SearchMessageType.RESULT,
        data: index ? index.search(message.data) : []
      }

    /* All other messages */
    default:
      throw new TypeError("Invalid message type")
  }
}

/* ----------------------------------------------------------------------------
 * Worker
 * ------------------------------------------------------------------------- */

addEventListener("message", async ev => {
  postMessage(await handler(ev.data))
})
