<template>
  <loader>
    <header class="page-header pt-4 pb-3">
      <h1>
        {{ capitalizeModuleName }} <span v-if="isReadOnly" class="crud-editor-readonly-title">[ Alleen lezen ]</span
        ><span v-else>Editor</span>
      </h1>

      <div class="page-header-buttons d-flex">
        <div>
          <span v-for="(button, index) in headerButtons" :key="index">
            <a
              v-if="!button.component"
              :class="button.classList"
              :href="button.href"
              :target="button.target || '_blank'"
              v-html="button.label"
              :item="item"
            />
            <component v-if="button.component" :is="button.component" :item="item" />
          </span>
        </div>

        <translation-buttons
          v-if="item && !isNew && canTranslate && showLanguageElements"
          :itemId="itemId"
          :translations="translations"
          @goToTranslation="goToItem"
          @translate="translateItem"
        ></translation-buttons>
        <publish-buttons
          v-if="item && !isNew && showPublishElements"
          :canPublish="canPublish"
          :isPublished="isPublished"
          :itemId="itemId"
          @publish="publishItem"
          @depublish="depublishItem"
        ></publish-buttons>
        <references ref="referenceDialog" v-if="item && !isNew" :item-id="item.id" :item-name="capitalizeModuleName"></references>
      </div>
    </header>

    <validator-group>
      <div class="container">
        <div class="card">
          <div class="card-body">
            <editor :item="item" :schema="crudStore.schema">
              <field label="Taal" v-if="canTranslate && showLanguageElements">
                <language-picker
                  v-if="item"
                  v-model="item.language"
                  :id="itemId"
                  :translations="translations"
                  :languageInfos="languageInfos"
                />
              </field>
            </editor>
          </div>
        </div>
      </div>

      <bottom-toolbar>
        <button class="btn btn-danger mr-2" v-on:click="deleteItem" v-if="!isNew && canDelete" :disabled="!canDelete">
          <span class="icon-cross"></span> Verwijderen
        </button>

        <span v-for="(button, index) in footerButtons" :key="index">
          <a
            v-if="!button.component"
            :key="index"
            :class="button.classList"
            :href="button.href"
            :target="button.target || '_blank'"
            v-html="button.label"
            :item="item"
          />
          <component v-if="button.component" :is="button.component" :item="item" />
        </span>

        <button slot="right" type="button" class="btn btn-secondary-outline mr-2" v-on:click="cancelItemChanges">Annuleren</button>

        <button class="btn btn-secondary mr-2" slot="right" v-if="!isNew && !isSingleton && canUpdate && canCopy" v-on:click="copyItem">
          <span class="icon-copy"></span> Kopieer {{ capitalizeModuleName }}
        </button>

        <submit
          v-if="(isNew && canCreate) || (!isNew && canUpdate)"
          slot="right"
          :action="saveItem"
          :disabled="(!canUpdate && !canCreate) || isSavingItem"
          :dropdownItems="
            !isSingleton && canCreate
              ? [
                  {
                    label: `Opslaan en nieuw ${capitalizeModuleName} aanmaken`,
                    action: saveItemAndCreateNewItem
                  }
                ]
              : undefined
          "
        >
          <span v-if="isSavingItem" class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
          <span v-else class="icon-floppy-disk"></span>
          Opslaan
        </submit>
      </bottom-toolbar>

      <confirm-dialog ref="confirmDialog"></confirm-dialog>
    </validator-group>
  </loader>
</template>

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

import BottomToolbar from '../shared/form/bottom-toolbar.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 Field from '../shared/form/field.vue';
import LanguagePicker from '../shared/form/language-picker.vue';
import ValidatorGroup from '../shared/form/validator-group.vue';
import CrudHelpers from './crud-helpers';
import TranslationButtons from '../shared/translation-buttons.vue';
import PublishButtons from '../shared/publish-buttons.vue';
import { LanguageInfo, Translation } from '../stores/types/TranslationStoreModel';
import CrudApi from '../api/crud-api';
import { getCmsSettings } from '../tools/settings';
import { PermissionTypes } from '../stores/types/PermissionStoreModel';
import { CustomCrudEditorButtons, ToolbarItem } from '../types/settings';

