<template>
  <v-dialog
    :value="value"
    @input="(e) => $emit('input', e)"
    max-width="600"
  >

    <template v-slot:activator="{ on, attrs }">
      <v-btn
        class="mx-2"
        fab dark
        x-small
        color="primary"
        title="Add event"
        @click="(e) => $emit('new', e)"
        v-bind="attrs"
        v-on="on"
      >
        <v-icon dark>mdi-plus</v-icon>
      </v-btn>
    </template>

    <v-card>
      <v-toolbar
        flat
        color="transparent"
      >
        <v-toolbar-title>Event</v-toolbar-title>
        <v-spacer></v-spacer>
      </v-toolbar>

      <v-container class="py-0">

        <v-row dense>
          <v-col>
            <v-menu
              v-model="dateMenu"
              :close-on-content-click="false"
              :nudge-right="40"
              transition="scale-transition"
              offset-y
              min-width="auto"
            >
              <template v-slot:activator="{ on, attrs }">
                <v-text-field
                  v-model="tsDate"
                  :disabled="!!(entrySequence || entryPoint)"
                  label="Date"
                  suffix="UTC"
                  prepend-icon="mdi-calendar"
                  readonly
                  v-bind="attrs"
                  v-on="on"
                  @change="updateAncillaryData"
                ></v-text-field>
              </template>
              <v-date-picker
                v-model="tsDate"
                @input="dateMenu = false"
              ></v-date-picker>
            </v-menu>

          </v-col>
          <v-col>
            <v-text-field
              v-model="tsTime"
              :disabled="!!(entrySequence || entryPoint)"
              label="Time"
              suffix="UTC"
              prepend-icon="mdi-clock-outline"
              type="time"
              step="1"
              @change="updateAncillaryData"
            >
              <template v-slot:prepend>
                <v-menu
                  v-model="timeMenu"
                  :close-on-content-click="false"
                  :nudge-right="40"
                  transition="scale-transition"
                  offset-y
                  min-width="auto"
                >
                  <template v-slot:activator="{ on, attrs }">
                    <v-icon v-on="on" v-bind="attrs">mdi-clock-outline</v-icon>
                  </template>
                  <v-time-picker
                    v-model="tsTime"
                    format="24hr"
                  ></v-time-picker>
                </v-menu>
              </template>
            </v-text-field>
          </v-col>
        </v-row>

        <v-row dense>
          <v-col>
            <v-text-field
              v-model="entrySequence"
              type="number"
              min="1"
              step="1"
              label="Sequence"
              prepend-icon="mdi-format-list-bulleted"
              @change="updateAncillaryData"
            >
            </v-text-field>
          </v-col>
          <v-col>
            <v-text-field
              v-model="entryPoint"
              type="number"
              min="1"
              step="1"
              label="Point"
              prepend-icon="mdi-map-marker-circle"
              @change="updateAncillaryData"
            >
            </v-text-field>
          </v-col>
        </v-row>

        <v-row dense>
          <v-col cols="12">
            <dougal-event-select
              v-bind.sync="entryRemarks"
              :preset-remarks="presetRemarks"
              @update:labels="(v) => this.entryLabels = v"
            ></dougal-event-select>
          </v-col>
        </v-row>

        <v-row dense>
          <v-col cols="12">
            <v-autocomplete
              ref="labels"
              v-model="entryLabels"
              :items="categories"
              multiple
              menu-props="closeOnClick, closeOnContentClick"
              attach
              chips
              label="Labels"
              prepend-icon="mdi-tag-multiple"
              append-outer-icon="mdi-magnify"
              @click:append-outer="() => $refs.labels.focus()"
            >

              <template v-slot:selection="{ item, index, select, selected, disabled }">
                <v-chip
                  :disabled="loading"
                  small
                  light
                  :color="item.colour"
                  :title="item.title"
                  close
                  @click:close="entryLabels.splice(index, 1)"
                >
                  <v-icon
                    left
                    v-text="item.icon"
                  ></v-icon>
                  {{ item.text }}
                </v-chip>
              </template>

              <template v-slot:item="{ item }">
                <v-list-item-avatar
                  class="my-0"
                  width="12ex"
                >
                  <v-chip
                    x-small
                    light
                    :color="item.colour"
                    :title="item.title"
                  >{{item.text}}</v-chip>
                </v-list-item-avatar>
                <v-list-item-title v-text="item.title"></v-list-item-title>
              </template>

            </v-autocomplete>
          </v-col>
        </v-row>

        <v-row dense>
          <v-col>
            <v-text-field
              v-model="entryLatitude"
              label="Latitude"
              prepend-icon="φ"
              disabled
            >
              <template v-slot:append-outer>
                <v-icon v-if="false/*TODO*/"
                  title="Click to set position"
                  @click="1==1/*TODO*/"
                >mdi-crosshairs-gps</v-icon>
                <v-icon v-else
                  disabled
                  title="No GNSS available"
                >mdi-crosshairs</v-icon>
              </template>
            </v-text-field>
          </v-col>
          <v-col>
            <v-text-field
              v-model="entryLongitude"
              label="Longitude"
              prepend-icon="λ"
              disabled
            >
              <template v-slot:append-outer>
                <v-icon v-if="false"
                  title="Click to set position"
                  @click="getPosition"
                >mdi-crosshairs-gps</v-icon>
                <v-icon v-else
                  title="No GNSS available"
                  disabled
                >mdi-crosshairs</v-icon>
              </template>
            </v-text-field>
          </v-col>
        </v-row>

      </v-container>


      <v-divider></v-divider>

      <v-card-actions>
        <v-btn
          color="warning"
          text
          @click="close"
        >
          Cancel
        </v-btn>
        <v-btn v-if="!id && (entrySequence || entryPoint)"
          color="info"
          text
          title="Enter an event by time"
          @click="timed"
        >
        <v-icon left small>mdi-clock-outline</v-icon>
        Timed
        </v-btn>
        <v-spacer></v-spacer>
        <v-btn
          :disabled="!canSave"
          :loading="loading"
          color="primary"
          text
          @click="save"
        >
          Save
        </v-btn>
      </v-card-actions>
    </v-card>
  </v-dialog>
