<template>
  <v-container fluid>

    <v-data-table
    :headers="headers"
    :items="displayItems"
    :options.sync="options"
    :loading="loading"
    @contextmenu:row="contextMenu"
    >

      <template v-slot:item.pid="{item, value}">
        <template v-if="item.archived">
          <a class="secondary--text" title="This project has been archived" :href="`/projects/${item.pid}`">{{value}}</a>
          <v-icon
            class="ml-1 secondary--text"
            small
             title="This project has been archived"
          >mdi-archive-outline</v-icon>
        </template>
        <template v-else>
          <a :href="`/projects/${item.pid}`" :title="title(item)">{{value}}</a>
        </template>
      </template>

      <template v-slot:item.groups="{item, value}">
        <v-chip v-for="group in value"
          label
          small
        >{{ group }}</v-chip>
      </template>

      <template v-slot:item.shots="{item}">
        {{ item.total ? (item.prime + item.other) : "" }}
      </template>

      <template v-slot:item.progress="{item}">
        {{
          item.total
            ? ((1 - (item.remaining / item.total))*100).toFixed(1)+"%"
            : ""
        }}
        <v-progress-linear v-if="item.total"
          height="2"
          :value="((1 - (item.remaining / item.total))*100)"
        />
      </template>

      <template v-slot:footer.prepend>
        <v-checkbox
          class="mr-3"
          v-model="showArchived"
          dense
          hide-details
          title="Projects that have been marked as archived by an administrator no longer receive updates from external sources, such as the project's file repository or the navigation system, but they may still be interacted with via Dougal, including adding or editing log comments."
        >
          <template v-slot:label>
            <span class="subtitle-2">Show archived projects</span>
          </template>
        </v-checkbox>
      </template>

    </v-data-table>

    <v-menu v-if="adminaccess(contextMenuItem)"
      v-model="contextMenuShow"
      :position-x="contextMenuX"
      :position-y="contextMenuY"
      absolute
      offset-y
    >
      <v-list dense v-if="contextMenuItem">
        <v-list-item :href="`${contextMenuItem.pid}/configuration`">
          <v-list-item-icon><v-icon>mdi-file-document-edit-outline</v-icon></v-list-item-icon>
          <v-list-item-title class="warning--text">Edit project settings</v-list-item-title>
        </v-list-item>
        <v-divider></v-divider>
        <v-list-item @click="cloneDialogOpen = true">
          <v-list-item-icon><v-icon>mdi-sheep</v-icon></v-list-item-icon>
          <v-list-item-title class="warning--text">Clone project</v-list-item-title>
        </v-list-item>
      </v-list>
    </v-menu>

    <v-dialog
      v-model="cloneDialogOpen"
      max-width="600"
    >
      <dougal-project-settings-name-id-rootpath
        v-model="cloneProjectDetails"
        @input="cloneProject"
        @close="cloneDialogOpen = false"
      >
      </dougal-project-settings-name-id-rootpath>
    </v-dialog>

  </v-container>
</template>

<style>
td p:last-of-type {
  margin-bottom: 0;
}
</style>

<script>
import { mapActions, mapGetters } from 'vuex';
import DougalProjectSettingsNameIdRootpath from '@/components/project-settings/name-id-rootpath'
import AccessMixin from '@/mixins/access';