const AlertStore = namespace('alerts');
const PermissionStore = namespace('permissions');
const TranslationStore = namespace('translations');

@Component({
  components: {
    BottomToolbar,
    TranslationButtons,
    PublishButtons,
    ConfirmDialog,
    Editor,
    LanguagePicker,
    Loader,
    Field,
    References,
    Submit,
    ValidatorGroup
  }
})
export default class CrudEditor<T extends CrudStoreModel> extends Vue {
  item = null as CrudStoreItemsModel | null | undefined;
  translations = [] as Translation[];
  customCrudEditorButtons: CustomCrudEditorButtons | undefined = getCmsSettings().customCrudEditorButtons;

  @State((state) => state.translations.languageInfos)
  readonly languageInfos!: LanguageInfo[];

  @State((state) => state.translations.isLoadingLanguageInfos)
  readonly isLoadingLanguageInfos!: boolean;

  @Prop(String) readonly id!: string;
  @Prop(String) readonly languageFromRoute!: string;
  @Prop(String) readonly module!: string;
  @Prop(Boolean) readonly copied!: boolean;
  @Prop(Object) crudStore!: T;
  @Prop(Object) crudItem?: CrudStoreItemsModel;
  @Prop(Object) query!: { [k: string]: string };

  @Ref('referenceDialog') readonly referenceDialog!: typeof References;
  @Ref('confirmDialog') readonly confirmDialog!: ConfirmDialog;

  @AlertStore.Action('addCmsError') readonly addCmsError!: Function;
  @AlertStore.Action('addSuccess') readonly addSuccess!: Function;
  @PermissionStore.Getter('canUpdate') readonly canUpdatePermission!: Function;
  @PermissionStore.Getter('canDelete') readonly canDeletePermission!: Function;
  @PermissionStore.Getter('canCreate') readonly canCreatePermission!: Function;

  @TranslationStore.Getter('defaultLanguage') readonly defaultLanguage!: string;

  isSavingItem = false;

  @Watch('schema', { immediate: true, deep: true })
  async onSchemaLoaded(schema: CrudStoreSchemaModel) {
    if (!schema) {
      return;
    }

    // singleton items need the defaultLanguage, make sure that it's loaded (to avoid a race condition)
    if (this.isLoadingLanguageInfos) {
      setTimeout(() => {
        this.onSchemaLoaded(schema);
      }, 100);
      return;
    }

    if (this.id) {
      this.item = await this.getItemById(this.id, this.module);
      this.translations = await CrudApi.getItemTranslations(this.module, this.id);
      return;
    }

    if (this.isSingleton) {
      this.item = await this.getSingletonItem();

      if (this.item) {
        this.goToItem(this.item.id);
        return;
      }
    }

    // no item present in the database yet, create a new item
    const query = { ...this.query, language: this.defaultLanguage };
    this.item = CrudHelpers.initialValues(schema, query);
  }

  @Watch('id', { immediate: true, deep: true })
  async onIdChanged(id: string) {
    if (id) {
      this.item = await this.getItemById(id, this.module);
      this.translations = await CrudApi.getItemTranslations(this.module, this.id);
    }
  }

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