</template>

<style>
/* https://github.com/vuetifyjs/vuetify/issues/471 */
.v-dialog {
    overflow-y: initial;
}
</style>

<script>
import { mapActions } from 'vuex';
import DougalContextMenu from '@/components/context-menu';
import DougalEventSelect from '@/components/event-select';

function stringSort (a, b) {
  return a == b
    ? 0
    : a < b
      ? -1
      : +1;
}


function flattenRemarks(items, keywords=[], labels=[]) {
    const result = [];

    if (items) {
      for (const item of items) {
          if (!item.items) {
              result.push({
                  text: item.text,
                  properties: item.properties,
                  labels: labels.concat(item.labels??[]),
                  keywords
              })
          } else {
              const k = [...keywords, item.text];
              const l = [...labels, ...(item.labels??[])];
              result.push(...flattenRemarks(item.items, k, l))
          }
      }
    }
    return result;
}

/** Compare two arrays
 *
 * @a a First array
 * @a b Second array
 * @a cbB Callback to transform elements of `b`
 *
 * @return true if the sets are distinct, false otherwise
 *
 * Note that this will not work with object or other complex
 * elements unless the array members are the same object (as
 * opposed to merely identical).
 */
function distinctSets(a, b, cbB = (i) => i) {
  return !a.every(i => b.map(cbB).includes(i)) ||
    !b.map(cbB).every(i => a.find(j => j==i));
}