export default {
  name: "ProjectList",

  components: {
    DougalProjectSettingsNameIdRootpath
  },

  mixins: [
    AccessMixin
  ],

  data () {
    return {
      headers: [
        {
          value: "pid",
          text: "Project ID"
        },
        {
          value: "name",
          text: "Name"
        },
        {
          value: "groups",
          text: "Groups"
        },
        {
          value: "lines",
          text: "Lines"
        },
        {
          value: "seq_final",
          text: "Sequences"
        },
        {
          value: "total",
          text: "Preplot points"
        },
        {
          value: "shots",
          text: "Shots acquired"
        },
        {
          value: "progress",
          text: "% Complete"
        },
      ],
      items: [],
      options: { sortBy: ["pid"], sortDesc: [true] },

      // Whether or not to show archived projects
      showArchived: true,

      // Cloned project stuff (admin only)
      cloneDialogOpen: false,
      cloneProjectDetails: {
        name: null,
        id: null,
        path: null
      },

      // Context menu stuff
      contextMenuShow: false,
      contextMenuX: 0,
      contextMenuY: 0,
      contextMenuItem: null
    }
  },

  computed: {
    displayItems () {
      return this.showArchived
        ? this.items
        : this.items.filter(i => !i.archived);
    },

    ...mapGetters(['loading', 'projects'])
  },

  methods: {

    async list () {
      this.items = [...this.projects];
    },

    async summary (item) {
      const details = await this.api([`/project/${item.pid}/summary`]);
      if (details) {
        return Object.assign({}, details, item);
      }
    },

    title (item) {
      if (item.organisations) {
        return "Access:\n" + Object.entries(item.organisations).map( org =>
          `• ${org[0]} (${Object.entries(org[1]).filter( access => access[1] ).map( access => access[0] ).join(", ")})`
        ).join("\n")
      }
      return "";
    },

    async load () {
      await this.refreshProjects();
      await this.list();
      const promises = [];
      for (const key in this.items) {
        const item = this.items[key];
        const promise = this.summary(item)
        .then( expanded => {
          if (expanded) {
            this.$set(this.items, key, expanded);
          }
        });
        promises.push(promise);
      }
    },

    registerNotificationHandlers () {
      this.$store.dispatch('registerHandler', {
        table: 'project`',

        handler: (context, message) => {
          if (message.payload?.table == "public") {
            this.load();
          }
        }
      });
    },

    contextMenu (e, {item}) {
      e.preventDefault();
      this.contextMenuShow = false;
      this.contextMenuX = e.clientX;
      this.contextMenuY = e.clientY;
      this.contextMenuItem = item;
      this.$nextTick( () => this.contextMenuShow = true );
    },


    async cloneProject () {

      /* Plan of action:
       * 1. Pop up dialogue asking for new project name, ID and root path
       * 2. Get source project configuration
       * 3. Blank out non-clonable parameters (ASAQC, …)
       * 4. Rewrite paths prefixed with source rootPath with dest rootPath
       * 5. Replace name, ID and rootPath
       * 6. Set archived=true
       * 7. POST new project
       * 8. Redirect to new project settings page
       */

      // 1. Pop up dialogue asking for new project name, ID and root path
      // (already done, that's why we're here)

      const pid = this.contextMenuItem?.pid;

      if (!pid) return;

      const tpl = this.cloneProjectDetails; // Shorter

      if (!tpl.id || !tpl.name || !tpl.path) {
        this.showSnack(["Missing project details. Cannot proceed", "warning"]);
        return;
      }

      this.cloneDialogOpen = false;

      /** Drills down an object and applies function fn(obj, key)
       * on each recursive property [...keys].
       */
      function drill (obj, keys, fn) {
        if (obj) {
          if (Array.isArray(keys)) {
            if (keys.length) {
              const key = keys.shift();

              if (keys.length == 0 && key != "*") { // After shift()
                if (typeof fn == "function") {
                  fn(obj, key);
                }
              }

              if (key == "*") {
                // Iterate through this object's keys
                if (keys.length) {
                  for (const k in obj) {
                    drill(obj[k], [...keys], fn);
                  }
                } else {
                  for (const k in obj) {
                    drill(obj, [k], fn);
                  }
                }
              } else {
                drill(obj[key], keys, fn);
              }
            }
          }
        }
      }

      // 2. Get source project configuration
      const src = await this.api([`/project/${pid}/configuration`]);

      const blankList = [ "id", "name", "schema", "asaqc.id", "archived" ];

      const prjPaths = [
        "preplots.*.path",
        "raw.p111.paths.*",
        "raw.p190.paths.*",
        "raw.smsrc.paths.*",
        "final.p111.paths.*",
        "final.p190.paths.*",
        "qc.definitions",
        "qc.parameters",
        "imports.map.layers.*.*.path",
        "rootPath" // Needs to go last because of lazy replacer() implementation below
      ];

      // Technically don't need this
      const deleter = (obj, key) => {
        delete obj[key];
      }

      const replacer = (obj, key) => {
        if (src.rootPath && tpl.path) {
          if (obj[key].startsWith(src.rootPath)) {
            obj[key] = obj[key].replace(src.rootPath, tpl.path);
          }
        }
      }

      // 3. Blank out non-clonable parameters (ASAQC, …)
      blankList.forEach( i => drill(src, i.split("."), deleter) );

      // 4. Rewrite paths prefixed with source rootPath with dest rootPath
      prjPaths.forEach( i => drill(src, i.split("."), replacer) );

      // 5. Replace name, ID and rootPath
      // Could use deepMerge, but meh!
      src.name = tpl.name;
      src.id = tpl.id;

      // 6. Set archived=true
      src.archived = true;

      // 7. POST new project

      const init = {
        method: "POST",
        body: src
      };
      const cb = (err, res) => {
        if (!err && res) {
          if (res.status == "201") {
            // 8. Redirect to new project settings page
            const settingsUrl = `/projects/${tpl.id.toLowerCase()}/configuration`;
            this.$router.push(settingsUrl);

          }
        }
      };
      await this.api(["/project", init, cb]);

    },

    ...mapActions(["api", "showSnack", "refreshProjects"])
  },

  mounted () {
    this.registerNotificationHandlers();
    this.load();
  }
}

</script>
