var D4S_STORAGE_SCRIPT = document.currentScript

/**
 * Base class of <d4s-storage-tree> and <d4s-storage-folder>
 */
class D4SStorageHtmlElement extends HTMLElement {

  #srcbaseurl = null
  
  constructor() {
    super()
    if (D4S_STORAGE_SCRIPT && D4S_STORAGE_SCRIPT.src) {
      this.#srcbaseurl = D4S_STORAGE_SCRIPT.src.substring(0, D4S_STORAGE_SCRIPT.src.lastIndexOf('/'));
    }
    this.attachShadow({mode: 'open'})
  }

  connectedCallback() {
    let linkElem = document.createElement('link');
    linkElem.setAttribute('rel', 'stylesheet');
    linkElem.setAttribute('href', 'https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css');
    this.shadowRoot.appendChild(linkElem);
  }

  get srcBaseURL() {
    return this.#srcbaseurl;
  }

}

class D4SStorageToolbar extends D4SStorageHtmlElement {

  static toolbar_event_name = "d4s-toolbar";

  static refresh_folder = "refresh-folder";
  static create_folder = "create-folder";
  static delete_folder = "delete-folder";
  static download = "download";
  static upload_file = "upload-file";
  static upload_archive = "upload-archive";

  #last_action = null;

  constructor() {
    super()
  }

  connectedCallback() {
    super.connectedCallback();

    var div = document.createElement('div');
    div.innerHTML = /*html*/`
    <div class="border">
      <span class="px-2" id="refresh"><img src="${this.srcBaseURL}/img/arrow-clockwise.svg" alt="Refresh folder contents"></img></span>
      <span class="px-2" id="create"><img src="${this.srcBaseURL}/img/folder-plus.svg" alt="Create new folder"></img></span>
      <span class="px-2" id="delete"><img src="${this.srcBaseURL}/img/folder-x.svg" alt="Delete folder"></img></span>
      <span class="px-2" id="download"><img src="${this.srcBaseURL}/img/box-arrow-down.svg" alt="Download folder as ZIP"></img></span>
      <span class="px-2" id="upload-file"><img src="${this.srcBaseURL}/img/box-arrow-in-up.svg" alt="Upload file"></img></span>
      <span class="px-2" id="upload-archive"><img src="${this.srcBaseURL}/img/file-earmark-zip.svg" alt="Upload archive"></img></span>
    </div>`;
    div.querySelector("#refresh").addEventListener('click', (ev) => {
      ev.stopPropagation();
      this.actionPerformed(D4SStorageToolbar.refresh_folder);
    });
    div.querySelector("#create").addEventListener('click', (ev) => {
      ev.stopPropagation();
      this.actionPerformed(D4SStorageToolbar.create_folder);
    });
    div.querySelector("#delete").addEventListener('click', (ev) => {
      ev.stopPropagation();
      this.actionPerformed(D4SStorageToolbar.delete_folder);
    });
    div.querySelector("#download").addEventListener('click', (ev) => {
      ev.stopPropagation();
      this.actionPerformed(D4SStorageToolbar.download);
    });
    div.querySelector("#upload-file").addEventListener('click', (ev) => {
      ev.stopPropagation();
      this.actionPerformed(D4SStorageToolbar.upload_file);
    });
    div.querySelector("#upload-archive").addEventListener('click', (ev) => {
      ev.stopPropagation();
      this.actionPerformed(D4SStorageToolbar.upload_archive);
    });
    this.shadowRoot.appendChild(div);
  }

  actionPerformed(action) {
    this.#last_action = action;
    this.dispatchEvent(new CustomEvent(D4SStorageToolbar.toolbar_event_name, {detail: {action: action}}));
  }

  get lastAction() {
    return this.#last_action;
  }

}

class D4SStorageTree extends D4SStorageHtmlElement {

  static tree_event_name = "d4s-toolbar";
  static folder_data_event_name = "d4s-folder-data";

  static dataid_attr = 'data-id';
  static dataname_attr = 'data-name';
  static parentid_attr = 'parent-id';

  #loadedClass = 'loaded'
  #selectedClass = 'selected'
  #selectedbgcolor = 'lightgray'

  #boot = null
  #baseurl = 'https://api.d4science.org/workspace'
  #filedownloadenabled = false;
  #showfiles = true;
  #allowdrag = false;
  #d4sworkspace = null;
  #foldersMap = {};
  #currentid = null;
  #pendingIdRequests = new Set();

  constructor() {
    super()
    this.#boot = document.querySelector('d4s-boot-2');
    this.#d4sworkspace = new D4SWorkspace(this.#baseurl, this.#boot);
  }

  get boot() {
    return this.#boot;
  }

  get baseUrl() {
    return this.#baseurl;
  }
  
