<template>
  <v-container fluid>
    <v-card>
      <v-card-title>
        <v-toolbar flat>
          <v-toolbar-title>Project quality control</v-toolbar-title>

          <v-spacer></v-spacer>
          <span title="Check this to select multiple sequences">
          <v-checkbox
            v-model="multiple"
            :label="$vuetify.breakpoint.mdAndUp ? 'Multiple' : ''"
            :append-icon="$vuetify.breakpoint.smAndDown ? 'mdi-checkbox-multiple-blank' : ''"
          ></v-checkbox>
          </span>
          <v-select
            v-model="selectedSequences"
            :items="multiple ? sequences : [ {text: 'All', value: null}, ...sequences ]"
            style="max-width:250px;"
            class="mx-2"
            placeholder="Sequence"
            :multiple="multiple"
          >
            <template v-slot:selection="{item, index}">
              <v-chip small v-if="index < 3">
                {{item.text || item}}
              </v-chip>
              <span v-if="index == 3" class="grey--text caption">(+ {{selectedSequences.length-3}} {{selectedSequences.length == 4 ? 'other' : 'others'}})</span>
            </template>
          </v-select>
          <v-text-field
            class="mx-2"
            v-model="search"
            append-icon="mdi-magnify"
            label="Filter"
            single-line
            clearable
          ></v-text-field>
          <v-checkbox
            v-model="autoexpand"
            :label="$vuetify.breakpoint.mdAndUp ? 'Auto expand' : ''"
            :append-icon="$vuetify.breakpoint.smAndDown ? 'mdi-expand-all' : ''"
            title="Automatically expand all items when searching"
          ></v-checkbox>
        </v-toolbar>
      </v-card-title>
      <v-card-text v-if="items.length">

        <v-row>
          <v-col col="12" sm="6">
            <p>QC checks done on {{updatedOn}}.</p>
          </v-col>
        </v-row>

        <v-treeview
        :items="filteredItems"
        :open.sync="open"
        item-key="_serial"
        item-text="_text"
        item-children="_children"
        :open-on-click="true"
        >

          <template v-slot:label="{item}">
            <div @dblclick.stop.prevent="toggleChildren(item)" v-if="item._kind=='test'">
              <b>{{item._text}}</b>
              <v-chip class="ml-2" v-if="item._children && itemCount(item)"
              x-small
              color="warning"
              v-text="itemCount(item)"
              >
              </v-chip>
              <v-chip class="ml-2" v-else
              x-small
              color="success"
              >
                All passed
              </v-chip>

              <v-chip v-for="label of item.labels" :key="label"
                class="mx-1"
                small
                :color="labels[label] && labels[label].view.colour"
                :title="labels[label] && labels[label].view.description"
                :close="writeaccess() && label == 'QCAccepted'"
                @click:close="unaccept(item)"
              >
                {{label}}
              </v-chip>

              <dougal-qc-acceptance v-if="writeaccess()"
                :item="item"
                @accept="accept"
                @unaccept="unaccept"
              ></dougal-qc-acceptance>

            </div>
            <div :title="item.remarks" @dblclick.stop.prevent="toggleChildren(item)" v-else-if="item._kind=='sequence'">
              {{item._text}}

              <v-chip class="ml-2" v-if="item._children && itemCount(item)"
              x-small
              color="primary"
              v-text="itemCount(item)"
              >
              </v-chip>

              <dougal-qc-acceptance v-if="writeaccess()"
                :item="item"
                @accept="accept"
                @unaccept="unaccept"
              ></dougal-qc-acceptance>

            </div>
            <div class="text--secondary" v-else>
              <dougal-qc-acceptance v-if="writeaccess()"
                :item="item"
                @accept="accept"
                @unaccept="unaccept"
              ></dougal-qc-acceptance>

              {{item._text}}
            </div>
          </template>

        </v-treeview>


      </v-card-text>

      <v-card-text v-if="!items.length && !loading">
        This project is not being quality controlled.
      </v-card-text>

    </v-card>
  </v-container>
