<template>
  <loader class="page-editor h-100" :is-loading="isLoading">
    <portal to="top-header-title" v-if="page">
      <span class="mr-2">{{ page.name }}</span>
      <span class="badge badge-secondary">{{ schema.title }}</span>
    </portal>
    <portal to="top-header-right">
      <documentation-editor v-if="schema && schema.properties" :schema="schema" />
    </portal>
    <page-bread-crumbs class="mb-3" :page="page" />
    <validator-group class="h-100">
      <page-version-editor ref="versionEditor" :version="version" :page="page" @reset="onVersionReset" :permissions="permissions">
        <template v-slot:sidebar>
          <editor-sidebar-item v-if="isPageVersion">
            <submit class="btn-block" :action="save" :disabled="!mayUpdate">
              Opslaan
            </submit>
            <button class="btn btn-light btn-block" @click="cancel" :disabled="versionEditor && !versionEditor.changed">
              Herstellen
            </button>
          </editor-sidebar-item>
          <editor-sidebar-item v-if="!isFolder(page)">
            <div class="alert alert-warning" v-if="!hasPublishedVersions">
              Deze pagina heeft geen gepubliceerde versie(s)
              <span v-if="redirects.length">en heeft een <a href="#">redirect</a>.</span>
              <span v-else>en geeft een 404.&nbsp;&nbsp;</span>
              <button class="btn btn-primary mt-3" @click="selectTab('redirects')" v-if="!redirects.length">Maak een redirect aan</button>
            </div>
            <button class="btn btn-success btn-block" @click="publish"
                    :disabled="!mayUpdate || versionEditor && (versionEditor.changed || versionEditor.hasRecurringSchedules)"
                    v-if="version && version !== live">
              Live zetten
            </button>
            <button class="btn btn-warning btn-block" @click="depublish" v-if="version && version == live && mayDepublish"
                    :disabled="versionEditor && versionEditor.hasRecurringSchedules">
              Depubliceren
            </button>
            <references ref="refsDialog" v-if="!isNew" :item-id="version.pageId" :item-name="version.page.name" :button-classes="['btn-light', 'btn-block', 'references-btn']"></references>
          </editor-sidebar-item>
          <editor-sidebar-item v-if="isPageVersion">
            <field label="Versie">
              <dropdown-menu :label="versionName" class="position-relative bg-white border rounded mb-2 version-dropdown" full-width>
                <div class="m-2">
                  <input class="form-control" type="text" v-model="versionSearchInput" placeholder="Zoek versies..." @click.stop.prevent />
                </div>
                <div class="dropdown-item-container">
                  <router-link class="px-2 py-2 d-flex align-items-center justify-space-between dropdown-item" v-for="version in filteredVersions" :key="version.id" :to="{ name: 'page-editor-version', params: { id: page.id, versionId: version.id }, activeClass: 'active'}">
                    <span class="text-truncate d-inline-block dropdown-item-text">{{ version.name || 'Naamloze versie' }}</span>
                    <calendar-icon aria-hidden="true" v-if="scheduled(version)" />
                    <check-icon aria-hidden="true" v-if="published(version)" />
                    <close-icon aria-hidden="true" v-if="!published(version) && !scheduled(version) && !hasRecurrentSchedule(version)" />
                    <sync-icon v-if="!published(version) && !scheduled(version) && hasRecurrentSchedule(version)" />
                  </router-link>
                </div>
              </dropdown-menu>
              <button type="button" class="btn btn-light btn-block" @click.prevent="handleCopyVersion" :disabled="!mayCreate">
                Nieuwe versie
              </button>
              <button type="button" class="btn btn-light btn-block" @click="openPreview" v-if="version && previewUrl">Preview</button>
            </field>
            <ul class="list-group list-group-flush">
              <li class="list-group-item d-flex justify-content-between">
                <div class="flex-shrink-0 mr-4">Status</div>
                <div><span class="badge" :class="{ 'badge-success': state.published, 'badge-secondary': !state.published }">{{ state.published ? 'Gepubliceerd' : 'Niet gepubliceerd' }}</span></div>
              </li>
              <li class="list-group-item">
                <edited-data label="Aangemaakt" :date="createdDate" :username="version.createdBy" />
              </li>
              <li class="list-group-item">
                <edited-data label="Gewijzigd" :date="modifiedDate" :username="version.modifiedBy" />
              </li>
            </ul>
          </editor-sidebar-item>
          <multi-language-only>
            <editor-sidebar-item v-if="page">
              <page-editor-translations ref="pageEditorTranslations" :language-ref="page.languageRef" :version-id="version.id" :page-id="page.id" />
            </editor-sidebar-item>
          </multi-language-only>
          <editor-sidebar-item>
            <button class="btn btn-danger btn-block mb-3" @click="() => deletePageModal.open(page, version)" :disabled="!mayDelete">Verwijderen</button>
          </editor-sidebar-item>
        </template>
      </page-version-editor>
      <confirm-dialog ref="dialog" />
      <delete-page-modal ref="deletePageModal" @delete="onDelete" @error="onDeleteError" />
    </validator-group>
  </loader>
