<template>
  <div>
    <portal to="top-header-title">{{capitalizeModuleName}} Editor</portal>
    <portal to="top-header-right">
      <div class="page-header-buttons d-flex align-items-center">
        <references ref="referenceDialog" v-if="item && !isNew" :item-id="item.id" :item-name="capitalizeModuleName"></references>
        <documentation-editor v-if="schema && schema.properties" :schema="schema"></documentation-editor>
      </div>
    </portal>

    <validator-group class="flex-grow-1">
      <div class="page-container d-flex flex-row">
        <loader class="flex-grow-1" :is-loading="isLoading">
          <div class="card flex-grow-1 crud-editor">
            <editor :item="item" :schema="schema"/>
          </div>
        </loader>
        <editor-sidebar class="crud-editor-sidebar">
          <editor-sidebar-item>
            <submit class="btn-block" :action="saveItem" :disabled="!canUpdate">
              Opslaan
            </submit>
          </editor-sidebar-item>
          <editor-sidebar-item>
            <button class="btn btn-block btn-light mr-2" v-if="!isNew && !isSingleton && canUpdate && canCopy" @click="copyItem">
              Kopiëren
            </button>
            <button type="button" class="btn btn-block btn-light mr-2" @click="cancelItemChanges">
              Annuleren
            </button>
          </editor-sidebar-item>
          <multi-language-only v-if="item && !isNew">
            <editor-sidebar-item>
              <crud-editor-translations ref="crudEditorTranslations" :item-id="item.id" :language-ref="item.languageRef" :is-singleton="isSingleton" :module="module" :name="capitalizeModuleName" />
            </editor-sidebar-item>
          </multi-language-only>
          <editor-sidebar-item v-if="item && (item.createdBy || item.modifiedBy)">
            <ul class="list-group list-group-flush">
              <li class="list-group-item">
                <edited-data v-if="item.createdBy" label="Aangemaakt" :date="createdDate" :username="item.createdBy" />
              </li>
              <li class="list-group-item">
                <edited-data v-if="item.modifiedBy" label="Gewijzigd" :date="modifiedDate" :username="item.modifiedBy" />
              </li>
            </ul>
          </editor-sidebar-item>
          <editor-sidebar-item v-if="!isNew && isDeletable">
            <button class="btn btn-block btn-danger mr-2" @click="deleteItem" :disabled="!canDelete">
              Verwijderen
            </button>
          </editor-sidebar-item>
        </editor-sidebar>
      </div>

      <confirm-dialog ref="confirmDialog"></confirm-dialog>
      <multi-language-only>
        <delete-crud-item-modal ref="deleteCrudItemModal" :item="item" :id="id" :module="module" :name="capitalizeModuleName" />
      </multi-language-only>
    </validator-group>
  </div>
</template>

<script lang="ts">
import { namespace } from "vuex-class"
import { Component, Prop, Ref, Vue, Watch } from 'vue-property-decorator';
import CrudStoreModel, { CrudStoreItemsModel, CrudStoreSchemaModel } from "../stores/types/CrudStoreModel";

import DocumentationEditor from '../shared/documentation-editor.vue';
import ConfirmDialog from "../shared/form/confirm-dialog.vue";
import Editor from "../shared/editor.vue";
import Loader from '../shared/loader.vue';
import References from "../shared/references.vue";
import Submit from "../shared/form/submit.vue";
import ValidatorGroup from "../shared/form/validator-group.vue";
import CrudHelpers from "./crud-helpers"
import EditorSidebar from '../shared/editor-sidebar.vue';
import EditorSidebarItem from '../shared/editor-sidebar-item.vue';
import DateUtils from '../tools/date-utils';
import EditedData from '../shared/edited-data.vue';
import { Route } from 'vue-router'
import { RawLocation } from 'vue-router/types/router'

// Multi-language
import MultiLanguageOnly from '../shared/language/multi-language-only.vue';
import CrudEditorTranslations from '../shared/language/crud-editor-translations.vue';
import DeleteCrudItemModal from './delete-crud-item-modal.vue';
import LanguageApi from '../api/language-api';
import { ItemTranslation, SupportedLanguage } from '../types/language';
import RouteUtils, { createRoute, ROUTES } from '../tools/route-utils';

const AlertStore = namespace('alerts');
const PermissionStore = namespace('permissions');
const LanguageStore = namespace('languages');

Component.registerHooks([
  'beforeRouteUpdate',
  'beforeRouteLeave'
]);

@Component({
  components: {
    EditorSidebar,
    EditorSidebarItem,
    EditedData,
    ConfirmDialog,
    DocumentationEditor,
    Editor,
    Loader,
    References,
    Submit,
    ValidatorGroup,
    // Multi-language
    MultiLanguageOnly,
    CrudEditorTranslations,
    DeleteCrudItemModal
  }
})
export default class CrudEditor<T extends CrudStoreModel> extends Vue {
  @Prop(String) readonly id!: string;
  @Prop(String) readonly module!: string;
  @Prop(Boolean) readonly singleton!: boolean;
  @Prop(Boolean) readonly copied!: boolean;
  @Prop(Object) crudStore!: T;
  @Prop(Object) crudItem?: CrudStoreItemsModel;
  @Prop(Object) query!: { [k: string]: string };