export default {
  name: 'DougalEventEdit',

  components: {
    DougalContextMenu,
    DougalEventSelect
  },

  props: {
    value: { default: false },
    availableLabels: { type: Object, default: () => ({}) },
    presetRemarks: { type: Array, default: () => [] },
    id: { type: Number },
    tstamp: { type: String },
    sequence: { type: Number },
    point: { type: Number },
    remarks: { type: String },
    meta: { type: Object },
    labels: { type: Array, default: () => [] },
    latitude: { type: Number },
    longitude: { type: Number },
    loading: { type: Boolean, default: false }
  },

  data: () => ({
    dateMenu: false,
    timeMenu: false,
    remarksMenu: false,
    search: '',
    entryLabels: [],
    tsDate: null,
    tsTime: null,
    entrySequence: null,
    entryPoint: null,
    entryRemarks: null,
    entryLatitude: null,
    entryLongitude: null
  }),

  computed: {

    allSelected () {
      return this.entryLabels.length === this.items.length
    },

    dirty () {
      // Selected remark distinct from input remark
      if (this.entryRemarksText != this.remarks) {
        return true;
      }

      // Selected label set distinct from input labels
      if (distinctSets(this.selectedLabels, this.entryLabels, (i) => i.text)) {
        return true;
      }

      // Selected seqpoint distinct from input seqpoint (if seqpoint present)
      if ((this.entrySequence || this.entryPoint)) {
        if (this.entrySequence != this.sequence || this.entryPoint != this.point) {
          return true;
        }
      } else {
        // Selected timestamp distinct from input timestamp (if no seqpoint)
        const epoch = Date.parse(this.tstamp);
        const entryEpoch = Date.parse(`${this.tsDate} ${this.tsTime}Z`);
        // Ignore difference of less than one second
        if (Math.abs(entryEpoch - epoch) > 1000) {
          return true;
        }
      }

      return false;
    },

    canSave () {
      // There is either tstamp or seqpoint, latter wins
      if (!(this.entrySequence && this.entryPoint) && !this.entryTstamp) {
        return false;
      }

      // There are remarks and/or labels
      if (!this.entryRemarksText && !this.entryLabels.length) {
        return false;
      }

      // Form is dirty
      if (!this.dirty) {
        return false;
      }

      return true;
    },

    categories () {
      const search = this.search.toLowerCase()

      if (!search) return this.items

      return this.items.filter(item => {
        const text = item.text.toLowerCase();
        const title = item.title.toLowerCase();

        return text.includes(search) || title.includes(search);
      }).sort( (a, b) => stringSort(a.text, b.text) )
    },

    items () {
      return Object.keys(this.availableLabels).map(this.labelToItem);
    },

    selectedLabels () {
      return this.event?.labels ?? [];
    },

    entryTstamp () {
      const ts = new Date(Date.parse(`${this.tsDate} ${this.tsTime}Z`));
      if (isNaN(ts)) {
        return null;
      }

      return ts.toISOString();
    },

    entryRemarksText () {
      return typeof this.entryRemarks === 'string'
        ? this.entryRemarks
        : this.entryRemarks?.text;
    }
  },

  watch: {
    value () {
      if (this.value) {
        // Populate fields from properties
        if (!this.tstamp && !this.sequence && !this.point) {
          const ts = (new Date()).toISOString();
          this.tsDate = ts.substr(0, 10);
          this.tsTime = ts.substr(11, 8);
        } else if (this.tstamp) {
          this.tsDate = this.tstamp.substr(0, 10);
          this.tsTime = this.tstamp.substr(11, 8);
        }

        // NOTE Dead code
        if (this.meta?.geometry?.type == "Point") {
          this.entryLongitude = this.meta.geometry.coordinates[0];
          this.entryLatitude = this.meta.geometry.coordinates[1];
        }

        this.entryLatitude = this.latitude;
        this.entryLongitude = this.longitude;

        this.entrySequence = this.sequence;
        this.entryPoint = this.point;
        this.entryLabels = [...(this.labels??[])];
        this.makeEntryRemarks();
      }
    },

    tstamp () {
      if (this.tstamp) {
        this.tsDate = this.tstamp.substr(0, 10);
        this.tsTime = this.tstamp.substr(11, 8);
      } else if (this.sequence || this.point) {
        this.tsDate = null;
        this.tsTime = null;
      } else {
        const ts = (new Date()).toISOString();
        this.tsDate = ts.substr(0, 10);
        this.tsTime = ts.substr(11, 8);
      }
    },

    sequence () {
      if (this.sequence && !this.tstamp) {
        this.tsDate = null;
        this.tsTime = null;
      }
    },

    point () {
      if (this.point && !this.tstamp) {
        this.tsDate = null;
        this.tsTime = null;
      }
    },

    entryTstamp (n, o) {
      //this.updateAncillaryData();
    },

    entrySequence (n, o) {
      //this.updateAncillaryData();
    },

    entryPoint (n, o) {
      //this.updateAncillaryData();
    },

    entryRemarks () {
      if (this.entryRemarks?.labels) {
        this.entryLabels = [...this.entryRemarks.labels];
      } else if (!this.entryRemarks) {
        this.entryLabels = [];
      }
    },

    selectedLabels () {
      this.entryLabels = this.selectedLabels.map(this.labelToItem)
    },

    entryLabels () {
      this.search = '';
    },
  },

  methods: {
    labelToItem (k) {
      return {
        text: k,
        icon: this.availableLabels[k].view?.icon,
        colour: this.availableLabels[k].view?.colour,
        title: this.availableLabels[k].view?.description
      };
    },

    makeEntryRemarks () {
      this.entryRemarks = {
        template: null,
        schema: {},
        values: [],
        ...this.meta?.structured_values,
        text: this.remarks
      }
    },

    async getPointData () {
      const url = `/project/${this.$route.params.project}/sequence/${this.entrySequence}/${this.entryPoint}`;
      return await this.api([url]);
    },

    async getTstampData () {
      const url = `/navdata?q=tstamp:${this.entryTstamp}&tolerance:2500`;
      return await this.api([url]);
    },

    async updateAncillaryData () {
      if (this.entrySequence && this.entryPoint) {
        // Fetch data for this sequence / point
        const data = await this.getPointData();

        if (data?.tstamp) {
          this.tsDate = data.tstamp.substr(0, 10);
          this.tsTime = data.tstamp.substr(11, 8);
        }

        if (data?.geometry) {
          this.entryLongitude = (data?.geometry?.coordinates??[])[0];
          this.entryLatitude = (data?.geometry?.coordinates??[])[1];
        }
      } else if (!this.entrySequence && !this.entryPoint && this.entryTstamp) {
        // Fetch data for this timestamp
        const data = ((await this.getTstampData())??[])[0];
        console.log("TS DATA", data);
        if (data?._sequence && data?.shot) {
          this.entrySequence = Number(data._sequence);
          this.entryPoint = data.shot;
        }

        if (data?.tstamp) {
          this.tsDate = data.tstamp.substr(0, 10);
          this.tsTime = data.tstamp.substr(11, 8);
        }

        if (data?.longitude && data?.latitude) {
          this.entryLongitude = data.longitude;
          this.entryLatitude = data.latitude;
        }
      }
    },

    timed () {
      const tstamp = (new Date()).toISOString();
      this.entrySequence = null;
      this.entryPoint = null;
      this.tsDate = tstamp.substr(0, 10);
      this.tsTime = tstamp.substr(11, 8);
    },

    close () {
      this.entryLabels = this.selectedLabels.map(this.labelToItem)
      this.$emit("input", false);
    },

    save () {
      // In case the focus goes directly from the remarks field
      // to the Save button.

      let meta;

      if (this.entryRemarks.values?.length) {
        meta = {
          structured_values: {
            template: this.entryRemarks.template,
            schema: this.entryRemarks.schema,
            values: this.entryRemarks.values
          }
        };
      }

      const data = {
        id: this.id,
        remarks: this.entryRemarksText,
        labels: this.entryLabels,
        meta
      };

      /* NOTE This is the purist way.
       * Where we expect that the server will match
       * timestamps with shotpoints and so on
       *
      if (this.entrySequence && this.entryPoint) {
        data.sequence = this.entrySequence;
        data.point = this.entryPoint;
      } else {
        data.tstamp = this.entryTstamp;
      }
      */

      /* NOTE And this is the pragmatic way.
       */
      data.tstamp = this.entryTstamp;
      if (this.entrySequence && this.entryPoint) {
        data.sequence = this.entrySequence;
        data.point = this.entryPoint;
      }

      this.$emit("changed", data);
      this.$emit("input", false);
    },

    ...mapActions(["api"])
  },
}
</script>