</template>

<script lang="ts">
import { Vue, Component, Prop, Watch, Ref } from 'vue-property-decorator';
import { namespace } from 'vuex-class';

// @ts-ignore
import * as formElements from '../shared/form/all.js';
import references from '../shared/references.vue';
import loader from '../shared/loader.vue';
import documentationEditor from '../shared/documentation-editor.vue';
import DropdownMenu from '../shared/dropdown-menu.vue';
import PageBreadCrumbs from './page-bread-crumbs.vue';
import PageVersionEditor from './page-version-editor.vue';
import EditorSidebar from '../shared/editor-sidebar.vue';
import EditorSidebarItem from '../shared/editor-sidebar-item.vue';
import EditedData from '../shared/edited-data.vue';
import DeletePageModal from './delete-page-modal.vue';

// Multi-language
import MultiLanguageOnly from '../shared/language/multi-language-only.vue';
import PageEditorTranslations from '../shared/language/page-editor-translations.vue';

// @ts-ignore
import { getPublishedVersions, isDepublished, isPublished, isScheduled } from './version-utils';
import PageApi from '../api/page-api';
import DateUtils from '../tools/date-utils';
import { Redirect, Version, VersionedPage } from '../types/api';
import { PermissionListModel } from '../stores/types/PermissionStoreModel';
import { Route } from 'vue-router';
import { RawLocation } from 'vue-router/types/router';
import RedirectApi from '../api/redirect-api';
import RouteUtils, { createRoute, ROUTES } from '../tools/route-utils';

const PermissionStore = namespace('permissions');
const PageStore = namespace('pages');
const AlertStore = namespace('alerts');

export interface PageEditorPermissions extends PermissionListModel {
  canDepublish: boolean;
}

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

@Component({
  components: {
    DropdownMenu,
    PageBreadCrumbs,
    PageVersionEditor,
    EditorSidebar,
    EditorSidebarItem,
    EditedData,
    DeletePageModal,
    loader,
    references,
    documentationEditor,
    ...formElements,
    // Multi-language
    MultiLanguageOnly,
    PageEditorTranslations
  }
} as any)
export default class PageEditor extends Vue {
  @Prop(String) readonly versionId!: string;

  @Ref() readonly refsDialog!: any;
  @Ref() readonly dialog!: any;
  @Ref() readonly versionEditor!: PageVersionEditor;
  @Ref() readonly deletePageModal!: DeletePageModal;
  @Ref() readonly pageEditorTranslations!: PageEditorTranslations;

  page: VersionedPage | null = null;
  isLoading = true;
  versionsWithRecurrentSchedules: string[] = [];
  previewWindow: Window | null = null;
  redirects: Redirect[] | null = [];
  versionCounter = 0;
  versionSearchInput = '';

  @PermissionStore.Getter('getPermissions') readonly getPermissions!: (itemName: string) => PermissionListModel;
  @PermissionStore.Getter('canCreate') readonly canCreate!: (itemName: string) => boolean;
  @PermissionStore.Getter('canUpdate') readonly canUpdate!: (itemName: string) => boolean;
  @PermissionStore.Getter('canDelete') readonly canDelete!: (itemName: string) => boolean;
  @PageStore.Getter('types') readonly types!: any;
  @PageStore.Getter('isFolder') readonly isFolder!: (page: VersionedPage) => boolean;
  @PageStore.Action('updateVersion') readonly updateVersion!: (version: Version) => Promise<Version>;
  @PageStore.Action('publishVersion') readonly publishVersion!: (version: Version) => Promise<Version>;
  @PageStore.Action('depublishVersion') readonly depublishVersion!: (version: Version) => Promise<Version>;
  @AlertStore.Action('addError') readonly addError!: (message: string) => void;
  @AlertStore.Action('addSuccess') readonly addSuccess!: (message: string) => void;

