/*
 * Module browser-fs-access 0.38.0
 * https://github.com/GoogleChromeLabs/browser-fs-access
 *
 * Copyright 2020-2023 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

async function fileOpenLegacy(options = [{}]) {
  if (!Array.isArray(options)) {
    options = [options];
  }
  return new Promise((resolve, reject) => {
    const input = document.createElement('input');
    input.type = 'file';
    const accept = [
      ...options.map((option) => option.mimeTypes || []),
      ...options.map((option) => option.extensions || []),
    ].join();
    input.multiple = options[0].multiple || false;
    input.accept = accept || '';
    input.style.display = 'none';
    document.body.append(input);

    input.addEventListener('cancel', () => {
      input.remove();
      reject(new DOMException('The user aborted a request.', 'AbortError'));
    });

    input.addEventListener('change', () => {
      input.remove();
      resolve(input.multiple ? Array.from(input.files) : input.files[0]);
    });

    if ('showPicker' in HTMLInputElement.prototype) {
      input.showPicker();
    } else {
      input.click();
    }
  });
}

async function fileSaveLegacy(blobOrPromiseBlobOrResponse, options = {}) {
  if (Array.isArray(options)) {
    options = options[0];
  }
  const a = document.createElement('a');
  let data = blobOrPromiseBlobOrResponse;
  if ('body' in blobOrPromiseBlobOrResponse) {
    data = await streamToBlob(
      blobOrPromiseBlobOrResponse.body,
      blobOrPromiseBlobOrResponse.headers.get('content-type')
    );
  }
  a.download = options.fileName || 'Untitled';
  a.href = URL.createObjectURL(await data);

  const _reject = () => cleanupListenersAndMaybeReject();
  const _resolve = () => {
    if (typeof cleanupListenersAndMaybeReject === 'function') {
      cleanupListenersAndMaybeReject();
    }
  };

  const cleanupListenersAndMaybeReject =
    options.legacySetup && options.legacySetup(_resolve, _reject, a);

  a.addEventListener('click', () => {
    setTimeout(() => URL.revokeObjectURL(a.href), 30 * 1000);
    _resolve(null);
  });
  a.click();
  return null;
}

async function streamToBlob(stream, type) {
  const reader = stream.getReader();
  const pumpedStream = new ReadableStream({
    start(controller) {
      return pump();

      async function pump() {
        return reader.read().then(({ done, value }) => {
          if (done) {
            controller.close();
            return;
          }
          controller.enqueue(value);
          return pump();
        });
      }
    },
  });

  const res = new Response(pumpedStream);
  const blob = await res.blob();
  reader.releaseLock();
  return new Blob([blob], { type });
}

const getFileWithHandle = async (handle) => {
  const file = await handle.getFile();
  file.handle = handle;
  return file;
};

async function fileOpenModern(options = [{}]) {
  if (!Array.isArray(options)) {
    options = [options];
  }
  const types = [];
  options.forEach((option, i) => {
    types[i] = {
      description: option.description || 'Files',
      accept: {},
    };
    if (option.mimeTypes) {
      option.mimeTypes.map((mimeType) => {
        types[i].accept[mimeType] = option.extensions || [];
      });
    } else {
      types[i].accept['*/*'] = option.extensions || [];
    }
  });
  const handleOrHandles = await window.showOpenFilePicker({
    id: options[0].id,
    startIn: options[0].startIn,
    types,
    multiple: options[0].multiple || false,
    excludeAcceptAllOption: options[0].excludeAcceptAllOption || false,
  });
  const files = await Promise.all(handleOrHandles.map(getFileWithHandle));
  if (options[0].multiple) {
    return files;
  }
  return files[0];
}

async function fileSaveModern(
  blobOrPromiseBlobOrResponse,
  options = [{}],
  existingHandle = null,
  throwIfExistingHandleNotGood = false,
  filePickerShown = null
) {
  if (!Array.isArray(options)) {
    options = [options];
  }
  options[0].fileName = options[0].fileName || 'Untitled';
  const types = [];
  let type = null;
  if (
    blobOrPromiseBlobOrResponse instanceof Blob &&
    blobOrPromiseBlobOrResponse.type
  ) {
    type = blobOrPromiseBlobOrResponse.type;
  } else if (
    blobOrPromiseBlobOrResponse.headers &&
    blobOrPromiseBlobOrResponse.headers.get('content-type')
  ) {
    type = blobOrPromiseBlobOrResponse.headers.get('content-type');
  }
  options.forEach((option, i) => {
    types[i] = {
      description: option.description || 'Files',
      accept: {},
    };
    if (option.mimeTypes) {
      if (i === 0 && type) {
        option.mimeTypes.push(type);
      }
      option.mimeTypes.map((mimeType) => {
        types[i].accept[mimeType] = option.extensions || [];
      });
    } else if (type) {
      types[i].accept[type] = option.extensions || [];
    } else {
      types[i].accept['*/*'] = option.extensions || [];
    }
  });
  if (existingHandle) {
    try {
      await existingHandle.getFile();
    } catch (err) {
      existingHandle = null;
      if (throwIfExistingHandleNotGood) {
        throw err;
      }
    }
  }
  const handle =
    existingHandle ||
    (await window.showSaveFilePicker({
      suggestedName: options[0].fileName,
      id: options[0].id,
      startIn: options[0].startIn,
      types,
      excludeAcceptAllOption: options[0].excludeAcceptAllOption || false,
    }));
  if (!existingHandle && filePickerShown) {
    filePickerShown(handle);
  }
  const writable = await handle.createWritable();
  if ('stream' in blobOrPromiseBlobOrResponse) {
    const stream = blobOrPromiseBlobOrResponse.stream();
    await stream.pipeTo(writable);
    return handle;
  } else if ('body' in blobOrPromiseBlobOrResponse) {
    await blobOrPromiseBlobOrResponse.body.pipeTo(writable);
    return handle;
  }
  await writable.write(await blobOrPromiseBlobOrResponse);
  await writable.close();
  return handle;
}

export const supported = (() => {
  if (typeof self === 'undefined') {
    return false;
  }
  if ('top' in self && self !== top) {
    try {
      top.window.document._ = 0;
    } catch {
      return false;
    }
  }
  if ('showOpenFilePicker' in self) {
    return true;
  }
  return false;
})();

export function isAvailable() {
    // Unlike the File API (Blob, File, FileList, FileReader),
    // the File System Access API (showOpenFilePicker, etc)
    // is available only in secure contexts.
    return supported && window.isSecureContext;
}

export async function fileOpen(...args) {
    if (!isAvailable()) {
        return fileOpenLegacy(...args);
    } else {
        return fileOpenModern(...args);
    }
}

export async function fileSave(...args) {
    if (!isAvailable()) {
        return fileSaveLegacy(...args);
    } else {
        return fileSaveModern(...args);
    }
}