</template>

<script>
import { mapActions, mapGetters } from 'vuex';
import { withParentProps } from '@/lib/utils';
import DougalQcAcceptance from '@/components/qc-acceptance';
import AccessMixin from '@/mixins/access';

export default {
  name: "QC",

  components: {
    DougalQcAcceptance
  },

  mixins: [
    AccessMixin
  ],

  data () {
    return {
      updatedOn: null,
      items: [],
      open: [],
      options: {},
      labels: {},
      search: null,
      selectedSequences: null,
      multiple: false,
      autoexpand: false,
      itemIndex: 0
    }
  },

  computed: {

    filteredItems () {
      return this.items.map( item =>
        this.filterByText(
          this.filterBySequence(item, this.multiple? this.selectedSequences : this.selectedSequences && [this.selectedSequences]),
          this.search
        )
      ).filter(i => !!i);
      //return this.items.map( item => this.filter(item, this.search, this.selectedSequences) ).filter(i => !!i);
    },

    sequences () {
      function getSeq (item) {
        return item?._kind == "sequence"
          ? item.sequence
          : item?._children?.length
            ? item._children.map(i => getSeq(i)).flat()
            : undefined;
      }

      const s = new Set();
      this.items.map(i => getSeq(i)).flat().filter(i => !!i).forEach(i => s.add(i));
      return [...s].sort((a,b) => b-a); // Descending order
    },

    resultObjects () {
      const values = [];

      function filterResults (item) {
        if (item._children) {
          for (const child of item._children) {
            filterResults(child);
          }
        } else if (item._id && item.id) {
          const sequence = item._id.length
            ? item._id[0]
            : item._id;
          const point = item._id.length >= 2
            ? item._id[1]
            : undefined;
          const line = item._id.length == 3
            ? item._id[2]
            : undefined;
          const path = [ "qc", item.id, "labels" ];
          const value = item.labels;
          values.push({
            sequence,
            point,
            line,
            path,
            value,
            item
          });
        }
      }

      for (const item of this.items) {
        filterResults(item);
      }

      return values;
    },

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

  watch: {
    filteredItems (v) {
      if (this.autoexpand) {
        v.forEach( item => this.toggleChildren(item, true) );
      }
    },

    multiple (value) {
      if (value && !Array.isArray(this.selectedSequences)) {
        this.selectedSequences = [];
      }
    },

    autoexpand (value) {
      setImmediate( () =>
        this.filteredItems.forEach( item => this.toggleChildren(item, value) )
      )
    }
  },

  methods: {

    itemCount (item, count = 0) {
      let sum = count;
      if (item._children) {
        sum += item._children.map(child => this.itemCount(child)).reduce( (a, b) => a+b, 0 )
      } else {
        sum++;
      }
      return sum;
    },

    async accept (items) {
      const url = `/project/${this.$route.params.project}/qc/results/accept`;
      await this.api([url, {
        method: "POST",
        body: items.map(i => i.id)
      }]);

      // The open/closed state of the tree branches should stay the same, unless
      // the tree structure itself has changed in the meanwhile.
      await this.getQCData();
    },

    async unaccept (items) {
      const url = `/project/${this.$route.params.project}/qc/results/unaccept`;
      await this.api([url, {
        method: "POST",
        body: items.map(i => i.id)
      }]);

      await this.getQCData();
    },

    async getQCLabels () {

      const sequences = new Set();
      const points  = new Set();

      this.resultObjects
      .filter(r => !!r.sequence && !r.point)
      .forEach(r => sequences.add(r.path.join("/")));

      this.resultObjects
      .filter(r => !!r.sequence && !!r.point)
      .forEach(r => points.add(r.path.join("/")));

      const promises = [];

      for (const path of sequences) {
        const url = `/project/${this.$route.params.project}/meta/raw/sequences/${path}`;
        const promise = this.api([url]).then(res => {
          for (const item of res) {
            const obj = this.resultObjects.find(o => o.sequence == item.sequence &&
              o.point == item.point &&
              o.path.join("/") == path
            );
            if (obj) {
              obj.labels = item.data;
              this.$set(obj.item, "labels", item.data);
            }
          }
        });
        promises.push(promise);
      }

      for (const path of points) {
        const url = `/project/${this.$route.params.project}/meta/raw/points/${path}`;
        const promise = this.api([url]).then(res => {
          for (const item of res) {
            const obj = this.resultObjects.find(o => o.sequence == item.sequence &&
              o.point == item.point &&
              o.path.join("/") == path
            );
            if (obj) {
              obj.labels = item.data;
              this.$set(obj.item, "labels", item.data);
            }
          }
        });
        promises.push(promise);
      }

      await Promise.all(promises);
    },

    filterByText(item, queryText) {
      if (!queryText || !item) return item;

      if (item._children) {
        const newItem = Object.assign({}, item);
        newItem._children = item._children.map( child => this.filterByText(child, queryText) ).filter(i => !!i)
        if (newItem._children.length > 0) {
          return newItem;
        }
      }

      if (item._text && item._text.toLowerCase().indexOf(queryText.toLowerCase()) > -1) {
        return item;
      }
    },

    filterBySequence(item, sequences) {
      if (!sequences || !sequences.length) return item;

      if (item._kind == "sequence" && (sequences.includes(item.sequence) || sequences.includes(item))) {
        return item;
      }

      if (item._children) {
        const newItem = {...item};
        newItem._children = item._children.map(child =>
          this.filterBySequence(child, sequences)
        ).filter(i => !!i);

        if (newItem._children.length) {
          return newItem;
        }
      }
    },

    toggleChildren (item, state) {
      const open = typeof state == 'undefined'
        ? !this.open.includes(item._serial)
        : state;

      if (item._children) {
        item._children.forEach(child => this.toggleChildren(child, open));
      }

      if (open) {
        if (!this.open.includes(item._serial)) {
          this.open.push(item._serial);
        }
      } else {
        const index = this.open.indexOf(item._serial);
        if (index > -1) {
          this.open.splice(index, 1);
        }
      }
    },

    transform (item, qcId) {
      item._serial = ++this.itemIndex;
      if (item.name && (item.check || item.children)) {
        // This is probably a test
        qcId ??= item.id;

        item._kind = "test";
        item._text = item.name;
        item._children = [];
        if (item.children) {
          // Child tests
          item._children = item.children.map(i => this.transform(i, qcId));
        }

        if (item.sequences) {
          // In theory an item could have both subtests and its own results
          // so we don't do an if … else but two independent ifs.
          item._children = item._children.concat(item.sequences.map(i => this.transform(i, qcId)));
        }
      } else if (item.sequence && item.line) {
        // This is probably a sequence

        item._kind = "sequence";
        item._text = `Sequence ${item.sequence}${item.meta?.qc && item.meta.qc[qcId] ? (": "+item.meta.qc[qcId]) : ""}`;

        if (item.shots && item.shots.length) {
          item._children = item.shots.map(i => this.transform(i, qcId));
        }
      } else if (item.sequence && item.point) {
        // This is probably a shotpoint

        item._kind = "point";
        item._text = `Point ${item.point}: ${item.remarks}`
      }

      return item;
    },

    async getLabelDefinitions () {
      const url = `/project/${this.$route.params.project}/label`;

      this.labels = await this.api([url]) || {};
    },

    async getQCData () {

      const url = `/project/${this.$route.params.project}/qc/results`;

      const res = await this.api([url]);

      if (res) {
        this.itemIndex = 0;
        this.items = res.map(i => this.transform(i)) || [];
        this.updatedOn = res.updatedOn;
        await this.getQCLabels();
      } else {
        this.items = [];
        this.updatedOn = null;
      }

    },

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

  async mounted () {
    await this.getLabelDefinitions();
    await this.getQCData();
  }

}

</script>