  get showFiles() {
    return this.#showfiles;
  }
  
  set showFiles(value){
    this.#showfiles = value
  }
  
  get allowDrag() {
    return this.#allowdrag;
  }
  
  set allowDrag(value){
    this.#allowdrag = value
  }

  set baseUrl(url) {
    this.#baseurl = url;
    // this.setAttribute("baseurl", url)
    this.#d4sworkspace = new D4SWorkspace(this.#baseurl, this.#boot);
  }

  set fileDownloadEnabled(value) {
    this.#filedownloadenabled = value;
  }

  get fileDownloadEnabled() {
    return this.#filedownloadenabled;
  }

  get d4sWorkspace() {
    return this.#d4sworkspace;
  }

  set currentId(id) {
    this.#currentid = id;
  }

  get currentId() {
    return this.#currentid;
  }

  setFolderItems(id, items, fireEvent = true) {
    this.#foldersMap[id] = items;
    this.#pendingIdRequests.delete(id);
    if (fireEvent) {
      this.dispatchEvent(
        new CustomEvent(
          D4SStorageTree.folder_data_event_name,
          {detail: {id: id}}
        )
      );
    }
  }

  getFolderItems(id) {
    return this.#foldersMap[id];
  }

  getSelectedFolderItems() {
    this.getFolderItems(this.currentId);
  }

  static get observedAttributes() {
    return ["base-url", "file-download-enabled", "show-files", "allow-drag"];
  }

  attributeChangedCallback(name, oldValue, newValue) {
    if (oldValue !== newValue) {
      switch (name) {
        case "base-url":
          this.baseUrl = newValue;
          break;
        case "file-download-enabled":
          this.fileDownloadEnabled = (newValue.toLowerCase() === 'true');
          break;
        case "show-files":
          this.showFiles = (newValue.toLowerCase() === 'true');
          break;
        case "allow-drag":
          this.allowDrag = (newValue.toLowerCase() === 'true');
          break;
        default:
          console.warn("Unexpected attribute changed: " + name);
      }
    }
  }

  connectedCallback() {
    super.connectedCallback();

    const div = document.createElement('div')
    div.innerHTML = /*css*/`
      <style>
        .selected > span {
          background-color: ${this.#selectedbgcolor};
        }
        ul.root {
          padding-left: 0;
        }
        ul, li {
          list-style-type: none;
          margin-bottom: 0;
          padding-left: 0.6em;
        }
        li {
          cursor: pointer;
        }
        .dragover {
          background-color: purple;
        }
      </style>`;

    this.shadowRoot.appendChild(div);
    const workspaceDIV = document.createElement('div');
    const vreFolderDIV = document.createElement('div');
    div.appendChild(workspaceDIV);
    div.appendChild(vreFolderDIV);

    this.d4sWorkspace.getWorkspace().then(data => {
      this.parseItemsData(workspaceDIV, data);
    }).catch(err => console.error(err));

    this.d4sWorkspace.getVREFolder().then(data => {
      this.parseItemsData(vreFolderDIV, data);
    }).catch(err => console.error(err))

    this.addEventListener(D4SStorageTree.folder_data_event_name, this.folderDataEventHandler);
    if (document.readyState === "loading") {
      document.addEventListener("DOMContentLoaded", this.registerOtherListeners.bind(this));
    }
  }

  disconnectedCallback() {
    this.removeEventListener(D4SStorageTree.folder_data_event_name);
    const toolbar = document.querySelector("d4s-storage-tool");
    if (toolbar) toolbar.removeEventListener(D4SStorageToolbar.toolbar_event_name);
    const folder = document.querySelector("d4s-storage-folder");
    if (folder) folder.removeEventListener(D4SStorageFolder.selected_event_name);
  }

  registerOtherListeners(event) {
    const toolbar = document.querySelector("d4s-storage-tool");
    if (toolbar) toolbar.addEventListener(D4SStorageToolbar.toolbar_event_name, this.toolbarEventHandler.bind(this));
    const folder = document.querySelector("d4s-storage-folder");
    if (folder) folder.addEventListener(D4SStorageFolder.selected_event_name, this.folderEventHandler.bind(this));
  }

  folderDataEventHandler(event) {
    this.fillWithContent(this.shadowRoot.querySelector(`*[${D4SStorageTree.dataid_attr}='${event.detail.id}']`))
  }