    this.item = crudItem;
  }

  async getItemById(id: string, module: string): Promise<CrudStoreItemsModel | null> {
    try {
      const payload: GetItemByIdPayload = { id, ignoreCache: true };
      return await this.$store.dispatch(`${module}/getItemById`, payload);
    } catch (err) {
      this.addCmsError(err);
    }

    return null;
  }

  async getSingletonItem(): Promise<CrudStoreItemsModel | null> {
    const payload: GetSingletonItemPayload = {
      defaultLanguage: this.defaultLanguage,
      id: this.id
    };

    try {
      return await this.$store.dispatch(`${this.module}/getSingletonItem`, payload);
    } catch (err) {
      this.addCmsError(err);
    }

    return null;
  }

  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(navigateToNewItem = false): Promise<void> {
    this.isSavingItem = true;

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

      this.navigateToCreatedItem(this.item);

      if (navigateToNewItem) {
        await this.$nextTick();
        this.navigateToNewCreatedItem();
      }

      this.addSuccess('Opgeslagen');
    } catch (err) {
      this.addCmsError(err);
      throw err;
    } finally {
      this.isSavingItem = false;
    }
  }

  async saveItemAndCreateNewItem(): Promise<void> {
    await this.saveItem(true);
  }

  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.item.translationSetId = undefined;

      this.resetNotCopyableProperties();

      this.navigateToCopiedItem(this.item);
      this.addSuccess('Kopie gemaakt');
    } catch (err) {
      this.addCmsError(err);
      throw err;
    }
  }

  translateItem(languageInfo: LanguageInfo) {
    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.item.language = languageInfo.language;

      this.resetNotCopyableProperties();

      this.navigateToTranslatedItem(this.item);
      this.addSuccess('Vertaling gemaakt');
    } catch (err) {
      this.addCmsError(err);
      throw err;
    }
  }

  resetNotCopyableProperties() {
    if (!this.schema || !this.item) {
      return;
    }

    for (const [key] of Object.entries(this.schema.properties)) {
      if (this.schema.properties[key].isCopyable === false) {
        // This property is not copyable
        // Todo: respect defaultValue attribute on properties
        this.item[key] = undefined;
      }
    }
  }

  async publishItem(publish = true) {
    try {
      if (!this.item) {
        throw new Error(`De gegevens voor deze ${this.module} zijn (nog) niet beschikbaar.`);
      }

      this.item = await this.$store.dispatch(`${this.module}/publishItem`, {
        id: this.id,
        item: this.item,
        action: publish ? 'publish' : 'depublish'
      });
    } catch (err) {
      this.addCmsError(err);

      throw err;
    }
  }

  depublishItem() {
    this.publishItem(false);
  }

  async deleteItem() {
    try {
      try {
        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
      });

      this.navigateToItemOverview();
      this.addSuccess('Verwijderd');
    } catch (err) {
      if (err.isReferenceError) {
        this.referenceDialog.openErrorModal();
      }

      this.addCmsError(err);

      throw err;
    }
  }

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

  goToItem(id: string) {
    this.$router.replace({
      name: `${this.module}-editor`,
      params: {
        id
      },
      query: this.$route.query
    });
  }

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

    this.navigateToItemOverview();
  }

  navigateToCreatedItem(item: CrudStoreItemsModel) {
    this.$router.replace({
      name: `${this.module}-editor`,
      params: {
        id: item.id as string
      },
      query: this.$route.query
    });
  }

  navigateToCopiedItem(item: CrudStoreItemsModel) {
    this.$router.push({
      name: `copy-${this.module}`,
      params: {
        item: item as any
      },
      query: this.$route.query
    });
  }

  navigateToTranslatedItem(item: CrudStoreItemsModel) {
    this.$router.push({
      name: `translate-${this.module}`,
      params: {
        item: item as any
      },
      query: this.$route.query
    });
  }

  navigateToItemOverview() {
    this.$router.push({
      name: `index-${this.module}`,
      query: this.$route.query
    });
  }

  navigateToNewCreatedItem() {
    this.$router.push({
      name: `create-${this.module}`,
      query: this.$route.query
    });
  }

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

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

  get isReadOnly(): boolean {
    return !!(this.item && PermissionTypes[String(this.item.userEntityPermission)] === PermissionTypes.Read);
  }

  get isPublished(): boolean {
    return this.item && this.item.isPublished;
  }

  get itemId(): boolean {
    return this.item && this.item.id;
  }

  get customButtons() {
    return this.customCrudEditorButtons && this.customCrudEditorButtons[this.module];
  }

  get headerButtons(): ToolbarItem[] {
    return (this.customButtons && this.customButtons.header) || [];
  }

  get footerButtons(): ToolbarItem[] {
    return (this.customButtons && this.customButtons.footer) || [];
  }

  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.crudStore.loading || this.isLoadingLanguageInfos || !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, this.item);
  }

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

    return this.canCreatePermission(this.module);
  }

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

    return !this.crudStore.schema.preventDelete && this.canDeletePermission(this.module, this.item);
  }

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

    return this.crudStore.schema.isTranslatable || false;
  }

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

    return (this.crudStore.schema.isPublishable && this.canUpdatePermission(this.module)) || false;
  }

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

    return this.crudStore.schema.isPublishable || false;
  }

  get showLanguageElements(): boolean {
    return this.languageInfos.length > 1;
  }
}
</script>