  item = null as CrudStoreItemsModel | null | undefined;
  unsubscribe: (() => void) | null = null;
  loading = false;

  @Ref() readonly referenceDialog!: References;
  @Ref() readonly confirmDialog!: ConfirmDialog;
  @Ref() readonly deleteCrudItemModal!: DeleteCrudItemModal;
  @Ref() readonly crudEditorTranslations!: CrudEditorTranslations;

  @AlertStore.Action('addError') readonly alertError!: Function;
  @AlertStore.Action('addSuccess') readonly alertSuccess!: Function;
  @PermissionStore.Getter('canUpdate') readonly canUpdatePermission!: Function;
  @PermissionStore.Getter('canDelete') readonly canDeletePermission!: Function;
  @LanguageStore.State('useMultiLanguage') readonly useMultiLanguage!: boolean;
  @LanguageStore.State('default') defaultLanguage!: SupportedLanguage | null;
  @LanguageStore.State('selected') selectedLanguage!: SupportedLanguage;

  beforeRouteUpdate(to: Route, from: Route, next: (to?: RawLocation | false) => void) {
    this.crudEditorTranslations?.handleItemTranslationSwitch(to, from, next);
  }

  async beforeRouteLeave(to: Route, from: Route, next: (to?: RawLocation | false) => void) {
    if (this.isNew && !to.params.redirected) {
      try {
        //@ts-ignore This dialog is not written in Typescript yet, so errors occur
        await this.confirmDialog.confirm(
          'Het item is nog niet opgeslagen',
          'Weet je zeker dat je de pagina wil verlaten?',
          'Ja, verlaten',
          'Nee, blijven'
        );
        next();
      } catch (_) {
        // This empty catch is for handling the promise rejection on user cancellation
        next(false);
        return;
      }
    }

    next();
  }

  async beforeMount() {
    this.loading = true;
    this.unsubscribe = await CrudHelpers.subscribeCrudData(this.module);

    if (this.useMultiLanguage) {
      await this.initializeMultiLanguage();
    }

    this.loading = false;
  }

  beforeDestroy() {
    this.unsubscribe?.();
  }

  async initializeMultiLanguage() {
    const translations = await LanguageApi.getItemTranslations(this.module, this.item?.languageRef || undefined) as ItemTranslation[];
    const existingTranslations = translations.filter(translation => translation.itemId);
    const translateFrom = existingTranslations.find(language => language.language === this.selectedLanguage.key)

    if (existingTranslations.length > 0 && !translateFrom && !this.item?.languageRef) {
      await RouteUtils.push(createRoute(ROUTES.CRUD_EMPTY_TRANSLATION(this.module), null, {
        query: { itemId: existingTranslations[0].itemId, languageRef: null, itemName: this.capitalizeModuleName }
      }));
    }
  }

  async getItemById(id: string, module: string): Promise<CrudStoreItemsModel | null> {
    try {
      return await this.$store.dispatch(`${module}/getItemById`, id);
    } catch (err) {
      this.alertError(`Er ging iets fout bij het openen van ${module}: ${err.message}`);
    }

    return null;
  }

  getSingletonItem(items: CrudStoreItemsModel[]): CrudStoreItemsModel | undefined {
    if (items.length <= 0 && this.schema) {
      return CrudHelpers.initialValues(this.schema, this.query);
    }

    return items[0];
  }

  createItemFromQuery(query: { [k: string]: string }): CrudStoreItemsModel {
    const item: CrudStoreItemsModel = {
      $type: this.crudItemType
    };

    for (const [property, value] of Object.entries(query)) {
      if (!this.crudSchemaPropertyNames.includes(property)) {
        continue;
      }

      item[property] = value;
    }

    return item as CrudStoreItemsModel;
  }

  async saveItem(): Promise<void> {
    try {
      // Prevent id isNew check from losing its integrity after a save (which will set the id and therefore not work later in the code)
      const isNew = this.isNew;

      this.item = await this.$store.dispatch(`${this.module}/saveItem`, {
        id: this.id,
        item: this.item,
      }) as CrudStoreItemsModel;

      if (isNew) {
        await this.navigateToCreatedItem(this.item);
      }

      this.alertSuccess('Opgeslagen');
    } catch (err) {
      this.alertError(`Opslaan is niet gelukt! <br> ${err.message}`);

      throw err;
    }
  }

  copyItem() {
    try {
      if (!this.item) {
        throw new Error(`De gegevens voor deze ${this.module} zijn (nog) niet beschikbaar.`)
      }

      // Delete the item ID so we start fresh from the new view
      this.item.id = undefined;

      this.navigateToCopiedItem(this.item);
      this.alertSuccess('Kopie gemaakt');
    } catch (err) {
      this.alertError(`Kopie maken is niet gelukt! <br> ${err.message}`);

      throw err;
    }

  }

