class CCPMethodList extends HTMLElement{
  
    #boot;
    #rootdoc;
    #data = null;
    #filtered;
    #dragged = null;
    #searchfield = null;
    #allowedit = false;
    #allowexecute = false;
    #archive = false;
    #fileupload = null;
    #archiveupload = null;
    
    #serviceurl; 
     
    constructor(){
      super()
      this.#boot = document.querySelector("d4s-boot-2")
      this.#rootdoc = this.attachShadow({ "mode" : "open"})
      this.#archive = this.getAttribute("archive")
    }
  
    render(){
      this.#rootdoc.innerHTML = `
        <link rel="stylesheet" href="https://cdn.dev.d4science.org/ccp/css/common.css"></link>
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
        <style>
          .ccp-process-category-list{
            list-style:none;
            padding-left: 0;
          }
          .ccp-process-category{
            user-select: none;
          }
          .ccp-process-list{
            list-style:none;
          }
          .ccp-process {
            
          }
        </style>
        <template id="PROCESS_LIST_TEMPLATE">
          <ul name="process_category_list" class="border border-2 list-group ccp-process-category-list">
            <li class="list-group-item list-group-item-dark ccp-process-category">
              <details>
                <summary>
                  <h5 class="d-inline mr-2 text-primary"></h5>
                  <div class="float-right d-flex" style="gap:2px">
                    <span name="count_notexecutables" title="Number of non executable methods" class="badge border border-danger text-danger"></span>
                    <span name="count_executables" title="Number of executable methods" class="badge border border-success text-success"></span>
                    <button name="export_category" title="Export whole category" class="btn btn-primary ccp-toolbar-button ccp-toolbar-button-small">
                        <svg viewBox="0 0 24 24"><g><rect fill="none" height="24" width="24"/></g><g><path d="M18,15v3H6v-3H4v3c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2v-3H18z M17,11l-1.41-1.41L13,12.17V4h-2v8.17L8.41,9.59L7,11l5,5 L17,11z"/></g></svg>
                    </button>                    
                  </div>
                </summary>
                <ul name="process_list" class="list-group ccp-process-list">
                  <li class="d-flex list-group-item list-group-item-secondary ccp-process p-2 my-1" style="flex-direction: column; gap:5px" draggable="true">
                    <div>
                      <span name="title" class="h5"></span>
                      <span name="version" class="badge badge-primary"></span>
                      <span name="author" class="badge badge-warning"></span>
                      <div class="float-right d-flex" style="gap:3px">
                        <button name="executable" title="Execute" class="btn btn-success ccp-toolbar-button ccp-toolbar-button-small">
                          <svg viewBox="0 0 48 48" style="fill:white; stroke:white">
                            <path d="M10,10 v28 L38,24 Z"/>
                          </svg>
                        </button>
                        <button name="export_method" title="Export this version" class="btn btn-primary ccp-toolbar-button ccp-toolbar-button-small">
                          <svg viewBox="0 0 24 24"><g><rect fill="none"/></g><g><path d="M18,15v3H6v-3H4v3c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2v-3H18z M17,11l-1.41-1.41L13,12.17V4h-2v8.17L8.41,9.59L7,11l5,5 L17,11z"/></g></svg>
                        </button>
                        <button name="edit" title="Edit" class="btn btn-primary ccp-toolbar-button ccp-toolbar-button-small">
                          <svg viewBox="0 0 48 48">
                            <path d="M9 39h2.2l22.15-22.15-2.2-2.2L9 36.8Zm30.7-24.3-6.4-6.4 2.1-2.1q.85-.85 2.1-.85t2.1.85l2.2 2.2q.85.85.85 2.1t-.85 2.1Zm-2.1 2.1L12.4 42H6v-6.4l25.2-25.2Zm-5.35-1.05-1.1-1.1 2.2 2.2Z"/>
                          </svg>
                        </button>
                        ${ this.#archive ? `
                          <button data-index="0" name="archive" title="Archive to workspace" class="btn btn-primary ccp-toolbar-button ccp-toolbar-button-small">
                             <svg viewBox="0 96 960 960"><path d="M140 796h680V516H140v280Zm540.118-90Q701 706 715.5 691.382q14.5-14.617 14.5-35.5Q730 635 715.382 620.5q-14.617-14.5-35.5-14.5Q659 606 644.5 620.618q-14.5 14.617-14.5 35.5Q630 677 644.618 691.5q14.617 14.5 35.5 14.5ZM880 456h-85L695 356H265L165 456H80l142-142q8-8 19.278-13 11.278-5 23.722-5h430q12.444 0 23.722 5T738 314l142 142ZM140 856q-24.75 0-42.375-17.625T80 796V456h800v340q0 24.75-17.625 42.375T820 856H140Z"/></svg>
                          </button>`
                          : ``
                        }
                        <button data-index="0" name="publish" title="Share with vlab" class="btn btn-warning ccp-toolbar-button ccp-toolbar-button-small">
                            <svg style="fill:black" viewBox="0 -960 960 960"><path d="M727-80q-47.5 0-80.75-33.346Q613-146.693 613-194.331q0-6.669 1.5-16.312T619-228L316-404q-15 17-37 27.5T234-366q-47.5 0-80.75-33.25T120-480q0-47.5 33.25-80.75T234-594q23 0 44 9t38 26l303-174q-3-7.071-4.5-15.911Q613-757.75 613-766q0-47.5 33.25-80.75T727-880q47.5 0 80.75 33.25T841-766q0 47.5-33.25 80.75T727-652q-23.354 0-44.677-7.5T646-684L343-516q2 8 3.5 18.5t1.5 17.741q0 7.242-1.5 15Q345-457 343-449l303 172q15-14 35-22.5t46-8.5q47.5 0 80.75 33.25T841-194q0 47.5-33.25 80.75T727-80Zm.035-632Q750-712 765.5-727.535q15.5-15.535 15.5-38.5T765.465-804.5q-15.535-15.5-38.5-15.5T688.5-804.465q-15.5 15.535-15.5 38.5t15.535 38.465q15.535 15.5 38.5 15.5Zm-493 286Q257-426 272.5-441.535q15.5-15.535 15.5-38.5T272.465-518.5q-15.535-15.5-38.5-15.5T195.5-518.465q-15.5 15.535-15.5 38.5t15.535 38.465q15.535 15.5 38.5 15.5Zm493 286Q750-140 765.5-155.535q15.5-15.535 15.5-38.5T765.465-232.5q-15.535-15.5-38.5-15.5T688.5-232.465q-15.5 15.535-15.5 38.5t15.535 38.465q15.535 15.5 38.5 15.5ZM727-766ZM234-480Zm493 286Z"/></svg>
                       </button>
                      </div>
                    </div>
                    <p class="m-0 p-0 small" name="description"></p>
                    <div>
                      <span name="keyword" class="badge badge-pill badge-light border border-dark mr-1" style="opacity:.6"></span>
                    </div>
                    <div style="overflow:hidden">
                      <span name="infrastructures" class="badge badge-light text-info border border-info mr-1" style="text-overflow:ellipsis"></span>
                    </div>
                  </li>
                </ul>
              </details>
            </li>
          </ul>
        </template>
        <div class="card">
          <div class="card-header">
            <div class="ccp-toolbar-header d-flex flex-wrap justify-content-between">
              <div>
                <span name="header">Methods</span>        
              </div>
              <div class="d-flex flex-wrap" style="gap:2px">  
                <button name="refresh" class="btn btn-primary ccp-toolbar-button" title="Refresh">
                  <svg viewBox="0 0 48 48"><path d="M24 40q-6.65 0-11.325-4.675Q8 30.65 8 24q0-6.65 4.675-11.325Q17.35 8 24 8q4.25 0 7.45 1.725T37 14.45V8h3v12.7H27.3v-3h8.4q-1.9-3-4.85-4.85Q27.9 11 24 11q-5.45 0-9.225 3.775Q11 18.55 11 24q0 5.45 3.775 9.225Q18.55 37 24 37q4.15 0 7.6-2.375 3.45-2.375 4.8-6.275h3.1q-1.45 5.25-5.75 8.45Q29.45 40 24 40Z"/></svg>
                </button>
                <label name="fileupload" class="btn btn-primary ccp-toolbar-button m-0" title="Upload from file">
                  <svg viewBox="0 96 960 960"><path d="M452 854h60V653l82 82 42-42-156-152-154 154 42 42 84-84v201ZM220 976q-24 0-42-18t-18-42V236q0-24 18-42t42-18h361l219 219v521q0 24-18 42t-42 18H220Zm331-554V236H220v680h520V422H551ZM220 236v186-186 680-680Z"/></svg>
                  <input type="file" class="d-none" multiple="multiple"/>
                </label>
                <div class="d-flex" style="gap:2px">
                  <input type="text" class="form-control" placeholder="Paste link here"/>
                  <button name="archive" class="btn btn-primary ccp-toolbar-button m-0" title="Upload from link">
                    <svg viewBox="0 96 960 960"><path d="M450 776H280q-83 0-141.5-58.5T80 576q0-83 58.5-141.5T280 376h170v60H280q-58.333 0-99.167 40.765-40.833 40.764-40.833 99Q140 634 180.833 675q40.834 41 99.167 41h170v60ZM324 606v-60h310v60H324Zm556-30h-60q0-58-40.833-99-40.834-41-99.167-41H510v-60h170q83 0 141.5 58.5T880 576ZM699 896V776H579v-60h120V596h60v120h120v60H759v120h-60Z"/></svg>
                  </button>
                </div>             
              </div>
            </div>
          </div>
          <div class="card-body">
            <div class="mb-3">
              <input type="text" name="search" class="form-control" placeholder="Search"/> 
            </div>
            <div>
              <ul name="process_category_list"></ul>
            </div>
          </div>
        </div>
      `
      
      this.#rootdoc.querySelector("button[name=refresh]").addEventListener("click", ev=>{
        this.fetchProcesses()  
      })
      