  toolbarEventHandler(event) {
    switch (event.detail.action) {
      case D4SStorageToolbar.refresh_folder:
        if (this.currentId) {
          this.refreshFolder(this.currentId);
        }
        break;
      case D4SStorageToolbar.create_folder:
        if (this.currentId) {
          this.createNewFolderIn(this.currentId);
        }
        break;
      case D4SStorageToolbar.delete_folder:
        if (this.currentId) {
          this.delete(this.currentId);
        }
        break;
      case D4SStorageToolbar.download:
        if (this.currentId) {
          const name = this.shadowRoot.querySelector(`*[${D4SStorageTree.dataid_attr}='${this.currentId}']`).getAttribute(D4SStorageTree.dataname_attr);
          this.d4sWorkspace.download(this.currentId, name);
        }
        break;
      case D4SStorageToolbar.upload_file:
        var file = document.createElement('input');
        file.type = 'file';
        file.addEventListener('change', (ev) => {
          const selectedFile = file.files[0];
          if (selectedFile) {
            this.uploadFile(this.currentId, selectedFile);
          }
        });
        file.click();
        break;
      case D4SStorageToolbar.upload_archive:
        var file = document.createElement('input');
        file.type = 'file';
        file.addEventListener('change', (ev) => {
          const selectedFile = file.files[0];
          if (selectedFile) {
            this.uploadArchive(this.currentId, selectedFile);
          }
        });
        file.click();
        break;
      default:
        console.error('Unexpected event action: ' + event.detail.action);
        break;
    }
  }

  folderEventHandler(event) {
    switch (event.detail.itemtype) {
      case D4SStorageFolder.file_selected_detail_itemtype:
        if (this.fileDownloadEnabled) {
          console.info("Download of file: " + event.detail.name + " [" + event.detail.id + "]");
          this.d4sWorkspace.download(event.detail.id, event.detail.name).catch(err => {alert(err)});
        } else {
          console.log("File download is disabled");
        }
        break;
      case D4SStorageFolder.folder_selected_detail_itemtype:
        this.select(event.detail.id);
        break;
      default:
        console.warn("Unexpected folder event: " + event.detail.itemtype);
        break;
    }
  }

