// loaders.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

import {assert} from '../env-utils/assert';

const workerURLCache = new Map();

/**
 * Creates a loadable URL from worker source or URL
 * that can be used to create `Worker` instances.
 * Due to CORS issues it may be necessary to wrap a URL in a small importScripts
 * @param props
 * @param props.source Worker source
 * @param props.url Worker URL
 * @returns loadable url
 */
export function getLoadableWorkerURL(props: {source?: string; url?: string}) {
  assert((props.source && !props.url) || (!props.source && props.url)); // Either source or url must be defined

  let workerURL = workerURLCache.get(props.source || props.url);
  if (!workerURL) {
    // Differentiate worker urls from worker source code
    if (props.url) {
      workerURL = getLoadableWorkerURLFromURL(props.url);
      workerURLCache.set(props.url, workerURL);
    }

    if (props.source) {
      workerURL = getLoadableWorkerURLFromSource(props.source);
      workerURLCache.set(props.source, workerURL);
    }
  }

  assert(workerURL);
  return workerURL;
}

/**
 * Build a loadable worker URL from worker URL
 * @param url
 * @returns loadable URL
 */
function getLoadableWorkerURLFromURL(url: string): string {
  // A local script url, we can use it to initialize a Worker directly
  if (!url.startsWith('http')) {
    return url;
  }

  // A remote script, we need to use `importScripts` to load from different origin
  const workerSource = buildScriptSource(url);
  return getLoadableWorkerURLFromSource(workerSource);
}

/**
 * Build a loadable worker URL from worker source
 * @param workerSource
 * @returns loadable url
 */
function getLoadableWorkerURLFromSource(workerSource: string): string {
  const blob = new Blob([workerSource], {type: 'application/javascript'});
  return URL.createObjectURL(blob);
}

/**
 * Per spec, worker cannot be initialized with a script from a different origin
 * However a local worker script can still import scripts from other origins,
 * so we simply build a wrapper script.
 *
 * @param workerUrl
 * @returns source
 */
function buildScriptSource(workerUrl: string): string {
  return `\
try {
  importScripts('${workerUrl}');
} catch (error) {
  console.error(error);
  throw error;
}`;
}