      this.#searchfield = this.#rootdoc.querySelector("input[name=search]")
      this.#searchfield.addEventListener("input", ev=>{
        this.updateList()
      })
      
      this.#fileupload = this.#rootdoc.querySelector("label[name=fileupload] > input[type=file]")
      this.#fileupload.addEventListener("change", ev=>{
        const filelist = ev.target.files;
        if (filelist.length > 0) {
          const files = Array.prototype.slice.call(filelist)
          this.importMethods(files)
        }
      })
      
      this.#archiveupload = this.#rootdoc.querySelector("button[name=archive]")
      this.#archiveupload.addEventListener("click", ev=>{
        const link = ev.target.parentElement.querySelector("input").value
        if(link){
          if(confirm("Please confirm importing of method from link?")){
            this.fromArchive(link)
          }
        }
      })
    }
  
    connectedCallback(){
      this.#allowedit = this.getAttribute("allow-edit") === "true"
      this.#allowexecute = this.getAttribute("allow-execute") === "true"
      this.#serviceurl = this.getAttribute("serviceurl")
      this.render()
      this.fetchProcesses()
    }
  
    fetchProcesses(){
      console.log("Calling fetch processes")
      this.#boot.secureFetch(this.#serviceurl + "/methods").
      then(resp=>{
        return resp.json()
      }).then(data=>{
        this.#data = data
        this.updateList()
        return this.fetchInfrastructures()
      }).then(d => {
        this.updateList()
      }).catch(err=>{
        alert("Error while downloading methods")
        console.error("Error while downloading methods: " + err)
      })
    }
    
    fetchInfrastructures(){
      const url = this.#serviceurl + "/infrastructures"
      this.#boot.secureFetch(url).
      then(resp=>{
        if(resp.status !== 200) throw "Unable to fetch infrastructures " + resp.status;
        else return resp.json()
      }).then(infras=>{
        for(let m=0; m < this.#data.length; m++){
          const method = this.#data[m]
          method["executable"] = false
          for(let i=0; i < infras.length; i++){
            const infra = infras[i]
            const matches = method.links.filter(l => l.rel === "compatibleWith" && l.href === "infrastructures/" + infra.id)
            if(matches.length > 0){
              method["executable"] = true
              break
            } 
          }
        }
        this.updateList()
      }).catch(err=>{
        alert("Error while checking runtimes for method")
        console.error("Error while checking runtimes for method: " + err)
      })
    }
    
    updateList(){
      const filter = this.#searchfield.value
      if(filter === "" || filter == null || filter == undefined){
        this.#filtered = this.#data
      }else{
        const f = filter.toLowerCase()
        this.#filtered = this.#data.filter(d=>{
          return false || 
            (d.title.toLowerCase().indexOf(f) !== -1)|| 
            (d.description.indexOf(f) !== -1) ||
            (d.keywords.map(k=>k.toLowerCase()).filter(i=>i.indexOf(f) !== -1)).length
        })
      }
      this.groupBy()
      BSS.apply(this.#process_list_bss, this.#rootdoc)
    }
    
    groupBy(){
      this.#filtered = this.#filtered.reduce((catalog, meth)=>{
        const categories = meth.metadata.filter(md=>md.role === "category").map(c=>c.title)
        if(categories.length === 0){
          catalog["Uncategorised"].push(meth)
        }else{
          for(let c in categories){
            const category = categories[c]
            catalog[category] = catalog[category] ?? []
            catalog[category].push(meth)    
          }
        }
        return catalog
      }, { "Uncategorised" : []})
    }
    
    getCategoryHints(){
      const s = new Set()
      if(this.#data === null) return [];
      for(let i=0; i < this.#data.length; i++){
        const cat = this.#data[i].metadata.filter(md=>md.role === "category").map(md=>md.title)
        cat.forEach(c=>s.add(c))
      }
      return [...s]
    }
    
    exportCategory(category){
      Promise.all(
        this.#filtered[category].map(m=>this.exportMethod(m.id))
      )
    }
    
    exportMethod(method){
      this.#boot.secureFetch(this.#serviceurl + "/methods/" + method + "/shareable").then(
        (resp)=>{
          if(resp.status === 200){
            return resp.json()
          }else throw "Error exporting sharable process: " + resp.status
        }
      ).then(data=>{
        const filename = data.title + "-" + data.version + ".json"
        const datastr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(data));
        var tmplnk = document.createElement("a")
        tmplnk.download = filename
        tmplnk.href = datastr
        document.body.appendChild(tmplnk)
        tmplnk.click()
        document.body.removeChild(tmplnk)
      }).catch(err=>{
        console.log(err)
      })
    }
    
    importMethods(files){
      if(files && files.length) {
        let formdata = new FormData();
        files.reduce((formdata, f)=>{
          formdata.append("files[]", f)
          return formdata
        }, formdata)
        this.#boot.secureFetch(`${this.#serviceurl}/methods`, { body: formdata, method : "POST"})
        .then(reply=>{
          if (reply.status !== 200) {
            throw "Unable to import" 
          }else return reply.text()
        }).then(data=>{
          this.fetchProcesses()
        }).catch(err=>{ alert(err) })
      }
    }
    
    toArchive(id){
      this.#boot.secureFetch(`${this.#serviceurl}/methods/${id}/archive`, { method: "POST" })
      .then(reply =>{
        if (!reply.ok) {
          throw "Unable to archive"
        }
      }).catch(err=>{ alert(err)})
    }
  
    fromArchive(url){
      if(url){
        this.#boot.secureFetch(`${this.#serviceurl}/methods/archive?url=${url}`)
        .then(reply =>{
          if (!reply.ok) {
            throw "Unable to fetch from archive"
          }
          return reply.text()
        }).then(data=>{
          this.fetchProcesses()  
        }).catch(err=>{ alert(err)}) 
      }
    }
    
    publish(id){
      this.#boot.secureFetch(`${this.#serviceurl}/methods/${id}/publish`, { method: "POST" })
      .then(reply =>{
        if (!reply.ok) {
          throw `Unable to archive (${reply.status})`
        }
      }).catch(err=>{ alert(err)})
    }
    
    isShared(process){
      return 0 < process.metadata.filter(md=>{ return md.role === "context" && md.title === this.#boot.context}).length
    }
    
    isAuthor(m){
      const href = `${this.#boot.url}/admin/realms/${this.#boot.realm}/users/${this.#boot.subject}`
      const found = m.metadata.filter(md=>{return md.role === "author" && md.href === href})
      return found.length > 0
    }
    
    #process_list_bss = {
      template : "#PROCESS_LIST_TEMPLATE",
      target : "ul[name=process_category_list]",
      "in" : this,
      on_dragover : (ev)=> ev.preventDefault(),
      on_dragenter : (ev)=> ev.target.classList.toggle("border-info"),
      on_dragleave : (ev)=> ev.target.classList.toggle("border-info"),
      on_drop : (ev)=>{
        if(ev.dataTransfer && ev.dataTransfer.files && ev.dataTransfer.files.length){
          const files = Array.prototype.slice.call(ev.dataTransfer.files)
          const jsons = files.filter(f=>f.type === "application/json")
          if(confirm("Please confirm import of method files?")){
            this.importMethods(files)
          }
          ev.preventDefault()
          ev.stopPropagation()
        }
        ev.target.classList.toggle("border-info")
      },
      recurse : [
        {
          target : "li.ccp-process-category",
          "in" : (e,d)=>Object.keys(this.#filtered),
          on_click : ev=>{
            if(ev.target.getAttribute("name") === "export_category"){
              this.exportCategory(ev.currentTarget.bss_input.data)
            }
          },
          recurse : [
            {
              target : "summary",
              apply : (e,d)=>{ 
                const executables = this.#filtered[d].filter(m=>m.executable).length
                e.querySelector("h5").textContent = d
                e.querySelector("span[name=count_notexecutables]").textContent = this.#filtered[d].length - executables 
                e.querySelector("span[name=count_executables]").textContent = executables 
              }
            },
            {
              target : "details",
              apply : (e,d)=>{
                e.alt = e.title = d
                if(sessionStorage.getItem(d) === "open") e.open = "open";
                else e.removeAttribute("open");
              },
              on_toggle : ev=>{
                if(ev.target.open){
                  sessionStorage.setItem(ev.currentTarget.alt, 'open')
                }else{
                  sessionStorage.removeItem(ev.currentTarget.alt)
                }
              }
            },
            {
              target : "details ul[name=process_list]",
              in : (e,d)=>d,
              recurse: [
                {
                  target : "li.ccp-process",
                  "in" : (e,d)=>this.#filtered[d],
                  apply : (e,d)=>{ e.alt = e.title = `${d.title} (${d.id})` },
                  on_click : ev=>{
                    const id = ev.currentTarget.bss_input.data.id
                    if(ev.target.getAttribute("name") === "export_method"){
                      this.exportMethod(id)
                    }else  if(ev.target.getAttribute("name") === "executable"){
                      const event = new CustomEvent('newexecutionrequest', { detail: id });
                      document.dispatchEvent(event)
                    }else  if(ev.target.getAttribute("name") === "edit"){
                      const event = new CustomEvent('neweditrequest', { detail: ev.currentTarget.bss_input.data });
                      document.dispatchEvent(event)
                    }else if(ev.target.getAttribute("name") === "archive"){
                      if(confirm("Please confirm archiving of method to workspace?")){
                        this.toArchive(id)
                      }
                    }else if(ev.target.getAttribute("name") === "publish"){
                      if(confirm("Please confirm publication of method to Vlab?")){
                        this.publish(id)
                      }
                    }
                  },
                  on_dragstart : ev=>{
                    ev.dataTransfer.effectAllowed = 'move'
                    ev.dataTransfer.setData('text/html', ev.currentTarget.innerHTML)
                    ev.dataTransfer.setData('text/plain+ccpmethod', ev.currentTarget.bss_input.data.id)
                    ev.dataTransfer.setData('application/json+ccpmethod', JSON.stringify(ev.currentTarget.bss_input.data))
                  },
                  on_dragend : ev=>{
                    ev.preventDefault()
                  },
                  recurse : [
                    {
                      target: "span[name=title]",
                      apply : (e,d)=>{ e.textContent = d.title }
                    },
                    {
                      target: "span[name=version]",
                      apply : (e,d)=>{ e.textContent = d.version }
                    },
                    {
                      target: "button[name=executable]",
                      apply : (e,d)=>{ e.style.display = d.executable && this.#allowexecute ? "revert" : "none" }
                    },
                    {
                      target: "button[name=edit]",
                      apply : (e,d)=>{ e.style.display = this.#allowedit ? "revert" : "none" }
                    },
                    {
                      target: "button[name=publish]",
                      apply : (e,d)=>{ 
                        if(this.isShared(d)){
                          const span = document.createElement("span")
                          span.classList.add("badge")
                          span.classList.add("badge-warning")
                          span.textContent = "shared"
                          e.replaceWith(span)
                        }else if(!this.isAuthor(d)){
                          e.parentElement.removeChild(e)
                        }
                      }
                    },
                    {
                      target : "span[name=author]",
                      "in" : (e,d)=>d.metadata.filter(md=>md.role === "author"),
                      apply : (e,d)=>{ e.textContent = d.title; e.alt = e.title = "author" }
                    },
                    {
                      target : "span[name=keyword]",
                      "in" : (e,d)=>d.keywords,
                      apply : (e,d)=>{ e.alt = e.title = e.textContent = d }
                    },
                    {
                      target : "span[name=infrastructures]",
                      "in" : (e,d)=>d.links.filter(md=>md.rel === "compatibleWith"),
                      apply : (e,d)=>{ e.alt = e.title = e.textContent = d.title }
                    },
                    {
                      target : "p[name=description]",
                      apply : (e,d)=>{ e.textContent = d.description }
                    },
                  ]
                }
              ] 
            }
          ]
        }  
      ]
    }  
  }
  
window.customElements.define('d4s-ccp-methodlist', CCPMethodList);