<template>
  <q-card>
    <q-form @submit="save()" @reset="reset">
      <q-card-section class="fit row justify-start content-center items-center">
        <div class="text-h6 col-xs-12 col-sm-6 q-pa-sm">
          Veranstaltung <template v-if="modelValue">bearbeiten</template
          ><template v-else>erstellen</template>
        </div>
        <q-select
          :model-value="template"
          filled
          label="Vorlage"
          class="col-xs-12 col-sm-6 q-pa-sm"
          :options="templates"
          option-label="name"
          map-options
          clearable
          :disable="templates.length == 0"
          @update:model-value="fromTemplate"
          @clear="reset()"
        />
        <q-input
          v-model="event.name"
          class="col-xs-12 col-sm-6 q-pa-sm"
          label="Name"
          type="text"
          filled
        />
        <q-select
          v-model="event.type"
          filled
          use-input
          label="Veranstaltungstyp"
          input-debounce="0"
          class="col-xs-12 col-sm-6 q-pa-sm"
          :options="eventtypes"
          option-label="name"
          option-value="id"
          emit-value
          map-options
          clearable
          :rules="[notEmpty]"
        />
        <IsoDateInput
          v-model="event.start"
          class="col-xs-12 col-sm-6 q-pa-sm"
          label="Veranstaltungsbeginn"
          :rules="[notEmpty]"
        />
        <IsoDateInput
          v-model="event.end"
          class="col-xs-12 col-sm-6 q-pa-sm"
          label="Veranstaltungsende"
          :rules="[afterStart]"
          :key="update_time"
        />
        <q-input
          v-model="event.description"
          class="col-12 q-pa-sm"
          label="Beschreibung"
          type="textarea"
          filled
        />
      </q-card-section>
      <q-card-section v-if="modelValue === undefined">
        <q-btn-toggle
          v-model="recurrent"
          spread
          no-caps
          :options="[
            { label: 'Einmalig', value: false },
            { label: 'Wiederkehrend', value: true },
          ]"
        />
        <RecurrenceRule v-if="!!recurrent" v-model="recurrenceRule" />
      </q-card-section>
      <q-separator />
      <q-card-section>
        <div class="row justify-around q-mb-sm" align="around">
          <div class="text-h6 text-center col-6">Schichten</div>
          <div class="col-6 text-center">
            <q-btn color="primary" label="Schicht hinzufügen" @click="addJob()" />
          </div>
        </div>
        <template v-for="(job, index) in event.jobs" :key="index + update_time">
          <edit-job-slot
            ref="activeJob"
            v-model="event.jobs[index]"
            :active="index === active"
            class="q-mb-md"
            @remove-job="removeJob(index)"
            @activate="activate(index)"
          />
        </template>
      </q-card-section>
      <q-card-actions align="around">
        <q-card-actions align="left">
          <q-btn v-if="!template" color="secondary" label="Neue Vorlage" @click="save(true)" />
          <q-btn v-else color="negative" label="Vorlage löschen" @click="removeTemplate" />
        </q-card-actions>
        <q-card-actions align="right">
          <q-btn label="Zurücksetzen" type="reset" />
          <q-btn color="primary" type="submit" label="Speichern" />
        </q-card-actions>
      </q-card-actions>
    </q-form>
  </q-card>
</template>

<script lang="ts">
import { notEmpty } from '@flaschengeist/api';
import { IsoDateInput } from '@flaschengeist/api/components';

import { useEventStore } from '../../store';
import { emptyEvent, Job, EditableEvent } from '../../store/models';

import { date, DateOptions } from 'quasar';
import { computed, defineComponent, PropType, ref, onBeforeMount, watch } from 'vue';

import EditJobSlot from './EditJobSlot.vue';
import RecurrenceRuleVue from './RecurrenceRule.vue';
import { RecurrenceRule } from 'app/events';