  async deleteItem() {
    if (this.useMultiLanguage) {
      this.deleteCrudItemModal.open();
    } else {
      try {
        try {
          //@ts-ignore This dialog is not written in Typescript yet, so errors occur
          await this.confirmDialog.confirm(
            `${this.capitalizeModuleName} verwijderen`,
            `Weet je zeker dat je deze ${this.capitalizeModuleName} wilt verwijderen?`,
          );
        } catch {
          // This empty catch is for handling the promise rejection on user cancellation
          return;
        }

        await this.$store.dispatch(`${this.module}/deleteItem`, {
          id: this.id,
          item: this.item,
        });

        await this.navigateToItemOverview();
        this.alertSuccess('Verwijderd');
      } catch (err) {
        if (err.isReferenceError) {
          // @ts-ignore This modal is not written in Typescript yet, so errors occur
          this.referenceDialog.openErrorModal();
        }

        this.alertError(`Verwijderen is niet gelukt! <br> ${err.message}`);

        throw err;
      }
    }
  }

  reloadItem() {
    // Fetch the CrudItems from the server to replace the current ones
    this.$store.dispatch(`${this.module}/fetchCrudItems`);
  }

  cancelItemChanges() {
    if (this.isSingleton) {
      this.reloadItem();
      return;
    }

    this.navigateToItemOverview();
  }

  async navigateToCreatedItem(item: CrudStoreItemsModel) {
    await RouteUtils.replace(createRoute(ROUTES.CRUD_EDITOR(this.module), { id: item.id as string }, {
      query: this.$route.query
    }))
  }

  async navigateToCopiedItem(item: CrudStoreItemsModel) {
    await RouteUtils.replace(createRoute(ROUTES.CRUD_COPY(this.module), { item: item as any }, {
      query: this.$route.query
    }));
  }

  async navigateToItemOverview() {
    await RouteUtils.push(createRoute(ROUTES.CRUD_INDEX(this.module), null, { query: this.$route.query }));
  }

  get createdDate(): string | null {
    return this.item ? DateUtils.formatDateTimeString(this.item.created) : null;
  }

  get modifiedDate(): string | null {
    return this.item ? DateUtils.formatDateTimeString(this.item.modified) : null;
  }

  get schema(): CrudStoreSchemaModel | undefined {
    return this.crudStore.schema;
  }

  get isNew(): boolean {
    return !this.id && !this.item?.id;
  }

  get capitalizeModuleName(): string {
    if (!this.crudStore.schema) {
      return '';
    }

    return this.crudStore.schema.title;
  }

  get crudSchemaPropertyNames(): string[] {
    if (!this.crudStore.schema) {
      return [];
    }

    return Object.keys(this.crudStore.schema.properties);
  }

  get crudItemType(): string {
    if (!this.crudStore.schema) {
      return '';
    }

    return this.crudStore.schema.properties.$type.enum[0];
  }

  get isLoading(): boolean {
    return this.loading || this.crudStore.loading || !this.crudStore.schema || !this.crudStore.overview || !this.item;
  }

  get isSingleton(): boolean {
    if (!this.crudStore.schema) {
      return true;
    }

    return !!this.crudStore.schema.isSingleton;
  }

  get canCopy(): boolean {
    if (!this.crudStore.schema) {
      return false;
    }

    return !this.crudStore.schema.preventCopy;
  }

  get canUpdate(): boolean {
    if (!this.crudStore.schema) {
      return false;
    }

    return this.canUpdatePermission(this.module);
  }

  get isDeletable(): boolean {
    if (!this.crudStore.schema) {
      return false;
    }

    return !this.crudStore.schema.preventDelete;
  }

  get canDelete(): boolean {
    if (!this.crudStore.schema) {
      return false;
    }

    return this.canDeletePermission(this.module);
  }

  @Watch('schema', { immediate: true, deep: true })
  async onSchemaChanged(schema: CrudStoreSchemaModel) {
    if (this.id) {
      this.item = await this.getItemById(this.id, this.module);
      return;
    }

    if (schema) {
      this.item = CrudHelpers.initialValues(schema, this.query);
    }
  }

  @Watch('crudItem', { immediate: true, deep: true })
  onCrudItemChanged(crudItem: CrudStoreItemsModel | undefined) {
    if (!crudItem) {
      return;
    }

    this.item = crudItem;
  }

  @Watch('crudStore', { immediate: true, deep: true })
  onCrudStoreChanged(crudStore: T | null) {
    if (!this.singleton || !crudStore) {
      return;
    }

    this.item = this.getSingletonItem(crudStore.items);
  }
}
</script>

<style lang="scss" scoped>
  .crud-editor {
    border-radius: 0.25rem 0 0 0.25rem;
    border-right: 0;
  }
  .crud-editor-sidebar {
    border-radius: 0 0.25rem 0.25rem 0;
  }
</style>
