<template>
  <v-container fluid>

    <v-card>
      <v-card-title>
        <v-toolbar flat>
          <v-toolbar-title>
            <template v-if="$route.params.sequence">
              <v-btn icon small
                :disabled="sequenceIndex >= (sequences.length - 1)"
                :to="{name: 'logBySequence', params: { sequence: (sequences[sequences.length-1]||{}).sequence }}"
                title="Go to the first sequence"
              >
                <v-icon dense>mdi-chevron-double-left</v-icon>
              </v-btn>
              <v-btn icon small
                :disabled="sequenceIndex >= (sequences.length - 1)"
                :to="{name: 'logBySequence', params: { sequence: (sequences[sequenceIndex+1]||{}).sequence }}"
                title="Go to the previous sequence"
              >
                <v-icon dense>mdi-chevron-left</v-icon>
              </v-btn>
            </template>
            <span class="d-none d-lg-inline">
              {{
                $route.params.sequence
                  ? ($route.params.sequence.includes && $route.params.sequence.includes(";"))
                    ? `Sequences ${$route.params.sequence.split(";").sort().join(", ")}`
                    : `Sequence ${$route.params.sequence}`
                  : $route.params.date0
                    ? $route.params.date1
                      ? `Between ${$route.params.date0} and ${$route.params.date1}`
                      : `On ${$route.params.date0}`
                    : "All events"
              }}
            </span>
            <span class="d-lg-none">
              {{
                $route.params.sequence
                  ? ($route.params.sequence.includes && $route.params.sequence.includes(";"))
                    ? `${$route.params.sequence.split(";").sort().join(", ")}`
                    : `${$route.params.sequence}`
                  : $route.params.date0
                    ? $route.params.date1
                      ? `${$route.params.date0} ‒ ${$route.params.date1}`
                      : `${$route.params.date0}`
                    : ""
              }}
            </span>

            <template v-if="$route.params.sequence">
              <v-btn icon small
                :disabled="sequenceIndex==0"
                :to="{name: 'logBySequence', params: { sequence: (sequences[sequenceIndex-1]||{}).sequence }}"
                title="Go to the next sequence"
              >
                <v-icon dense>mdi-chevron-right</v-icon>
              </v-btn>
              <v-btn icon small class="mr-1"
                :disabled="sequenceIndex==0"
                :to="{name: 'logBySequence', params: { sequence: (sequences[0]||{}).sequence }}"
                title="Go to the last sequence"
              >
                <v-icon dense>mdi-chevron-double-right</v-icon>
              </v-btn>
            </template>

            <a v-if="$route.params.sequence"
              class="mr-3"
              :href="`/projects/${$route.params.project}/sequences/${$route.params.sequence}`"
              title="View the shotlog for this sequence"
            >
              <v-icon
                right
                color="teal"
              >mdi-format-list-numbered</v-icon>
            </a>

          </v-toolbar-title>



            <dougal-event-edit v-if="$parent.writeaccess()"
              v-model="eventDialog"
              v-bind="editedEvent"
              :available-labels="userLabels"
              :preset-remarks="presetRemarks"
              @new="newEvent"
              @changed="saveEvent"
            >
            </dougal-event-edit>

            <dougal-event-edit-labels v-if="$parent.writeaccess()"
              v-model="eventLabelsDialog"
              :labels="userLabels"
              :selected="contextMenuItem ? contextMenuItem.labels||[] : []"
              @selectionChanged="(data) => patchEvent(contextMenuItem.id, data)"
            >
            </dougal-event-edit-labels>

          <v-menu v-if="$route.params.sequence">
            <template v-slot:activator="{on, attrs}">
              <v-btn class="ml-3" small v-on="on" v-bind="attrs">
                <span class="d-none d-lg-inline">Download as…
                  <v-icon right small>mdi-cloud-download</v-icon>
                </span>
                <span class="d-lg-none">
                  <v-icon small>mdi-cloud-download</v-icon>
                </span>
              </v-btn>
            </template>

            <v-list>
              <v-list-item
                :href="`/api/project/${$route.params.project}/event/-/${$route.params.sequence}?mime=application%2Fvnd.seis%2Bjson`"
                title="Download as a Multiseis-compatible Seis+JSON file."
              >Seis+JSON</v-list-item>
              <v-list-item
                :href="`/api/project/${$route.params.project}/event/-/${$route.params.sequence}?mime=application%2Fgeo%2Bjson`"
                title="Download as a QGIS-compatible GeoJSON file"
              >GeoJSON</v-list-item>
              <v-list-item
                :href="`/api/project/${$route.params.project}/event/-/${$route.params.sequence}?mime=application%2Fjson`"
                title="Download as a generic JSON file"
              >JSON</v-list-item>
              <v-list-item
                :href="`/api/project/${$route.params.project}/event/-/${$route.params.sequence}?mime=text%2Fcsv`"
                title="Download as Comma Separated Values file"
              >CSV</v-list-item>
              <v-list-item
                :href="`/api/project/${$route.params.project}/event/-/${$route.params.sequence}?mime=text%2Fhtml`"
                title="Download as an HTML formatted file"
              >HTML</v-list-item>
              <v-list-item
                :href="`/api/project/${$route.params.project}/event/-/${$route.params.sequence}?mime=application%2Fpdf`"
                title="Download as a Portable Document File"
              >PDF</v-list-item>
            </v-list>
          </v-menu>
          <v-menu v-else>
            <template v-slot:activator="{on, attrs}">
              <v-btn class="ml-5" small v-on="on" v-bind="attrs">
                <span class="d-none d-lg-inline">Download as…</span>
                <v-icon right small>mdi-cloud-download</v-icon>
              </v-btn>
            </template>

            <v-list>
              <v-list-item
                :href="`/api/project/${$route.params.project}/event?mime=application%2Fvnd.seis%2Bjson&filename=event-log-multiseis.json`"
                title="Download as a Multiseis-compatible Seis+JSON file."
              >Seis+JSON</v-list-item>
              <!-- Not yet implemented
              <v-list-item
                :href="`/api/project/${$route.params.project}/event?mime=application%2Fgeo%2Bjson&filename=event-log.geojson`"
                title="Download as a QGIS-compatible GeoJSON file"
              >GeoJSON</v-list-item>
              -->
              <v-list-item
                :href="`/api/project/${$route.params.project}/event?mime=application%2Fjson&filename=event-log.json`"
                title="Download as a generic JSON file"
              >JSON</v-list-item>
              <v-list-item
                :href="`/api/project/${$route.params.project}/event?mime=application%2Fyaml&filename=event-log.yaml`"
                title="Download as a YAML file"
              >YAML</v-list-item>
              <v-list-item
                :href="`/api/project/${$route.params.project}/event?mime=text%2Fcsv&sortBy=tstamp&sortDesc=false&filename=event-log.csv`"
                title="Download as Comma Separated Values file"
              >CSV</v-list-item>
              <!-- Not yet implemented
              <v-list-item
                :href="`/api/project/${$route.params.project}/event?mime=text%2Fhtml&filename=event-log.html`"
                title="Download as an HTML formatted file"
              >HTML</v-list-item>
              <v-list-item
                :href="`/api/project/${$route.params.project}/event?mime=application%2Fpdf&filename=event-log.pdf`"
                title="Download as a Portable Document File"
              >PDF</v-list-item>
              -->
            </v-list>
          </v-menu>

          <v-spacer></v-spacer>
          <v-text-field
            v-model="filter"
            append-icon="mdi-magnify"
            label="Filter"
            single-line
            clearable
            hide-details>
            <template v-slot:prepend-inner>
              <v-chip v-if="labelSearch"
                class="mr-1"
                small
                close
                @click:close="labelSearch=null"
                :color="labels[labelSearch] && labels[labelSearch].view.colour"
                :title="labels[labelSearch] && labels[labelSearch].view.description"
                :dark="labels[labelSearch] && labels[labelSearch].view.dark"
                :light="labels[labelSearch] && labels[labelSearch].view.light"
              >{{labelSearch}}</v-chip>
            </template>
          </v-text-field>
        </v-toolbar>
      </v-card-title>
      <v-card-text>

    <!-- BEGIN Context menu for log entries -->
    <v-menu v-if="$parent.writeaccess()"
      v-model="contextMenuShow"
      :position-x="contextMenuX"
      :position-y="contextMenuY"
      absolute
      offset-y
    >
      <!-- Add comment: goes on every entry -->
      <v-list dense v-if="contextMenuItem">
        <v-list-item
          @click="() => addEvent(contextMenuItem.items ? contextMenuItem.items[0] : contextMenuItem)"
        >
          <v-list-item-icon><v-icon>mdi-pencil-plus</v-icon></v-list-item-icon>
          <v-list-item-title>Add comment</v-list-item-title>
        </v-list-item>

        <!-- BEGIN These options don't apply to read-only items -->
        <template v-if="!isRowReadonly(contextMenuItem)">

          <!-- BEGIN These are whole row entries NOTE: Why? TODO Reconsider this -->
          <template v-if="contextMenuItem.key">

            <!-- If the item has a shotpoint, that's what we edit -->
            <!-- NOTE We may use this in the future to bulk edit comments
                      related to the same shot / moment, but not now.
            <v-list-item @click="true" v-if="contextMenuItem.sequence">
              <v-list-item-icon><v-icon>mdi-pencil</v-icon></v-list-item-icon>
              <v-list-item-title>Edit shotpoint</v-list-item-title>
            </v-list-item>
            -->

            <!-- Otherwise, it's the timestamp -->
            <!-- NOTE We may use this in the future to bulk edit comments
                      related to the same shot / moment, but not now.
            <v-list-item @click="true" v-else>
              <v-list-item-icon><v-icon>mdi-pencil</v-icon></v-list-item-icon>
              <v-list-item-title>Edit timestamp</v-list-item-title>
            </v-list-item>
            -->
          </template>
          <!-- END These are whole row entries -->

          <!-- BEGIN These apply to individual comments -->

          <!-- Edit comment -->
          <v-list-item @click="editEvent(contextMenuItem)" v-if="contextMenuItem.remarks">
            <v-list-item-icon><v-icon>mdi-pencil</v-icon></v-list-item-icon>
            <v-list-item-title>Edit comment</v-list-item-title>
          </v-list-item>

          <!-- Edit labels -->
          <!-- NOTE The edit comment dialogue takes care of this just fine, but let's
                    leave it for now and see if people use it. -->
          <v-list-item @click="() => eventLabelsDialog=true" v-if="contextMenuItem.labels">
            <v-list-item-icon><v-icon>mdi-tag-multiple</v-icon></v-list-item-icon>
            <v-list-item-title>Edit labels</v-list-item-title>
          </v-list-item>

          <!-- END These apply to individual comments -->

        </template>
        <!-- END These options don't apply to read-only items -->

        <!-- View item on map if it has a geometry -->
        <v-list-item v-if="viewOnMap(contextMenuItem)" :href="viewOnMap(contextMenuItem)">
          <v-list-item-icon><v-icon>mdi-map</v-icon></v-list-item-icon>
          <v-list-item-title>View on map</v-list-item-title>
        </v-list-item>

        <v-divider></v-divider>

        <!-- Delete command: single comment -->
        <v-list-item @click="() => removeEvent(contextMenuItem)" v-if="deletableEntries(contextMenuItem) == 1">
          <v-list-item-icon><v-icon>mdi-delete</v-icon></v-list-item-icon>
          <v-list-item-title class="warning--text">Delete comment</v-list-item-title>
        </v-list-item>
        <!-- Delete command: read-only item (no action) -->
        <v-list-item v-else-if="deletableEntries(contextMenuItem) == 0" disabled>
          <v-list-item-icon><v-icon>mdi-delete-off</v-icon></v-list-item-icon>
          <v-list-item-title>This entry is read-only</v-list-item-title>
        </v-list-item>
        <!-- Delete command: multiple comments -->
        <v-list-item @click="() => removeEvent(contextMenuItem)" v-else>
          <v-list-item-icon><v-icon>mdi-delete</v-icon></v-list-item-icon>
          <v-list-item-title class="error--text">Delete all comments</v-list-item-title>
        </v-list-item>

        <!-- BEGIN This section only applies to QC events -->
        <template v-if="contextMenuItem.meta.qc_id">

          <v-divider></v-divider>

          <!-- Mark QC accepted -->
          <v-list-item @click="() => acceptQc(contextMenuItem)" v-if="!isAcceptedQc(contextMenuItem)">
            <v-list-item-icon><v-icon>mdi-check</v-icon></v-list-item-icon>
            <v-list-item-title>Mark QC accepted</v-list-item-title>
          </v-list-item>
          <!-- Unmark QC accepted -->
          <v-list-item @click="() => acceptQc(contextMenuItem, false)" v-else>
            <v-list-item-icon><v-icon>mdi-restore</v-icon></v-list-item-icon>
            <v-list-item-title>Unmark QC accepted</v-list-item-title>
          </v-list-item>

        </template>
        <!-- END This section only applies to QC events -->

      </v-list>

    </v-menu>
    <!-- END Context menu for log entries -->

    <v-data-table
    dense
    :headers="headers"
    :items="rows"
    :items-per-page.sync="itemsPerPage"
    :server-items-length="eventCount"
    item-key="key"
    :item-class="itemClass"
    sort-by="tstamp"
    :sort-desc="true"
    :search="filter"
    :loading="eventsLoading"
    :options.sync="options"
    fixed-header
    :footer-props='{itemsPerPageOptions: [ 10, 25, 50, 100, 500, -1 ], showFirstLastPage: true}'
    @click:row="setActiveItem"
    @contextmenu:row="contextMenu"
    >

      <template v-slot:item.tstamp="{value}">
        <span style="white-space:nowrap;" v-if="value">
          {{ value.replace(/(.{10})T(.{8}).{4}Z$/, "$1 $2") }}
        </span>
      </template>

      <template v-slot:item.remarks="{item}">
        <template>
          <div>
            <div v-for="entry in item.items"
              @contextmenu.stop="(e) => contextMenu(e, {entry})"
            >
              <span v-if="entry.labels.length">
                <v-chip v-for="label in entry.labels"
                  class="mr-1 px-2 underline-on-hover"
                  x-small
                  :color="labels[label] && labels[label].view.colour"
                  :title="labels[label] && labels[label].view.description"
                  :dark="labels[label] && labels[label].view.dark"
                  :light="labels[label] && labels[label].view.light"
                  :key="label"
                  @click="labelSearch=label"
                >{{label}}</v-chip>
              </span>
              <v-icon v-if="entry.meta.auto || entry.meta.author"
                x-small
                left
                color="primary"
                :title="entry.meta.author?`Automatic event by ${entry.meta.author}`:'Automatic event'"
              >mdi-robot</v-icon>
              <dougal-event-edit-history v-if="entry.has_edits && $parent.writeaccess()"
                :id="entry.id"
                :disabled="eventsLoading"
                :labels="labels"
              ></dougal-event-edit-history>
              <span v-if="entry.meta.readonly"
                class="entry text--secondary"
                v-html="$options.filters.markdownInline(entry.remarks)">
              </span>
              <span v-else
                class="entry underline-on-hover"
                v-html="$options.filters.markdownInline(entry.remarks)">
              </span>
            </div>
          </div>
        </template>

      </template>

      <template v-slot:footer.prepend>
        <v-checkbox v-for="label in filterableLabels"
          :key="label"
          class="mr-3"
          v-model="shownLabels"
          :value="label"
          :title="`Show ${label} events`"
          dense
          hide-details
        >
          <template v-slot:label>
              <v-chip
                x-small
                :color="labels[label] && labels[label].view.colour"
                :title="labels[label] && labels[label].view.description"
                :dark="labels[label] && labels[label].view.dark"
                :light="labels[label] && labels[label].view.light"
              >{{label}}
              </v-chip>
          </template>
        </v-checkbox>
      </template>

    </v-data-table>
      </v-card-text>
    </v-card>
  </v-container>