export default defineComponent({
  name: 'EditEvent',
  components: { IsoDateInput, EditJobSlot, RecurrenceRule: RecurrenceRuleVue },
  props: {
    modelValue: {
      required: false,
      default: () => undefined,
      type: Object as PropType<FG.Event | undefined>,
    },
    date: {
      required: false,
      default: undefined,
      type: String as PropType<string | undefined>,
    },
  },
  emits: {
    done: (val: boolean) => typeof val === 'boolean',
    'update:modelValue': (val?: FG.Event) => typeof val === 'object',
  },
  setup(props, { emit }) {
    const store = useEventStore();

    const active = ref(0);
    const activeJob = ref<{ validate: () => Promise<boolean> }[]>([]);
    const templates = computed(() => store.templates);
    const template = ref<FG.Event>();
    const event = ref<EditableEvent>(props.modelValue || emptyEvent());
    const eventtypes = computed(() => store.eventTypes);
    const recurrent = ref(false);
    const recurrenceRule = ref<RecurrenceRule>({ frequency: 'daily', interval: 1 });

    onBeforeMount(() => {
      void store.getEventTypes();
      void store.getJobTypes();
      void store.getTemplates();
    });

    watch(
      () => props.modelValue,
      (newModelValue) => {
        if (event.value?.id !== newModelValue?.id) reset();
      }
    );

    function addJob() {
      if (!activeJob.value[active.value]) {
        event.value.jobs.push(new Job());
      } else
        void activeJob.value[active.value].validate().then((success) => {
          if (success) {
            event.value.jobs.push(new Job());
            active.value = event.value.jobs.length - 1;
          }
        });
    }

    function removeJob(index: number) {
      event.value.jobs.splice(index, 1);
      if (active.value >= index) active.value--;
    }

    function fromTemplate(tpl: FG.Event) {
      const today = props.modelValue?.start || new Date();
      template.value = tpl;

      event.value = Object.assign({}, tpl, { id: undefined });
      // Adjust the start to match today
      event.value.start = date.adjustDate(event.value.start, {
        date: today.getDate(),
        month: today.getMonth() + 1, // js inconsitency between getDate (1-31) and getMonth (0-11)
        year: today.getFullYear(),
      });
      // Use timestamp difference for faster adjustment
      const diff = event.value.start.getTime() - tpl.start.getTime();
      // Adjust end of event and all jobs
      if (event.value.end) event.value.end.setTime(event.value.end.getTime() + diff);
      event.value.jobs = [];
      tpl.jobs.forEach((job) => {
        const copied_job: FG.Job = Object.assign({}, job, {
          id: NaN,
          start: new Date(),
          end: undefined,
        });
        copied_job.start.setTime(job.start.getTime() + diff);
        if (job.end) {
          copied_job.end = new Date();
          copied_job.end.setTime(job.end.getTime() + diff);
        }
        event.value.jobs.push(<Job>copied_job);
      });
    }

    async function save(is_template = false) {
      event.value.is_template = is_template;
      try {
        const _event = await store.addEvent(event.value);
        emit('update:modelValue', _event);

        if (props.modelValue === undefined && recurrent.value && !event.value.is_template) {
          let count = 0;
          const options: DateOptions = {};
          switch (recurrenceRule.value.frequency) {
            case 'daily':
              options['days'] = 1 * recurrenceRule.value.interval;
              break;
            case 'weekly':
              options['days'] = 7 * recurrenceRule.value.interval;
              break;
            case 'monthly':
              options['months'] = 1 * recurrenceRule.value.interval;
              break;
          }
          while (true) {
            event.value.start = date.addToDate(event.value.start, options);
            if (event.value.end) event.value.end = date.addToDate(event.value.end, options);
            event.value.jobs.forEach((job) => {
              job.start = date.addToDate(job.start, options);
              if (job.end) job.end = date.addToDate(job.end, options);
            });
            count++;
            if (
              count <= 120 &&
              (!recurrenceRule.value.count || count <= recurrenceRule.value.count) &&
              (!recurrenceRule.value.until || event.value.start < recurrenceRule.value.until)
            )
              await store.addEvent(event.value);
            else break;
          }
        }
        reset();
        emit('done', true);
      } catch (error) {
        console.error(error);
      }
    }

    async function removeTemplate() {
      if (template.value !== undefined) {
        await store.removeEvent(template.value.id);
        template.value = undefined;
      }
    }

    function reset() {
      event.value = Object.assign({}, props.modelValue || emptyEvent());
      active.value = 0;
      template.value = undefined;
    }

    const afterStart = (d: Date) =>
      !d || event.value.start <= d || 'Das Veranstaltungsende muss vor dem Beginn liegen';

    function activate(idx: number) {
      void activeJob.value[active.value]?.validate().then((s) => {
        if (s) active.value = idx;
      });
    }

    const computed_start = computed({
      get: () => event.value?.start,
      set: (value) => {
        event.value.start = value;
      },
    });
    const computed_end = computed({
      get: () => event.value?.end,
      set: (value) => {
        event.value.end = value;
      },
    });

    const update_time = ref(false);

    watch(computed_start, (newValue, oldValue) => {
      update_time.value = true;
      const diff = newValue.getTime() - oldValue.getTime();
      event.value?.jobs.forEach((job) => {
        job.start.setTime(job.start.getTime() + diff);
      });
      computed_end.value?.setTime(computed_end.value?.getTime() + diff);
      setTimeout(() => {
        update_time.value = false;
      }, 0);
    });

    watch(computed_end, (newValue, oldValue) => {
      if (newValue && oldValue) {
        update_time.value = true;
        if (!newValue || !oldValue) return;
        const diff = newValue.getTime() - oldValue.getTime();
        event.value?.jobs.forEach((job) => {
          if (job.end) job.end.setTime(job.end.getTime() + diff);
          else job.end = new Date(newValue.getTime());
        });
      } else if (newValue && !oldValue) {
        event.value?.jobs.forEach((job) => {
          if (!job.end) job.end = new Date(newValue.getTime());
        });
      }
      setTimeout(() => {
        update_time.value = false;
      }, 0);
    });

    return {
      update_time,
      activate,
      active,
      addJob,
      activeJob,
      afterStart,
      event,
      eventtypes,
      fromTemplate,
      notEmpty,
      recurrenceRule,
      recurrent,
      removeJob,
      removeTemplate,
      reset,
      save,
      template,
      templates,
    };
  },
});
</script>

<style></style>
