class CCPExecutionHistory extends HTMLElement {
    
  #boot = null;
  #rootdoc = null;
  #serviceurl = null;
  #broadcasturl = null;
  #archive = null;
  #data = [];
  #pending = [];
  #filtered = [];
  #socket = null;
  #interval = null;
  #searchfield = null;
  #fileupload = null;
  #archiveupload = null;
  
  constructor(){
    super()
    this.#boot = document.querySelector("d4s-boot-2")
    this.#rootdoc = this.attachShadow({ "mode" : "open"})
    this.#serviceurl = this.getAttribute("serviceurl")
    this.#broadcasturl = this.getAttribute("broadcasturl")
    if(!this.#broadcasturl){
      this.#broadcasturl = this.#serviceurl.replace(/^http/, "ws")
    }
    this.#broadcasturl = this.#broadcasturl + "/ws/notification"
    this.#archive = this.getAttribute("archive")
    this.connectNewExecution()
  }
  
  connectedCallback(){
    this.connectBroadcastWithSubject()
    this.render()
    this.refreshExecutions()
  }
  
  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>
        .lxd{
          background-color: #dd4814;
          color: white;
        }
        .docker{
          color: white;
          background-color: #2496ed;
        }
        .ccp-execution-list {
          min-height: 3rem;
        }
        .ccp-execution-list .lxd{
          background-color: #dd4814;
        }
        .ccp-execution-list .docker{
          background-color: #2496ed;
        }
      </style>
      <template id="EXECUTIOM_LIST_TEMPLATE">
        <ul name="ccp_execution_list" class="ccp-execution-list list-group border border-2">
          <li class="ccp-method-item list-group-item list-group-item-dark">
            <details name="level1">
              <summary class="ccp-method-item-header noselect d-flex flex-wrap justify-content-between">
                <h5 class="text-primary d-inline"></h5>
                <div>
                  <span name="failed" title="Failed executions" class="badge badge-danger float-right">Z</span>
                  <span name="successful" title="Successful executions"class="badge badge-success float-right mr-1">Y</span>
                  <span name="running" title="Running executions"class="badge badge-primary float-right mr-1">Y</span>
                  <span name="accepted" title="Accepted executions" class="badge badge-secondary float-right mr-1">X</span>
                </div>
              </summary>
              <ul class="ccp-execution-list list-group" style="list-style:none">
                <li class="ccp-execution-item list-group-item-secondary my-2 p-2" draggable="true">
                  <details>
                    <summary>
                      <span name="version" class="badge badge-primary"></span>
                      <span name="status" class="ml-1 badge"></span>
                      <div class="d-flex float-right" style="gap: 3px 5px; max-width: 40%; min-width:60px; flex-wrap:wrap;">
                        ${ 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="provo" title="Export to Prov-o document" class="btn btn-primary ccp-toolbar-button ccp-toolbar-button-small">
                          <svg viewBox="0 0 24 24"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M13 3H6v18h4v-6h3c3.31 0 6-2.69 6-6s-2.69-6-6-6zm.2 8H10V7h3.2c1.1 0 2 .9 2 2s-.9 2-2 2z"/></svg>
                        </button>
                        <!-- button data-index="0" name="zip" title="Download as zip archive" class="btn btn-primary ccp-toolbar-button ccp-toolbar-button-small">
                          <svg viewBox="0 0 48 48"><path d="M7 40q-1.15 0-2.075-.925Q4 38.15 4 37V11q0-1.15.925-2.075Q5.85 8 7 8h14l3 3h17q1.15 0 2.075.925Q44 12.85 44 14v23q0 1.15-.925 2.075Q42.15 40 41 40Zm25-3h9V14h-9v4.6h4.6v4.6H32v4.6h4.6v4.6H32ZM7 37h20.4v-4.6H32v-4.6h-4.6v-4.6H32v-4.6h-4.6V14h-4.65l-3-3H7v26Zm0-23v-3 26-23Z"/></svg>
                        </button-->
                        <button data-index="0" name="archivefolder" title="Archive outputs to workspace" class="btn btn-primary ccp-toolbar-button ccp-toolbar-button-small">
                          <svg viewBox="0 0 48 48"><path d="M7 40q-1.15 0-2.075-.925Q4 38.15 4 37V11q0-1.15.925-2.075Q5.85 8 7 8h14l3 3h17q1.15 0 2.075.925Q44 12.85 44 14v23q0 1.15-.925 2.075Q42.15 40 41 40Zm25-3h9V14h-9v4.6h4.6v4.6H32v4.6h4.6v4.6H32ZM7 37h20.4v-4.6H32v-4.6h-4.6v-4.6H32v-4.6h-4.6V14h-4.65l-3-3H7v26Zm0-23v-3 26-23Z"/></svg>
                        </button>
                        <button data-index="0" name="reexecute1" title="Re-submit this execution" class="btn btn-info ccp-toolbar-button ccp-toolbar-button-small">
                          Re-submit
                        </button>
                        <button data-index="0" name="delete" title="Delete" class="btn btn-danger ccp-toolbar-button ccp-toolbar-button-small">
                          <svg viewBox="0 0 24 24">
                            <path d="M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z"></path>
                          </svg>
                        </button>
                      </div>
                      <p name="createstart" class="font-weight-light font-italic" style="margin-top:revert">
                        <span name="created"></span>.
                        <span name="started" class="ml-1"></span>
                      </p>
                      <p name="message" class="font-weight-light font-italic" style="margin-top:revert">
                        <span name="updated"></span>:
                        <span name="message" class="ml-1"></span>
                      </p>
                    </summary>
                    <div class="d-flex flex-wrap" style="gap:3px; overflow:hidden">
                      <span style="text-overflow:ellipsis" class="badge badge-light text-info border border-info" name="infrastructure" alt="Infrastructure" title="Infrastructure"></span>
                      <span class="badge" name="runtime" alt="Runtime" title="Runtime"></span>
                    </div>
                    <div name="logterminalcontainer" style="margin:5px 0 5px 0">
                    </div>
                    <ul>
                      <li></li>
                    </ul>
                    <div class="d-flex flex-column align-items-end" style="gap: 3px;">
                      <div class="d-flex align-items-center" style="gap:5px">
                        <label style="text-wrap:nowrap">Generate code for:</label>
                        <select name="language-selector" class="form-control">
                          <option value="text/python" data-ext="py" title="Generate plain Python3">Python 3</option>
                          <option value="text/plain+r" data-ext="r" title="Generate plain R">R</option>
                          <option value="application/vnd.jupyter+python" data-ext="ipynb" title="Generate Jupyter notebook with Python 3 cells">Jupyter Python3</option>
                          <option value="text/julia" data-ext="jl" title="Generate Julia 1.9.0 code (HTTP 1.10.0, JSON3 1.13.2)">Julia 1.9.0</option>
                          <option value="application/x-sh+curl" data-ext="sh" title="Generate Bash script (curl)">Bash (curl)</option>
                          <option value="application/x-sh+wget" data-ext="sh" title="Generate Bash script (wget)">Bash (wget)</option>
                        </select>
                        <button data-index="0" name="codegen" title="Generate code" class="btn btn-primary ccp-toolbar-button ccp-toolbar-button-small">
                          <svg viewBox="0 96 960 960">
                            <path d="m384 721 43-43-101-102 101-101-43-43-144 144.5L384 721Zm192 0 145-145-144-144-43 43 101 101-102 102 43 43ZM180 936q-24.75 0-42.375-17.625T120 876V276q0-24.75 17.625-42.375T180 216h205q5-35 32-57.5t63-22.5q36 0 63 22.5t32 57.5h205q24.75 0 42.375 17.625T840 276v600q0 24.75-17.625 42.375T780 936H180Zm0-60h600V276H180v600Zm300-617q14 0 24.5-10.5T515 224q0-14-10.5-24.5T480 189q-14 0-24.5 10.5T445 224q0 14 10.5 24.5T480 259ZM180 876V276v600Z"/>
                          </svg>
                        </button>
                      </div>
                      <div class="d-flex align-items-middle" style="gap:5px">
                        <div class="d-flex">
                          <a class="text-truncate" name="direct_link_execution" href="${window.location.href}">Direct link</a>
                        </div>
                      </div>
                    </div>
                  </details>
                </li>
              </ul>
            </details>
          </li>
        </ul>
      </template>
      <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">
      <div class="card">
        <div class="card-header">
          <div class="ccp-toolbar-header d-flex flex-wrap justify-content-between">
            <div>
              <span name="header">Execution Monitor</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 accept="application/zip" type="text" name="search" class="form-control" placeholder="Search"/> 
          </div>
          <div>
            <ul name="ccp_execution_list"></ul>
          </div>
        </div>
      </div>
    `
    this.#rootdoc.querySelector("button[name=refresh]").addEventListener("click", ev=>{
      this.refreshExecutions()
    })
    
    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.importExecutions(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 execution from link?")){
          this.fromArchive(link)
        }
      }
    })
  }
  
  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.status.toLowerCase().indexOf(f) !== -1)|| 
          (d.method.indexOf(f) !== -1)
      })
    }
    this.#filtered.sort((a,b)=>(new Date(b.updated)) - (new Date(a.updated)))
    this.groupBy()
    BSS.apply(this.#execution_list_bss, this.#rootdoc)
  }
    
  groupBy(){
    this.#filtered = this.#filtered.reduce((catalog, exec)=>{
      const category = exec.method
      catalog[category] = catalog[category] ?? []
      catalog[category].push(exec)
      return catalog
    }, {})
  }
  
  refreshExecution(id){
    this.#boot.secureFetch(`${this.#serviceurl}/executions?id=${id}`).then(reply =>{
      if(reply.ok) return reply.json();
      else throw `Unable to load execution ${id}. Check console.`
    }).then(data=>{
      //this may occur for timing issues since workflow start is async
      const exec = data.length === 0 ? { id : id} : data[0]
      for(var i=0; i < this.#data.length; i++){
        if(this.#data[i].id == exec.id){
          this.#data[i] = exec
          break
        }
      }
      if(i === this.#data.length){
        this.#data = [exec].concat(this.#data)
      }
      this.updateList()
    }).catch(err=>{ console.error(err)})
  }
  
  deleteExecution(id){
    this.#boot.secureFetch(`${this.#serviceurl}/executions/${id}`, { method: "DELETE"}).then(reply =>{
      if(reply.ok) this.refreshExecutions();
      else throw `Unable to delete execution ${id}. Check console.`
    }).catch(err=>{ console.error(err)})
  }
  
  refreshExecutions(){
    this.#boot.secureFetch(`${this.#serviceurl}/executions`).then(reply =>{
      if(reply.ok) return reply.json();
      else throw "Unable to load executions. Check console."
    }).then(data=>{
      this.#data = data
      this.updateList()
    }).catch(err=>{ console.error(err)})
  }
  
  connectNewExecution(){
    document.addEventListener("newexecution", ev=>{
      this.#pending.push(ev.detail)
    })
  }
  
  connectBroadcastWithSubject(){
    var interval = window.setInterval( ()=>{
      if(this.#boot.subject){
        window.clearInterval(interval)
        this.connectBroadcast() 
      }
    }, 1000) 
  }
  
  connectBroadcast(){
    this.#socket = new WebSocket(`${this.#broadcasturl}/unified?subject=${this.#boot.subject}`);
    this.#socket.onmessage = event=>{
      const data = JSON.parse(event.data)
      
      if(data[0] && data[0].source){
        //has to be logs
        this.appendLogs(data)
        return
      }
      
      let exec = this.#data.filter(e=>e.id === data.jobID)[0]
      if(exec){
        this.refreshExecution(exec.id)
      }else{
        this.#pending = this.#pending.filter(pe=>{ 
          if(pe === data.jobID){
            this.refreshExecution(pe)
            return false
          }else{
            return true
          }
        })
      }
    } 
    this.#interval = window.setInterval( ()=>{
      if(this.#socket.readyState === 3){
        this.#socket.close()
        window.clearInterval(this.#interval)
        this.connectBroadcast() 
      }else{
        this.#socket.send("ping")  
      }
    }, 30000) 
  }
  
  appendLogs(data){
    if(!data.length) return;
    const exid = data[0]["attrs"]["execution"]
    const lt = this.#rootdoc.querySelector(`d4s-ccp-logterminal[index='${exid}']`)
    if(!lt){
      console.error("No terminal found for adding logs of " + exid)
    }else{
      lt.addLines(data)
    }
  }
  
  download(url, name) {
    this.#boot.secureFetch(url).then(reply => {
      if (!reply.ok) {
        throw "Unable to download. Check console."
      }
      return reply.blob()

    }).then(blob => {
      const objectURL = URL.createObjectURL(blob)
      var tmplnk = document.createElement("a")
      tmplnk.download = name
      tmplnk.href = objectURL
      document.body.appendChild(tmplnk)
      tmplnk.click()
      document.body.removeChild(tmplnk)

    }).catch(err => console.error(err))
  }
  
  export(id, mime, filename){
    this.#boot.secureFetch(`${this.#serviceurl}/executions/${id}`, 
      { method: "GET", headers : { "Accept" : mime} }).then(reply =>{
       if (!reply.ok) {
        throw "Unable to export " + mime
      }
      return reply.blob()
    }).then(blob => {
      const objectURL = URL.createObjectURL(blob)
      var tmplnk = document.createElement("a")
      tmplnk.download = filename
      tmplnk.href = objectURL
      document.body.appendChild(tmplnk)
      tmplnk.click()
      document.body.removeChild(tmplnk)
    }).catch(err=>{ alert(err)})
  }
  
  reexecute(id,level){
    this.#boot.secureFetch(`${this.#serviceurl}/executions/${id}/level/${level}`, { method: "POST" })
    .then(reply =>{
      if (!reply.ok) {
        throw "Unable to re-execute. Check console."
      }
      return reply.json()
    }).then(data=>{
      console.log(data)
      this.refreshExecution(data.jobID)
    }).catch(err=>{ alert(err)})
  }
  
  importExecutions(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}/executions`, { body: formdata, method : "POST"})
      .then(reply=>{
        if (!reply.ok) {
          throw "Unable to import" 
        }else return reply.text()
      }).then(data=>{
        this.refreshExecutions()
      }).catch(err=>{ alert(err) })
    }
  }
  
  generateCode(id, mime, filename){
    this.#boot.secureFetch(`${this.#serviceurl}/executions/${id}/code`, 
      { method: "GET", headers : { "Accept" : mime} }).then(reply =>{
       if (!reply.ok) {
        throw "Unable to generate code for " + mime
      }
      return reply.blob()
    }).then(blob => {
      const objectURL = URL.createObjectURL(blob)
      var tmplnk = document.createElement("a")
      tmplnk.download = filename
      tmplnk.href = objectURL
      document.body.appendChild(tmplnk)
      tmplnk.click()
      document.body.removeChild(tmplnk)
    }).catch(err=>{ alert(err)})
  }
  
  toArchive(id){
    this.#boot.secureFetch(`${this.#serviceurl}/executions/${id}/archive`, { method: "POST" })
    .then(reply =>{
      if (!reply.ok) {
        throw "Unable to archive"
      }
    }).catch(err=>{ alert(err)})
  }
  
  toArchiveFolder(id){
    this.#boot.secureFetch(`${this.#serviceurl}/executions/${id}/archive-to-folder`, { method: "POST" })
    .then(reply =>{
      if (!reply.ok) {
        throw "Unable to archive"
      }
    }).catch(err=>{ alert(err)})
  }

  fromArchive(url){
    if(url){
      this.#boot.secureFetch(`${this.#serviceurl}/executions/archive?url=${url}`)
      .then(reply =>{
        if (!reply.ok) {
          throw "Unable to fetch from archive"
        }
        return reply.text()
      }).then(data=>{
        this.refreshExecutions()
      }).catch(err=>{ alert(err)}) 
    }
  }
  
  #execution_list_bss = {
    template : "#EXECUTIOM_LIST_TEMPLATE",
    target : "ul[name=ccp_execution_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 zips = files.filter(f=>f.type === "application/zip")
        if(confirm("Please confirm import of execution files?")){
          this.importExecutions(files)
        }
      }
      ev.target.classList.toggle("border-info")
    },
    recurse:[
      {
        target : "li.ccp-method-item",
        "in" : (e,d)=>Object.keys(this.#filtered),
        recurse: [
          {
            target : "details[name=level1]",
            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 : "summary.ccp-method-item-header h5",
            apply : (e,d) => { e.textContent = d }
          },
          {
            target : "summary.ccp-method-item-header span[name=accepted]",
            apply : (e,d) => { e.textContent = this.#filtered[d].filter(x=>x.status === 'accepted').length }
          },
          {
            target : "summary.ccp-method-item-header span[name=failed]",
            apply : (e,d) => { e.textContent = this.#filtered[d].filter(x=>x.status === 'failed').length }
          },
          {
            target : "summary.ccp-method-item-header span[name=successful]",
            apply : (e,d) => { e.textContent = this.#filtered[d].filter(x=>x.status === 'successful').length }
          },
          {
            target : "summary.ccp-method-item-header span[name=running]",
            apply : (e,d) => { e.textContent = this.#filtered[d].filter(x=>x.status === 'running').length }
          },
          {
            target : "li.ccp-execution-item",
            "in" : (e,d)=>this.#filtered[d],
            apply : (e,d)=>e.setAttribute("data-index", d.id),
            on_dragstart : ev=>{
              ev.dataTransfer.effectAllowed = 'move'
              ev.dataTransfer.setData('text/html', ev.currentTarget.innerHTML)
              ev.dataTransfer.setData('text/plain+ccpexecution', ev.currentTarget.getAttribute("data-index"))
              ev.dataTransfer.setData('application/json+ccpexecution', JSON.stringify(ev.currentTarget.bss_input.data))
            },
            on_dragend : ev=>{
              ev.preventDefault()
            },
            on_click: ev=>{
              if(ev.target.getAttribute("name") === "delete"){
                if(window.confirm("Please confirm deletion of this execution?")){
                  const id = ev.currentTarget.getAttribute("data-index")
                  this.deleteExecution(id) 
                }
              }
              if(ev.target.getAttribute("name") === "zip"){
                const id = ev.currentTarget.getAttribute("data-index")
                this.export(id, "application/zip", id + ".zip")
              }
              if(ev.target.getAttribute("name") === "provo"){
                const id = ev.currentTarget.getAttribute("data-index")
                this.export(id, "application/prov-o+xml", id + ".xml")
              }
              if(ev.target.getAttribute("name") === "reexecute1"){
                if(window.confirm("Please confirm re-execution?")){
                  const id = ev.currentTarget.getAttribute("data-index")
                  this.reexecute(id, 1) 
                }
              }
              if(ev.target.getAttribute("name") === "archive"){
                if(confirm(" Please confirm archiving of execution to workspace?")){
                  const id = ev.currentTarget.getAttribute("data-index")
                  this.toArchive(id)
                }
              }
              if(ev.target.getAttribute("name") === "archivefolder"){
                if(confirm(" Please confirm archiving of execution outputs to workspace?")){
                  const id = ev.currentTarget.getAttribute("data-index")
                  this.toArchiveFolder(id)
                }
              }
            },
            recurse : [
              {
                target : "details",
                apply : (e,d)=>{
                  e.alt = e.title = d.id
                  if(sessionStorage.getItem(d.id) === "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 : "span[name=version]",
                apply : (e,d)=>{
                  if(d.ccpnote){
                    e.textContent = `${d.ccpnote} (${d.methodversion})` 
                  }else{
                    e.textContent = `${d.methodversion}`  
                  }
                }
              },
              {
                target : "span[name=status]",
                apply : (e,d)=>{
                  if(d.status){
                    const status = d.status
                    e.textContent = status
                    if (status === "running") e.classList.add("badge-primary");
                    else if (status === "successful") e.classList.add("badge-success");
                    else if (status === "failed") e.classList.add("badge-danger");
                    else e.classList.add("badge-secondary");
                  }
                }
              },
              {
                target : "span[name=created]",
                apply : (e,d)=>{
                  if(d.created){
                    const dt = new Date(d.created)
                    e.textContent = `Accepted ${dt.toLocaleDateString()} @ ${dt.toLocaleTimeString()}` 
                  }
                }
              },
              {
                target : "span[name=started]",
                apply : (e,d)=>{
                  if(d.started){
                    const dt = new Date(d.started)
                    e.textContent = `Started ${dt.toLocaleDateString()} @ ${dt.toLocaleTimeString()}` 
                  }
                }
              },
              {
                target : "span[name=updated]",
                apply : (e,d)=>{
                  const dt = new Date(d.updated)
                  e.textContent = `Last update ${dt.toLocaleDateString()} @ ${dt.toLocaleTimeString()}`
                }
              },
              {
                target : "span[name=message]",
                apply : (e,d)=>{
                  if(d.message){
                    e.textContent = d.message 
                  }
                }
              },
              {
                target : "span[name=infrastructure]",              
                apply : (e,d)=>{
                  e.textContent = d.infrastructure
                }
              },
              {
                target : "span[name=runtime]",              
                apply : (e,d)=>{
                  const rt = d.runtime ? d.runtime : "" 
                  const infratype = d.infrastructuretype ? d.infrastructuretype : "" 
                  e.textContent = rt + (d.replicas && d.replicas !== "1" ? ' x ' + d.replicas : '')
                  const t = infratype.match(/docker/i) ? "docker" : null
                  const t2 = !t && infratype.match(/lxd/i) ? "lxd" : t
                  e.classList.add(t2)
                }
              },
              {
                target : "button[name=codegen]",              
                apply : (e,d)=>e.setAttribute("data-index", d.id),
                on_click: (ev)=>{
                  const id = ev.target.getAttribute("data-index")
                  const langsel = ev.target.parentElement.querySelector("select[name=language-selector]")
                  const lang = langsel.value
                  const ext = langsel.selectedOptions[0].getAttribute("data-ext")
                  this.generateCode(id, lang, `${id}.${ext}`)
                }
              },
              {
                target : "a[name=direct_link_execution]",              
                apply : (e,d)=>e.href = window.location.origin + window.location.pathname + "?execution=" + d.id
              },
              {
                target : "div[name=logterminalcontainer]",
                apply : (e,d)=>{
                  e.innerHTML = `<d4s-ccp-logterminal index="${d.id}" maxstoredlines="100" maxlines="10"></d4s-ccp-logterminal>`
                }
              },
              {
                target : "ul",
                recurse : [
                  {
                    target : "li",
                    "in" : (e,d)=>{
                      return d.resources.map(l=>{ 
                        return { href : this.#serviceurl + "/executions/" + d.id + "/" + l.path, path : l.path} 
                      })
                    },
                    on_click : ev=>{
                      const href = ev.currentTarget.bss_input.data.href
                      const name = ev.currentTarget.bss_input.data.path
                      this.download(href, name)
                    },
                    apply : (e,d)=>{
                      e.innerHTML = `<a href="${d.href}" onclick="event.preventDefault()">${d.path}</a>`
                    }
                  }
                ]
              }
            ]
          }
        ]
      }
    ]
  }
}
window.customElements.define('d4s-ccp-executionhistory', CCPExecutionHistory);