  mounted() {
    this.fetchData();
  }

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

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

  async fetchData() {
    this.isLoading = true;
    this.page = await PageApi.getPageById(this.$route.params.id);

    // Wait for 'this.live' to be computed after 'this.page' is set
    await this.$nextTick();

    const version = this.live || this.page.versions[0];

    if (version && !this.$route.params.versionId) {
      await this.changePage(this.page.id, version.id);

      return;
    }

    this.isLoading = false;

    // Wait for refs to be populated after loading state is cleared
    await this.$nextTick();

    this.versionsWithRecurrentSchedules = await PageApi.scheduledVersionIds(this.$route.params.id);
  }

  async changePage(pageId: string, versionId?: string) {
    try {
      if (versionId) {
        await RouteUtils.replace(createRoute(ROUTES.PAGE_EDITOR_VERSION, {
          id: pageId,
          versionId,
          replace: 'true'
        }));
      } else {
        await RouteUtils.replace(createRoute(ROUTES.PAGE_EDITOR, { id: pageId }));
      }
    } catch {
      // Ignore uncaught exception due to possibly unwanted redirect as it is expected here
    }
  }

  async save() {
    try {
      if (!this.version) {
        throw new Error('No active page version to save.');
      }

      await this.updateVersion(this.version);

      if (this.versionEditor) {
        this.versionEditor.resetChanged();
      }

      await this.addSuccess('Pagina is opgeslagen!');
    } catch (e) {
      await this.addError(`Pagina opslaan is niet gelukt! <br> ${e.message}`);
    }
  }

  async handleCopyVersion() {
    try {
      if (!this.version || !this.page) {
        throw new Error('No active page version to copy.');
      }

      const version = await PageApi.copyVersion(this.version);
      const page = await PageApi.getPageById(this.page.id);

      this.page = page;

      await this.$nextTick();

      if (this.versionEditor) {
        this.versionEditor.changed = false;
      }

      await RouteUtils.push(createRoute(ROUTES.PAGE_EDITOR_VERSION, { id: page.id, versionId: version.id }));
    } catch (e) {
      await this.addError(`Pagina kopiëren is niet gelukt! <br> ${e.message}`);
    }
  }

  confirmChangingRoute(to: Route, from: Route, next: (to?: RawLocation | false) => void) {
    // check if version is changed by user
    if (this.versionEditor && this.versionEditor.changed && !to.params.redirected) {
      this.dialog.confirm(
        'Er zijn wijzigingen niet opgeslagen',
        'Wil je de wijzigingen verwijderen?',
      ).then((isConfirmed: boolean) => {
        isConfirmed ? next() : next(false);
      }).catch(() => {
        // Fail silently
      });
    } else {
      next();
    }
  }

  openPreview() {
    this.previewWindow = this.previewUrl ? window.open(this.previewUrl) : null;
  }

  async cancel() {
    try {
      await this.dialog.confirm(
        'Herstellen',
        'Weet je zeker dat je de wijzigingen ongedaan wilt maken?',
      );

      if (!this.page?.id) {
        return;
      }

      this.page = await PageApi.getPageById(this.page.id);

      if (this.versionEditor) {
        this.versionEditor.resetChanged();
      }
    } catch (error) {
      // Do nothing if confirm is cancelled
    }
  }

  async publish() {
    try {
      if (!this.version) {
        throw new Error('No active page version to publish.');
      }
      const pageVersion = await this.publishVersion(this.version);
      this.handlePublication(pageVersion);
      this.pageEditorTranslations?.update();

      await this.addSuccess('Deze versie staat nu live!');
    } catch (e) {
      await this.addError(`Versie live zetten is niet gelukt! <br> ${e.message}`);
    }
  }

  async depublish() {
    try {
      await this.dialog.confirm(
        'Depubliceren',
        'Weet je zeker dat je deze versie wilt depubliceren?',
        'Ja, depubliceren',
      );

      try {
        if (!this.version) {
          throw new Error('No active page version to depublish.');
        }

        const pageVersion = await this.depublishVersion(this.version);
        this.handlePublication(pageVersion);
        this.pageEditorTranslations?.update();
      } catch (e) {
        await this.addError(`Versie depubliceren is niet gelukt! <br> ${e.message}`);
      }
    } catch (error) {
      // Do nothing if confirm is cancelled
    }
  }

  handlePublication(pageVersion: Version) {
    if (!this.version) {
      return;
    }

    this.version.publishDate = pageVersion.publishDate;
    this.version.depublishDate = pageVersion.depublishDate;

    if (this.versionEditor) {
      this.versionEditor.resetChanged();
    }
  }