</template>

<style>
tr.align-top td:not(.align-default) {
  vertical-align: top;
}
</style>

<style scoped>
.hover {
  opacity: 0.4;
}
.hover:hover {
  opacity: 1;
}
.hover.off:hover {
  opacity: 0.4;
}
.underline-on-hover:hover {
  text-decoration: underline;
}

.entry {
  cursor: default;
}
.entry.underline-on-hover:hover {
  text-decoration: underline;
}
</style>

<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import DougalContextMenu from '@/components/context-menu';
import DougalEventEdit from '@/components/event-edit'
import DougalEventEditLabels from '@/components/event-edit-labels'
import DougalEventEditHistory from '@/components/event-edit-history'

function ArraysEqual (a, b) {
  return a.every(i => b.includes(i)) && b.every(i => a.includes(i));
}

function EventKey (e) {
  return e.type+e.id;
}

function clone (obj) {
  return JSON.parse(JSON.stringify(obj));
}

export default {
  name: "Log",

  components: {
    DougalEventEdit,
    DougalEventEditLabels,
    DougalEventEditHistory,
    DougalContextMenu
  },

  data () {
    return {
      headers: [
        {
          value: "tstamp",
          text: "Timestamp",
          width: "20ex"
        },
        {
          value: "sequence",
          text: "Sequence",
          align: "end",
          width: "10ex"
        },
        {
          value: "point",
          text: "Shotpoint",
          align: "end",
          width: "10ex"
        },
        {
          value: "remarks",
          text: "Text",
          width: "100%",
          cellClass: "align-default"
        }
      ],
      items: [],
      options: {},
      filter: "",
      filterableLabels: [ "QC", "QCAccepted" ],
      shownLabels: [ "QC", "QCAccepted" ],
      eventCount: null,
      eventDialog: false,
      eventLabelsDialog: false,
      defaultEventTimestamp: null,
      remarksMenu: null,
      remarksMenuItem: null,
      editedEvent: {},
      labelSearch: null,
      queuedReload: false,
      itemsPerPage: 25,

      // Row highlighter
      activeItem: null,

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

  computed: {
    rows () {
      const rows = {};
      this.items
      .forEach(i => {
        const key = (i.sequence && i.point) ? (i.sequence+"@"+i.point) : i.tstamp;
        if (!rows[key]) {
          rows[key] = {
            key,
            tstamp: i.tstamp,
            sequence: i.sequence,
            point: i.point,
            items: []
          }
        }

        const row = rows[key];

        row.items.push(i);
      });
      return Object.values(rows);
    },

    popularLabels () {
      const tuples = this.items.flatMap( i => i.labels )
      .filter( l => (this.labels[l]??{})?.model?.user )
      .reduce( (acc, cur) => {
        return cur in acc ? ++acc[cur][1] : acc[cur]=[cur,1], acc
      }, {});

      return Object.values(tuples)
      .sort( (a, b) => b[1]-a[1] );
    },

    filteredLabels () {
      return this.filterableLabels.filter( label => !this.shownLabels.includes(label) );
    },

    presetRemarks () {
      return this.projectConfiguration?.events?.presetRemarks ?? [];
    },

    defaultSequence () {
      if (this.$route.params.sequence) {
        return Number(this.$route.params.sequence.split(";").pop());
      } else {
        return this.sequence;
      }
    },

    sequenceIndex () {
      if ("sequence" in this.$route.params) {
        const index = this.sequences.findIndex( i => i.sequence == this.$route.params.sequence );
        if (index != -1) {
          return index;
        }
      }
      // return undefined
    },

    ...mapGetters(['user', 'eventsLoading', 'online', 'sequence', 'sequences', 'line', 'point', 'position', 'timestamp', 'lineName', 'events', 'labels', 'userLabels', 'projectConfiguration']),
    ...mapState({projectSchema: state => state.project.projectSchema})

  },

  watch: {
    options: {
      async handler () {
        this.savePrefs(),
        await this.fetchEvents();
      },
      deep: true
    },

    async events () {
      console.log("Events changed");
      await this.fetchEvents();
    },

    eventDialog (val) {
      if (val) {
        // If not online
        this.defaultEventTimestamp = Date.now();
      }
    },

    filter (newVal, oldVal) {
      this.savePrefs();
      if (newVal?.toLowerCase() != oldVal?.toLowerCase()) {
        this.fetchEvents();
      }
    },

    labelSearch () {
      this.savePrefs();
      this.fetchEvents();
    },

    filteredLabels () {
      this.savePrefs()
      this.fetchEvents();
    },

    itemsPerPage (newVal, oldVal) {
      localStorage.setItem(`dougal/prefs/${this.user?.name}/${this.$route.params.project}/${this.$options.name}/items-per-page`, newVal);
    },

    user (newVal, oldVal) {
      this.loadPrefs();
    }

  },

  methods: {

    debug (value) {
      console.log("DEBUG", value);
    },

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

    /** Check if this item (or entry) has multiple
     * deletable items, just one, or none (i.e., it is read-only)
     *
     * @return {Number} Number of deletable items.
     */
    deletableEntries (item) {
      return item.items
        ? item.items.filter(e => !e.meta.readonly).length
        : item.meta.readonly
          ? 0
          : 1;
    },

    isRowReadonly (item) {
      return !this.deletableEntries(item);
    },

    itemClass (item) {
      if (this.activeItem == item) {
        return 'align-top blue accent-1 elevation-3';
      } else if (item.sequence && item.point && item.tstamp) {
        return this.$vuetify.theme.isDark
          ? 'align-top blue-grey darken-4'
          : 'align-top blue-grey lighten-5';
      } else {
        return 'align-top';
      }
    },

    async fetchEvents (opts = {}) {
      const options = {
        sequence: this.$route.params.sequence,
        text: this.filter,
        label: this.labelSearch,
        excludeLabels: this.filteredLabels,
        ...this.options
      };
      const res = await this.getEvents([this.$route.params.project, options]);
      this.items = res.events;
      this.eventCount = res.count;
    },

    newItem (from = {}) {
      const type = (from.sequence && from.point) ? "sequence" : "timed";
      const tstamp = from.tstamp || (new Date).toISOString();
      const sequence = from.sequence || null; // FIXME TODO Use vuex
      const point = from.point || null; // FIXME TODO Use vuex
      const geometry = from.geometry || null; // FIXME TODO Use vuex
      return {
        type,
        id: null,
        tstamp,
        sequence,
        point,
        remarks: "",
        labels: [],
        geometry
      };
    },

    cloneEvent (template = {}) {
      this.editedEvent = clone(template);

      // NOTE Can we actually use "id", it being a reserved HTML attribute?
      this.editedEvent.id = template.id;

      if (template.meta?.geometry?.type == "Point") {
        this.editedEvent.longitude = template.meta.geometry.coordinates[0];
        this.editedEvent.latitude = template.meta.geometry.coordinates[1];
      }
    },

    /** Add a brand new event.
     *
     * Used when adding a new event to the database,
     * assumed to have occurred in the immediate past,
     * so we populate it with the last received real-time
     * information (timestamp, shotpoint, position, etc.)
     */
    newEvent () {
      this.editedEvent = {
        tstamp: this.timestamp,
        sequence: this.sequence,
        point: this.point,
        longitude: (this.position??[])[0],
        latitude: (this.position??[])[1]
      }
    },

    /** Add an event based on another.
     *
     * Used when adding an event to a timestamp or
     * point for which we already have an event.
     */
    addEvent (template) {
      this.cloneEvent(template);
      this.editedEvent.id = null;
      this.editedEvent.remarks = null;
      this.editedEvent.labels = null;
      this.eventDialog = true;
    },

    editEvent (template) {
      this.cloneEvent(template);
      this.eventDialog = true;
    },

    async patchEvent (id, data) {
      const callback = (err, res) => {
        if (!err && res.ok) {
          this.showSnack(["Event saved", "success"]);
          this.queuedReload = true;
          this.fetchEvents({cache: "reload"});
        }
      }

      const url = `/project/${this.$route.params.project}/event/${id}`;
      await this.api([url, {
        method: "PATCH",
        body: data
      }, callback]);
    },

    // TODO POST or PATCH depending on whether this is a new event
    // (no id) or an edit (id present)
    async saveEvent (event) {
      const callback = (err, res) => {
        if (!err && res.ok) {
          this.showSnack(["Event saved", "success"]);
          this.queuedReload = true;
          this.fetchEvents({cache: "reload"});
        }
      }

      if (event) {
        if (event.id) {
          const id = event.id;
          delete event.id;

          // If this is an edit, ensure that it is *either*
          // a timestamp event or a sequence + point one.
          if (event.sequence && event.point && event.tstamp) {
            delete event.tstamp;
          }

          this.putEvent(id, event, callback); // No await
        } else {
          this.postEvent(event, callback); // No await
        }
      }
    },

    async putEvent (id, event, callback) {
      const url = `/project/${this.$route.params.project}/event/${id}`;
      await this.api([url, {
        method: "PATCH",
        body: event
      }, callback]);
    },

    async postEvent (event, callback) {
      const url = `/project/${this.$route.params.project}/event`;
      await this.api([url, {
        method: "POST",
        body: event
      }, callback]);
    },

    async removeEvent (target) {
      if (Array.isArray(target?.items)) {
        return await this.removeEvent(target.items);
      }

      if (Array.isArray(target)) {
        if (target.length == 1) {
          return await this.removeEvent(target[0]);
        }

        const ids = target.map(i => i?.id ?? i);

        const callback = (err, res) => {
          if (!err && res.ok) {
            this.showSnack([`${ids.length} events deleted`, "red"]);
            this.queuedReload = true;
            this.fetchEvents({cache: "reload"});
          }
        }

        Promise.all(ids.forEach( id => {
          const url = `/project/${this.$route.params.project}/event/${id}`;
          return this.api([url, {method: "DELETE"}]);
        })).then(callback);

      } else {
        const id = target?.id ?? target;

        const callback = (err, res) => {
          if (!err && res.ok) {
            this.showSnack(["Event deleted", "red"]);
            this.queuedReload = true;
            this.fetchEvents({cache: "reload"});
          }
        }

        const url = `/project/${this.$route.params.project}/event/${id}`;
        await this.api([url, {
          method: "DELETE",
        }, callback]);
      }
    },

    addPresetRemark ({text}) {
      if (this.remarksMenuItem) {
        this.remarksMenuItem.remarks = text;
      }
      this.remarksMenuItem = null;
    },

    availableLabels (usedLabels) {
      return Object.keys(this.labels)
      .filter(k => !usedLabels.includes(k) && this.labels[k].model.user);
    },

    handleKeyboardEvent (e) {
      if (e.ctrlKey && !e.altKey && !e.shiftKey && (e.keyCode == 13 || e.key == "F2")) {
        // Add timed event if offline or shot event if online
        this.eventDialog = true;
      } else if (e.ctrlKey && !e.altKey && e.shiftKey && (e.keyCode == 13 || e.key == "F2")) {
        // Add timed event (even if online)
        this.eventDialog = true;
      }

    },

    viewOnMap(item) {
      if (item?.meta && item.meta?.geometry?.type == "Point") {
        const [ lon, lat ] = item.meta.geometry.coordinates;
        return `map#z15x${lon.toFixed(6)}y${lat.toFixed(6)}::${lon.toFixed(6)},${lat.toFixed(6)}`;
      } else if (item?.items) {
        return this.viewOnMap(item.items[0]);
      }
    },

    isAcceptedQc (item) {
      return item.labels.includes('QCAccepted');
    },

    async acceptQc (item, accept = true) {

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

      await this.api([url, {
        method: "POST",
        body: [ item.id ]
      }]);

    },

    setActiveItem (item) {
    // Disable setting the active item for now,
    // it's kind of annoying.
      return;

      /*
      this.activeItem = this.activeItem == item
        ? null
        : item;
      */
    },

    getPrefsKey () {
      return `dougal/prefs/${this.user?.name}/${this.$route.params.project}/Log/v1`;
    },

    savePrefs () {
      const prefs = {
        shownLabels: this.shownLabels,
        labelSearch: this.labelSearch,
        filter: this.filter,
        options: this.options
      };
      localStorage.setItem(this.getPrefsKey(), JSON.stringify(prefs));
    },

    loadPrefs () {
      const stored = localStorage.getItem(this.getPrefsKey());
      if (stored) {
        const prefs = JSON.parse(stored);
        if (prefs.shownLabels !== undefined) this.shownLabels = prefs.shownLabels;
        if (prefs.labelSearch !== undefined) this.labelSearch = prefs.labelSearch;
        if (prefs.filter !== undefined) this.filter = prefs.filter;
        if (prefs.options !== undefined) this.options = prefs.options;
      }
    },

    ...mapActions(["api", "showSnack", "refreshEvents", "getEvents"])
  },

  async mounted () {
    this.loadPrefs();
    this.fetchEvents();

    window.addEventListener('keyup', this.handleKeyboardEvent);
  },

  beforeDestroy () {
    window.removeEventListener('keyup', this.handleKeyboardEvent);
  }

}

</script>