  select(id) {
    this.currentId = id;
    const li = this.shadowRoot.querySelector(`*[${D4SStorageTree.dataid_attr}='${id}']`);
    this.displayPath(li);

    if (li.classList.contains(this.#selectedClass)) {
      this.toggleULDisplay(li);
    }
    // switch selected and lists folder contents
    if (!li.classList.contains(this.#selectedClass)) {
      const selected = this.shadowRoot.querySelector('.' + this.#selectedClass)
      if (selected) {
        selected.classList.remove(this.#selectedClass)
      }
      li.classList.add(this.#selectedClass)
    }
    if (!li.classList.contains(this.#loadedClass)) {
      this.fillWithContent(li);
    }

    this.dispatchEvent(
      new CustomEvent(
        D4SStorageTree.tree_event_name,
        {detail: {id: id}}
      )
    );
  }

  refreshContents(element) {
    [...element.querySelectorAll(`ul`)].forEach(ul => {
      ul.remove();
    });
    this.fillWithContent(element);
  }

  fillWithContent(element) {
    const id = element.getAttribute(D4SStorageTree.dataid_attr);
    const contents = this.getFolderItems(id);
    if (contents) {
      this.parseItemsData(element, contents);
    } else {
      if (!this.#pendingIdRequests.has(id)) {
        this.#pendingIdRequests.add(id);
        this.d4sWorkspace.listFolder(id).then(data => {
          this.setFolderItems(id, data);
        }).catch(err => console.error(err));
      }
    }
  }

  parseItemsData(parentElement, data) {
    parentElement.classList.add(this.#loadedClass);
    const parentId = parentElement.getAttribute(D4SStorageTree.dataid_attr);
    const ul = document.createElement('ul');
    ul.classList.add('nested');
    if (!parentId) {
      ul.classList.add('root');
    }
    if (Array.isArray(data)) {
      data
      .filter(item => this.showFiles || item['@class'].includes('FolderItem'))
      .forEach(item => {
        ul.appendChild(this.createListItem(item.title, item.id, item['@class'], parentId));
      })
    } else {
      ul.appendChild(this.createListItem(data.displayName ? data.displayName : data.title, data.id, data['@class'], parentId));
    }
    parentElement.appendChild(ul);
  }

  createListItem(label, id, type, parentId) {
    const li = document.createElement('li');
    li.setAttribute(D4SStorageTree.dataname_attr, label);
    li.setAttribute(D4SStorageTree.dataid_attr, id);
    li.style.userSelect = "none"
    li.title = li.alt = label
    if (parentId) {
      li.setAttribute(D4SStorageTree.parentid_attr, parentId);
    }
    const icon = type.includes('FolderItem') || type.includes("SharedFolder")? "folder.svg" : "file-earmark.svg"
    li.innerHTML = `
      <img class="px-1" src="${this.srcBaseURL}/img/${icon}"</img>
      <span>${label}</span>
    `

    li.addEventListener('click', (ev) => {
      ev.stopPropagation();
      this.select(ev.currentTarget.getAttribute(D4SStorageTree.dataid_attr));
    });
    
    if(this.allowDrag){
      li.setAttribute("draggable", true)
    
      li.addEventListener("dragstart", ev=>{
        const id = ev.currentTarget.getAttribute(D4SStorageTree.dataid_attr)
        ev.dataTransfer.effectAllowed = 'copy'
        ev.dataTransfer.setData('text/html', ev.currentTarget.querySelector("span").innerHTML)
        ev.dataTransfer.setData('text/plain+publiclink', this.#d4sworkspace.getPublicLink(id))
        ev.dataTransfer.setData('text/plain+downloadlink', this.#d4sworkspace.getDownloadLink(id))
        ev.stopPropagation()
      })
      
      li.addEventListener("dragend", ev=>{
        ev.preventDefault()
        ev.stopPropagation()
      })
    }
    // li.addEventListener("dragenter", dee =>{
    //   dee.stopPropagation();
    //   dee.preventDefault();
    //   if (li == dee.target) {
    //     dee.target.classList.add("dragover");
    //   }
    // }, false);
    // li.addEventListener("dragleave", dle =>{
    //   dle.stopPropagation();
    //   dle.preventDefault();
    //   if (li == dle.target) {
    //     dle.target.classList.remove("dragover");
    //   }
    // }, false);
    // li.addEventListener("dragover", doe => {
    //   doe.stopPropagation();
    //   doe.preventDefault();
    // }, false);
    // li.addEventListener("drop", de => {
    //   de.stopPropagation();
    //   de.preventDefault();
    
    //   const dt = de.dataTransfer;
    //   const files = dt.files;
    
    //   console.log(files);
    // }, false);
    return li;
  }

  displayPath(li) {
    let curr = li
    while (curr.parentElement != null) {
      if (curr.parentElement.tagName == 'UL') {
        curr.parentElement.style.display = 'block'
      }
      curr = curr.parentElement
    }
  }

  toggleULDisplay(li) {
    const ul = li.querySelector('ul')
    if (ul) {
      ul.style.display = (ul.style.display === 'none') ? 'block' : 'none'
    }
  }

  refreshFolder(id) {
    const folder = this.shadowRoot.querySelector(`*[${D4SStorageTree.dataid_attr}='${id}']`);
    console.log("Refreshing folder: " + folder.getAttribute(D4SStorageTree.dataname_attr));
    this.setFolderItems(id, null, false);
    this.refreshContents(folder);
    // this.select(id);
  }

  createNewFolderIn(id) {
    const newFolderName = window.prompt("Enter the new folder name");
    if (newFolderName) {
      this.d4sWorkspace.getOrCreateFolder(id, newFolderName, "Created from JS UI").then(newId => {
        window.setTimeout(()=>{
          this.refreshContents(this.shadowRoot.querySelector(`*[${D4SStorageTree.dataid_attr}='${id}']`));
          this.select(id);
        }, 2000);
      }).catch(err => {
        alert(err);
      })
    }
  }

  delete(id) {
    const currentLI = this.shadowRoot.querySelector(`*[${D4SStorageTree.dataid_attr}='${id}']`);
    const name = currentLI.getAttribute(D4SStorageTree.dataname_attr);
    if (currentLI.getAttribute(D4SStorageTree.parentid_attr)) {
      if (confirm(`Do you really want to delete folder '${name}'?`)) {
        if(this.d4sWorkspace.deleteItem(id)) {
          window.setTimeout(()=>{
            const parentFolderId = currentLI.getAttribute(D4SStorageTree.parentid_attr);
            this.refreshContents(this.shadowRoot.querySelector(`*[${D4SStorageTree.dataid_attr}='${parentFolderId}']`));
            this.select(parentFolderId);
          }, 2000);
        } else {
          alert(err);
        }
      }
    } else {
      alert("You cannot delete ROOT folder: "+ name);
    }
  }

  uploadFile(targetFolderId, file) {
    if (this.d4sWorkspace.uploadFile(targetFolderId, file.name, "", file, file.type)) {
      window.setTimeout(()=>{
        this.select(targetFolderId);
      }, 2000);
    }
  }

  uploadArchive(targetFolderId, file) {
    if (this.d4sWorkspace.uploadArchive(targetFolderId, file.name.substring(0, file.name.lastIndexOf(".")), file)) {
      window.setTimeout(()=>{
        this.refreshContents(this.shadowRoot.querySelector(`*[${D4SStorageTree.dataid_attr}='${targetFolderId}']`));
        this.select(targetFolderId);
      }, 3000);
    }
  }

}

class D4SStorageFolder extends D4SStorageHtmlElement {

  static selected_event_name = "d4s-folder-selected";

  static folder_selected_detail_itemtype = "folder_selected";
  static file_selected_detail_itemtype = "file_selected";

  #selectedbgcolor = 'lightgray'

  constructor() {
    super()
  }

  connectedCallback() {
    super.connectedCallback();

    const style = document.createElement('style')
    style.innerHTML = /*css*/`
      span {
        cursor: pointer;
        user-select: none;
        -moz-user-select: none;
        -webkit-user-select: none;
        -ms-user-select: none;
      }
    `
    this.shadowRoot.appendChild(style);
    this.createContainer();

    if (document.readyState === "loading") {
      document.addEventListener("DOMContentLoaded", this.registerOtherListeners.bind(this));
    }
  }

  disconnectedCallback() {
    const tree = document.querySelector("d4s-storage-tree");
    if (tree) { 
      tree.removeEventListener(D4SStorageTree.folder_data_event_name);
      tree.removeEventListener(D4SStorageTree.tree_event_name);
    }
  }

  registerOtherListeners(event) {
    const tree = document.querySelector("d4s-storage-tree");
    if (tree) {
      tree.addEventListener(D4SStorageTree.folder_data_event_name, this.folderDataEventHandler.bind(this));
      tree.addEventListener(D4SStorageTree.tree_event_name, this.treeEventHandler.bind(this));
    }
  }
  
  folderDataEventHandler(event) {
    if (event.detail.id === event.target.currentId) {
      const folderItems = event.target.getFolderItems(event.detail.id);
      if (folderItems) {
        this.parseItemsData(folderItems);
      }
    }
  }

  treeEventHandler(event) {
    const folderItems = event.target.getFolderItems(event.detail.id);
    if (folderItems) {
      // It's first time folder is selected, data is stil not loaded,
      // they will be loaded by the folderDataEventHandler() function when available
      this.parseItemsData(folderItems);
    }
  }

  createContainer() {
    var div = document.createElement('div')
    div.classList.add('d4s-folder')
    var child = this.shadowRoot.querySelector('.d4s-folder')
    if (child) {
      this.shadowRoot.removeChild(child)
    }
    this.shadowRoot.appendChild(div)
    return div
  }

  parseItemsData(data) {
    const root = this.createContainer();
    if (Array.isArray(data)) {
      data.forEach(item => root.appendChild(this.createFileRow(item)));
    } else {
      console.error("Returned data is not an array: " + data);
    }
  }

  createFileRow(item) {
    var div = document.createElement('div')
    div.classList.add('row')
    var filename = document.createElement('div')
    filename.classList.add('col')
    var spanAndName = /*html*/`<span>${item.name}</span>`
    filename.innerHTML = this.iconTag(item) + spanAndName
    const isFolder = item['@class'].includes('FolderItem')
    filename.addEventListener('click', (ev) => {
      ev.stopPropagation()
      if (isFolder) {
        const span = ev.currentTarget.querySelector(':nth-child(2)')
        if (span) {
            span.style = 'background-color: ' + this.#selectedbgcolor
        }
        this.dispatchEvent(
          new CustomEvent(
            D4SStorageFolder.selected_event_name,
            {detail: {itemtype: D4SStorageFolder.folder_selected_detail_itemtype, id: item.id, name: item.name}}
          )
        );
      } else {
        this.dispatchEvent(
          new CustomEvent(
            D4SStorageFolder.selected_event_name,
            {detail: {itemtype: D4SStorageFolder.file_selected_detail_itemtype, id: item.id, name: item.name}}
          )
        );
      }
    })

    div.appendChild(filename)
    return div
  }

  iconTag(item) {
    var i = /*html*/`<img src="${this.srcBaseURL}/img/file-earmark.svg"></img>`
    if (item['@class'].includes('FolderItem')) {
      i = /*html*/`<img src="${this.srcBaseURL}/img/folder.svg"></img>`
    } else if (item['@class'].includes('ImageFile')) {
      i = /*html*/`<img src="${this.srcBaseURL}/img/image.svg"></img>`
    } else if (item['@class'].includes('PDFFileItem')) {
      i = /*html*/`<img src="${this.srcBaseURL}/img/filetype-pdf.svg"></img>`
    }
    return '<span class="px-2">' + i + '</span>'
  }

}

/**
 * Class that helps the interaction with the D4Science's Workspace via API and user's token as bearer authentication
 */
class D4SWorkspace {

  #d4sboot = null;
  #workspaceURL = null;

  /**
   * Creates the new D4SWorkspace object, the d4sboot parameter can be null, in this case it is searched in the DOM document via <code>querySelector()</code> method
   * @param {string} workspaceURL the WS API base URL
   * @param {d4s-boot-2} d4sboot the d4s-boot-2 instance
   */
  constructor(workspaceURL, d4sboot) {
    this.setWorkspaceURL(workspaceURL)
    if (d4sboot) {
      this.setD4SBoot(d4sboot)
    } else {
      this.setD4SBoot(document.querySelector("d4s-boot-2"));
    }
  }

  /**
   * Sets the Workspace API base URL
   * @param {string} workspaceURL the WS API base URL
   */
  setWorkspaceURL(workspaceURL) {
    this.#workspaceURL = workspaceURL;
  }

  /**
   * Sets the d4s-boot-2 instance to be used to perform the OIDC/UMA autenticated secure fetches
   * @param {string} d4sboot the d4s-boot-2 instance
   */
  setD4SBoot(d4sboot) {
    this.#d4sboot = d4sboot;
  }

  getPublicLink(itemId){
    return this.#workspaceURL + "/items/" + itemId + "/publiclink"
  }
  
  getDownloadLink(itemId){
    return this.#workspaceURL + "/items/" + itemId + "/download"
  }

  /**
   * Calls with a secure fetch the workspace with provided data.
   * To upload data by using a <code>FormData</code> object you have to leave the <code>mime</code> attribute as <code>null</code>.
   * @param {*} method the method to use for the call, default to <code>GET</code>
   * @param {*} uri the uri to invoke, relative to <code>workspaceURL</code> provided in the object's constructor
   * @param {*} body the payload to send
   * @param {*} mime the mime type of the payload
   * @param {*} extraHeaders extra HTTP headers to send
   * @returns the reponse payload as a Blob() promise
   * @throws the error as string, if occurred
   */
  #callWorkspace(method, uri, body, mime, extraHeaders) {
    let req = { };
    if (method) {
      req.method = method;
    }
    if (body) {
      req.body = body;
    }
    if (extraHeaders) {
      req.headers = extraHeaders;
    }
    if (mime) {
      if (req.headers) {
        req.headers["Content-Type"] = mime;
      } else {
        req.headers = { "Content-Type" : mime };
      }
    }
    let url = this.#workspaceURL;
    if (uri) {
      url += uri.startsWith('/') ? uri : '/' + uri;
    }
    console.log("Invoking WS API with: " + url);
    return this.#d4sboot.secureFetch(url, req)
      .then(resp => {
        if (resp.ok) {
          return resp.blob();
        } else {
          throw "Cannot invoke workspace via secure fetch for URL: " + url;
        }
      }).catch(err => {
        console.error(err);
        throw err;
      });
  }

  /**
   * Gets the user's Workspace root folder JSON item
   * @param {map} extraHeaders extra HTTP headers to be used for the request
   * @returns the WS root folder JSON item object
   * @throws the error as string, if occurred
   */
  getWorkspace(extraHeaders) {
    return this.#callWorkspace("GET", null, null, null, extraHeaders)
      .then(blob => {
        return blob.text().then(text => {
          return JSON.parse(text).item;
        })
      }).catch(err => {
        const msg = "Cannot get workspace root ID. Error message: " + err;
        console.error(msg);
        throw msg;
      });
  }

  /**
   * Gets the user's Workspace root folder id (as string)
   * @param {map} extraHeaders extra HTTP headers to be used for the request
   * @returns the id of the WS root folder
   * @throws the error as string, if occurred
   */
  getWorkspaceId(extraHeaders) {
    return this.getWorkspace(extraHeaders)
      .then(json => {
        return json.item.id;
      });
  }

  /**
   * Gets the contents of a folder, all sub-folders and files.
   * @param {string} folderId the WS folder id to list
   * @param {map} extraHeaders extra HTTP headers to be used for the request
   * @returns the contents of the WS folder as json objects
   * @throws the error as string, if occurred
   */
  listFolder(folderId, extraHeaders) {
    const uri = "/items/" + folderId + "/children";
    return this.#callWorkspace("GET", uri, null, null, extraHeaders)
      .then(blob => {
        return blob.text().then(text => {
          return JSON.parse(text).itemlist;
        });
      }).catch(err => {
        const msg = "Cannot get folder's content list. Error message: " + err;
        console.error(msg);
        throw msg;
      });
  }

  /**
   * Gets the VRE folder JSON item that belongs to the context where the access token has been issued.
   * @param {map} extraHeaders extra HTTP headers to be used for the request
   * @returns the id of the WS VRE folder
   * @throws the error as string, if occurred
   */
  getVREFolder(extraHeaders) {
    return this.#callWorkspace("GET", "/vrefolder", null, null, extraHeaders)
      .then(blob => {
        return blob.text().then(text => {
          return JSON.parse(text).item;
        });
      }).catch(err => {
        const msg = "Cannot get VRE folder ID. Error message: " + err;
        console.error(msg);
        throw msg;
      });
  }

  /**
   * Gets the VRE folder id (as string) that belongs to the context where the access token has been issued.
   * @param {map} extraHeaders extra HTTP headers to be used for the request
   * @returns the id of the WS VRE folder
   * @throws the error as string, if occurred
   */
  getVREFolderId(extraHeaders) {
    return this.getVREFolder(extraHeaders)
      .then(item => {
        return item.id;
      });
  }

  /**
   * @deprecated please use getOrCreateFolder() function instead
   * @param {string} parentFolderId the id of the parent folder where search fo the folder
   * @param {string} name the name of the folder (characters not allowed: ":", [others still to be found])
   * @param {string} description the description of the folder if it should be creaded
   * @param {map} extraHeaders extra HTTP headers to be used for the request 
   * @returns the id of the folder, if it exists or it has been just created
   * @throws the error as string, if occurred
   */
  checkOrCreateFolder(parentFolderId, name, description, extraHeaders) {
    return this.getOrCreateFolder(parentFolderId, name, description, extraHeaders);
  }

  /**
   * Gets the id of the folder represented by the name parameter if exists, a new folder is created if it doesn't not exist
   * @param {string} parentFolderId the id of the parent folder where search fo the folder
   * @param {string} name the name of the folder (characters not allowed: ":", [others still to be found])
   * @param {string} description the description of the folder if it should be creaded
   * @param {map} extraHeaders extra HTTP headers to be used for the request 
   * @returns the id of the folder, if it exists or it has been just created
   * @throws the error as string, if occurred
   */
  getOrCreateFolder(parentFolderId, name, description, extraHeaders) {
    const baseURI = "/items/" + parentFolderId;
    console.log("Checking existance of folder: " + name);
    let uri =  baseURI + "/items/" + name;
    return this.#callWorkspace("GET", uri, null, null, extraHeaders)
      .then(blob => {
        return blob.text().then(text => {
          const json = JSON.parse(text);
          if (json.itemlist[0]) {
            const id = json.itemlist[0].id;
            console.log("'" + name + "' folder exists with and has id: " + id);
            return id;
          } else {
            console.info("'" + name + "' folder doesn't exist, creating it: " + name);
            uri = baseURI + "/create/FOLDER";
            const params = new URLSearchParams({ name : name, description : description, hidden : false });
            return this.#callWorkspace("POST", uri, params.toString(), "application/x-www-form-urlencoded", extraHeaders).then(id => {
              console.log("New '" + name + "' folder successfully created with id: " + id);
              return id
            });
          }
        });
      }); 
  };

  /**
   * Deletes a Workspace item (folder or file) represented by its id.
   * @param {string} itemId the id of the item do delete
   * @param {map} extraHeaders extra HTTP headers to be used for the request
   * @returns <code>true</code> if the file has been correctly created, <code>false<c/ode> otherwise
   */
  deleteItem(itemId, extraHeaders) {
    return this.#callWorkspace("DELETE", "/items/" + itemId, null, null, extraHeaders)
      .then(results => {
        return true;
      }).catch(err => {
        console.error("Cannot DELETE workspace item with ID: " + itemId + ". Error message: " + err);
        return false;
      });
  }

  /**
   * Downloads a Workspace item (folder or file) represented by its id.
   * @param {string} itemId the WS item to download
   * @param {string} name the name of the file to use for the download, if null the id is used
   * @param {boolean} skipLocalDownload if provided and <code>true</code> returns the data to function caller, otherwise it also downloads it locally.
   * @param {map} extraHeaders extra HTTP headers to be used for the request
   * @returns the file's content, if item is a file, or a ZIP compressed archive if the item represents a folder, as a Blob object
   * @throws the error as string, if occurred
   */
  download(itemId, name, skipLocalDownload, extraHeaders) {
    return this.#callWorkspace("GET", "/items/" + itemId + "/download", null, null, extraHeaders)
      .then(data => {
        if (!skipLocalDownload) {
          console.log("Donwloads item also locally to browser");
          const objectURL = URL.createObjectURL(data);
          var tmplnk = document.createElement("a");
          tmplnk.download = name ? name : itemId;
          tmplnk.href = objectURL;
          //tmplnk.target="_blank";
          document.body.appendChild(tmplnk);
          tmplnk.click();
          document.body.removeChild(tmplnk);
        } else {
          console.log("Skipping local download");
        }
        return data;
      }).catch(err => {
        const msg = "Cannot download item with ID: " + itemId + ". Error message: " + err;
        console.error(msg);
        throw msg;
      });
  }

  /**
   * Uploads a new file into the folder represented by its id
   * @param {string} folderId the folder where to create the new file
   * @param {string} name the name of the file to crete (characters not allowed: ":", [others still to be found])
   * @param {object} data the archive data
   * @param {string} contentType the content type of the file
   * @param {map} extraHeaders extra HTTP headers to be used for the request  
   * @returns <code>true</code> if the file has been correctly created, <code>false<c/ode> otherwise
   */
  uploadFile(folderId, name, description, data, contentType, extraHeaders) {
    const uri = "/items/" + folderId + "/create/FILE";
    const request = new FormData();
    request.append("name", name);
    request.append("description", description);
    request.append(
      "file",
      new Blob([data], {
        type: contentType
      })
    );
    return this.#callWorkspace("POST", uri, request, null, extraHeaders)
      .then(blob => {
        return blob.text().then(id => {
          console.info("File '" + name + "' successfully uploaded and its id is: " + id);
          return true;
        })
      }).catch(err => {
        console.error("Cannot upload file '" + name + "'. Error: " + err);
        return false;
      });
  }

  /**
   * Uploads a new archive and stores its content into the folder represented by its id
   * @param {string} folderId the folder where to create the new file
   * @param {string} parentFolderName the name of the folder to create (characters not allowed: ":", [others still to be found])
   * @param {object} data the file's contents
   * @param {map} extraHeaders extra HTTP headers to be used for the request  
   * @returns <code>true</code> if the file has been correctly created, <code>false<c/ode> otherwise
   */
  uploadArchive(folderId, parentFolderName, data, extraHeaders) {
    const uri = "/items/" + folderId + "/create/ARCHIVE";
    const request = new FormData();
    request.append("parentFolderName", parentFolderName);
    request.append(
      "file",
      new Blob([data], {
        type: "application/octet-stream"
      })
    );
    return this.#callWorkspace("POST", uri, request, null, extraHeaders)
      .then(blob => {
        return blob.text().then(id => {
          console.info("Archive file successfully uploaded and extracted into the '" + parentFolderName + "' folder, its id is: " + id);
          return true;
        })
      }).catch(err => {
        console.error("Cannot upload archive file. Error: " + err);
        return false;
      });
  }

  /**
   * Returns the public URL of a file
   * @param {string} itemId the id of the item
   * @param {int} version the optional file version
   * @param {map} extraHeaders extra HTTP headers to be used for the request
   * @returns the public URL of the item
   * @throws the error as string, if occurred
   */
  getPublicURL(itemId, version, extraHeaders) {
    let uri = "/items/" + itemId + "/publiclink";
    if (version) {
      uri += "version=" + version;
    }
    return this.#callWorkspace("GET", uri, null, null, extraHeaders)
      .then(blob => {
        return blob.text().then(publicURL => {
          return publicURL;
        })
      }).catch(err => {
        const message = "Cannot retrieve the item's public URL. Error message: " + err;
        console.error(message);
        throw message;
      });
  }

  /**
   * Finds into a folder all the file files having the name than matches a specific pattern.
   * Wildcard (*) can be used at the start or the end of the pattern (eg. starts with *{name}, ends with = {name}*)
   * @param {string} folderId the WS folder id where to search
   * @param {string} pattern the pattern to use for the search. Wildcard '*' can be used at the start or the end of the pattern (eg. starts with *{name}, ends with = {name}*)
   * @param {map} extraHeaders extra HTTP headers to be used for the request
   * @returns the contents of the WS folder as json objects
   * @throws the error as string, if occurred
   */
  findByName(folderId, pattern, extraHeaders) {
    const uri = "/items/" + folderId + "/items/" + pattern;
    return this.#callWorkspace("GET", uri, null, null, extraHeaders)
      .then(blob => {
        return blob.text().then(text => {
          return JSON.parse(text).itemlist;
        });
      }).catch(err => {
        const msg = "Cannot get folder's content list. Error message: " + err;
        console.error(msg);
        throw msg;
      });
  }
}

/**
 * Builds storagehub toolbar and raises an event for each selected tool item
 * <d4s-storage-tool>
 */
window.customElements.define('d4s-storage-tool', D4SStorageToolbar);

/**
 * Builds storagehub VRE folder or Workspace root tree, linked to toolbar and foldr list by events
 * <d4s-storage-tree
 *  /@base-url: the base endpoint url string [defaults to: https://api.d4science.org/workspace]
 *  /@file-download-enabled: true/false to enable/disable the file download on d4s-storage-tool click event (false by default)
 */
window.customElements.define('d4s-storage-tree', D4SStorageTree);
/**
 * Lists elements in selected folder linked to tree and model by events
 * <d4s-storage-folder>
 */
window.customElements.define('d4s-storage-folder', D4SStorageFolder);