  hasRecurrentSchedule(version: Version) {
    return this.versionsWithRecurrentSchedules.includes(version.id);
  }

  scheduled(version: Version) {
    return isScheduled(version);
  }

  published(version: Version) {
    return isPublished(version);
  }

  depublished(version: Version) {
    return isDepublished(version);
  }

  async getRedirects() {
    if (this.page) {
      this.redirects = await RedirectApi.getRedirects(this.page.id);
    }
  }

  selectTab(tabTitle: string) {
    this.versionEditor.selectTabByTitle(tabTitle);
  }

  onDelete() {
    if (this.versionEditor) {
      this.versionEditor.changed = false;
    }
  }

  onDeleteError(error: any) {
    if (error.isReferenceError && this.versionEditor) {
      this.refsDialog.openErrorModal();
    }
  }

  onVersionReset() {
    this.versionCounter++;

    // update preview window with the new version
    try {
      // @ts-ignore
      this.previewWindow.location = this.previewUrl;
    } catch {
      // errors can safely be ignored
    }
  }

  get filteredVersions(): Version[] {
    return this.page?.versions?.filter(version => {
      if (version.name === null && !this.versionSearchInput) {
        return true;
      }

      return version.name?.toLowerCase().includes(this.versionSearchInput.toLowerCase())
    }) || [];
  }

  get version(): Version | null {
    if (this.page) {
      const version = this.page.versions.find(version => version.id == this.versionId);
      return version || null;
    }
    return null;
  }

  get versionName(): string {
    const defaultName = 'Naamloze versie';
    return this.version ? this.version.name || defaultName : defaultName;
  }

  get allPublished(): Version[] {
    return this.page ? getPublishedVersions(this.page.versions) : [];
  }

  get hasPublishedVersions(): boolean {
    return this.page ? this.page?.versions.some(isPublished) : false;
  }

  get live(): Version {
    return this.allPublished[0];
  }

  get state() {
    return {
      published: this.version ? this.published(this.version) : false,
      depublished: this.version ? this.depublished(this.version) : false,
      scheduled: this.version ? this.scheduled(this.version) : false,
      live: this.live,
    };
  }

  get schema() {
    return this.page?.versions && this.page?.versions[0] && this.types[this.page.versions[0].$type] || {};
  }

  get permissions(): PageEditorPermissions {
    return {
      ...this.getPermissions('CmsPage'),
      canDepublish: this.mayDepublish,
    };
  }

  get mayCreate(): boolean {
    // TODO: use meta.itemName when child classes are returned in the permissions api
    return this.canCreate('CmsPage');
  }

  get mayUpdate(): boolean {
    // TODO: use meta.itemName when child classes are returned in the permissions api
    return this.canUpdate('CmsPage');
  }

  get mayDepublish(): boolean {
    // TODO: use meta.itemName when child classes are returned in the permissions api
    return this.canUpdate('CmsPage')
      && (!this.schema.preventDepublish || this.allPublished.length > 1);
  }

  get mayDelete(): boolean {
    // TODO: use meta.itemName when child classes are returned in the permissions api
    return this.canDelete('CmsPage')
      && (!this.version
        || (
          (!this.schema.preventDelete || (this.page?.versions.length || false) > 1)
          && !this.published(this.version)
        )
      );
  }

  get versionSchemaProperties() {
    return this.$store.state.pages.schema.properties;
  }

  get previewUrl(): string | null {
    // Updating the versionCounter is will trigger a refresh of the preview iframes
    return this.version ? PageApi.previewUrl(this.version, this.versionCounter): null;
  }

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

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

  get isNew() {
    return !this.version?.id;
  }

  get isPageVersion() {
    return this.version && this.page && !this.isFolder(this.page);
  }

  @Watch('$route')
  onRouteChange() {
    this.fetchData();
  }

  @Watch('page')
  onPageChange() {
    this.getRedirects();
  }
}
</script>

<style lang="scss" scoped>
.page-editor {
  &-title {
    line-height: 0.8;
  }

  .version-dropdown {
    .dropdown-item {
      &.active .dropdown-item-text {
        color: white;
      }

      &-container {
        max-height: 300px;
        overflow: scroll;
      }

      &-text {
        width: 90%;
        padding: 0;
      }
    }
  }

  .references-btn {
    margin-top: .5rem;
  }
}
</style>
