<template>
  <loader class="asset-overview" :is-loading="isLoadingAssets">
    <form v-if="modal" class="w-100 my-3" @submit.prevent>
      <input type="text" v-model="searchInput" @input="onSearch" class="form-control search-input" placeholder="Zoeken in..."/>
    </form>
    <portal v-else to="top-header-left">
      <form @submit.prevent class="w-100">
        <input type="text" v-model="searchInput" @input="onSearch" class="form-control search-input" placeholder="Zoeken in..."/>
      </form>
    </portal>
    <div class="pb-3 d-flex justify-content-between align-items-center">
      <div>
        <div class="mr-4 d-inline-block">
          <span class="mr-2">Tags</span>
          <select-dropdown-menu placeholder="Zoek op tags" :items="tags" v-model="searchState.selectedTags">
            <p class="px-2 mb-0">Er zijn geen tags. <router-link v-if="permissions.canCreate" to="/cms-tags">Tags toevoegen</router-link></p>
          </select-dropdown-menu>
        </div>
        <div class="mr-2 d-inline-block">
          <span class="mr-2">Bestandstype</span>
          <dropdown-menu :label="assetTypeFilters[searchState.assetTypeFilter]" class="d-inline-block position-relative bg-white border rounded">
            <button class="px-2 py-2 d-flex align-items-center dropdown-item" v-for="(label, filter) in assetTypeFilters" :key="filter" @click="searchState.assetTypeFilter = parseInt(filter)">
              {{ label }}
            </button>
          </dropdown-menu>
        </div>
        <button v-if="hasSelectedFilters" class="btn btn-link inline-block" @click="resetFilters">Verwijder filters</button>
      </div>
      <div>
        <button class="btn px-1" @click="setViewMode(viewModes.List)" v-if="viewMode === viewModes.Grid">
          Lijst weergave <view-list-icon />
        </button>
        <button class="btn px-1" @click="setViewMode(viewModes.Grid)" v-else>
          <span class="mr-1">Grid weergave</span>
          <view-grid-icon />
        </button>
      </div>
    </div>
    <div class="page-container card h-100">
      <div v-if="editable" class="card-header bg-white d-flex justify-content-between align-items-center">
        <div class="d-flex align-items-center">
          <template v-if="permissions.canUpdate">
            <checkbox :value="allAssetsSelected" @input="toggleAllAssets" id="checkbox-all" />
            <strong v-if="!hasSelectedAssets" class="d-inline-block ml-2">Alles selecteren</strong>
            <template v-else>
              <strong class="mx-2">{{ selectedAssets.length }} geselecteerd</strong>
              <button class="btn btn-danger mr-3" @click="deleteSelectedAssets" v-if="permissions.canDelete">Verwijderen</button>
              <select-dropdown-menu class="mr-2" placeholder="Kies tags" :items="tags" v-model="selectedMultipleAssetsTags">
                <p class="px-2 mb-0">Er zijn geen tags. <router-link v-if="permissions.canCreate" to="/cms-tags">Tags toevoegen</router-link></p>
              </select-dropdown-menu>
              <button class="btn btn-light" :disabled="selectedMultipleAssetsTags.length === 0" @click="addSelectedTags">Tags koppelen</button>
            </template>
          </template>
        </div>
        <asset-pagination v-if="hasResults" @update="search" :persist="editable" />
      </div>
      <asset-drop-zone>
        <div class="card-body h-100">
          <template v-if="allAssets.length > 0">
            <div class="table-responsive-xl" v-if="viewMode === viewModes.List">
              <table class="table table-hover">
                <thead>
                  <tr>
                    <th scope="col" v-if="permissions.canUpdate && editable" />
                    <th scope="col">Preview</th>
                    <asset-sortable-field :field="assetSortFields.Title" :selected-field="searchState.assetSortField" :sort-order="searchState.assetSortOrder" @click="sortAssets">Naam</asset-sortable-field>
                    <asset-sortable-field :field="assetSortFields.DateCreated" :selected-field="searchState.assetSortField" :sort-order="searchState.assetSortOrder" @click="sortAssets">Aanmaakdatum</asset-sortable-field>
                    <asset-sortable-field :field="assetSortFields.DateModified" :selected-field="searchState.assetSortField" :sort-order="searchState.assetSortOrder" @click="sortAssets">Wijzigdatum</asset-sortable-field>
                    <th scope="col">Door</th>
                    <th scope="col">Bestandstype</th>
                    <th scope="col">Tags</th>
                  </tr>
                </thead>
                <tbody :class="{ loading: isLoading }">
                  <asset-overview-item
                    v-for="asset in allAssets"
                    :key="asset.id"
                    v-model="valueCopy"
                    :asset="asset"
                    :selected="selectedAssets.indexOf(asset) !== -1"
                    :selectable="editable"
                    @toggle="toggleAsset"
                  />
                </tbody>
              </table>
            </div>
            <div v-else class="row row-cols-md-3 row-cols-lg-4 row-cols-xl-5">
              <asset-overview-item
                v-for="asset in allAssets"
                :key="asset.id"
                v-model="valueCopy"
                :asset="asset"
                :selected="selectedAssets.indexOf(asset) !== -1"
                :selectable="editable"
                :list="viewMode === viewModes.List"
                @toggle="toggleAsset"
              />
            </div>
          </template>

          <p v-if="!isLoading && allAssets.length === 0" >Er zijn geen assets gevonden.</p>
          <asset-pagination v-if="hasResults" @update="search" :persist="editable" />
          <spinner v-if="isLoading" />
        </div>
      </asset-drop-zone>
    </div>
    <confirm-dialog ref="dialog"></confirm-dialog>
  </loader>
</template>

<script lang="ts">
import { Component, Prop, Ref, Vue, Watch } from 'vue-property-decorator';
import { namespace } from 'vuex-class';
import _upperFirst from 'lodash/upperFirst';
import _debounce from 'lodash/debounce';
import _isEqual from 'lodash/isEqual';
import _cloneDeep from 'lodash/cloneDeep';

import ConfirmDialog from '../shared/form/confirm-dialog.vue';
import AssetOverviewItem from './asset-overview-item.vue';
import Loader from '../shared/loader.vue';
import Field from '../shared/form/field.vue';
import AssetSortableField from './asset-sortable-field.vue';
import AssetPagination from './asset-pagination.vue';
import Spinner from '../shared/spinner.vue';
import AssetDropZone from './asset-drop-zone.vue';
import Checkbox from '../shared/form/checkbox.vue';
import CrudDataMixin from '../shared/mixins/crud-data-mixin';

// @ts-ignore
import { ValueSyncMixin } from '../shared/value-sync-mixin.js';
import {
  AddAssetTagsRequest,
  Asset,
  AssetPagination as IAssetPagination,
  AssetSortField,
  AssetSortOrder,
  AssetTag,
  AssetTypeFilter
} from '../types/api';
import RouteUtils from '../tools/route-utils';
import { PermissionListModel } from '../stores/types/PermissionStoreModel';
import { AssetSearchState } from '../stores/types/AssetStoreModel';
import { INITIAL_SEARCH_STATE } from '../stores/modules/asset-store';
import localStorage, { LocalStorageKeys } from '../tools/local-storage';

const AssetStore = namespace('assets');
const AssetTagStore = namespace('cmsTag');
const AlertStore = namespace('alerts');
const PermissionStore = namespace('permissions');

export enum ViewMode {
  List = 'List',
  Grid = 'Grid'
}

@Component({
  components: {
    AssetOverviewItem,
    Loader,
    ConfirmDialog,
    Field,
    AssetSortableField,
    AssetPagination,
    Spinner,
    AssetDropZone,
    Checkbox
  },
  mixins: [ValueSyncMixin, CrudDataMixin('cmsTag')],
})
export default class AssetOverview extends Vue {
  @Prop(Object) readonly value!: Asset | null;
  @Prop(Number) readonly type!: AssetTypeFilter;
  @Prop(Boolean) readonly editable!: boolean;
  @Prop(Boolean) readonly modal!: boolean;

  @Ref() readonly dialog!: any;

  isLoading = true;
  viewMode: ViewMode = localStorage.get<ViewMode>(LocalStorageKeys.AssetViewMode) || ViewMode.List;
  viewModes = ViewMode;
  selectedAssets: Asset[] = [];
  selectedMultipleAssetsTags: AssetTag[] = [];
  searchState: AssetSearchState = { ..._cloneDeep(INITIAL_SEARCH_STATE), assetTypeFilter: this.type || INITIAL_SEARCH_STATE.assetTypeFilter };
  searchInput = '';
  assetSortFields = AssetSortField;
  assetSortOrders = AssetSortOrder;

  @PermissionStore.Getter('getPermissions') readonly getPermissions!: (itemName: string) => PermissionListModel;
  @AssetTagStore.State('items') readonly tags!: AssetTag[];
  @AssetStore.State('initialized') readonly initialized!: boolean;
  @AssetStore.State('allAssets') readonly allAssets!: Asset[];
  @AssetStore.State('pagination') readonly pagination!: IAssetPagination;
  @AssetStore.State('isLoadingAssets') readonly isLoadingAssets!: Asset[];
  @AssetStore.Mutation('setSearchState') readonly setSearchState!: (searchState: AssetSearchState) => void;
  @AssetStore.Action('searchAssets') readonly searchAssets!: () => Promise<void>;
  @AssetStore.Action('deleteAssets') readonly deleteAssets!: (ids: string[]) => Promise<Response>;
  @AssetStore.Action('addTags') readonly addTags!: (request: AddAssetTagsRequest) => Promise<Response>;
  @AlertStore.Action('addError') readonly addError!: (message: string) => void;
  @AlertStore.Action('addSuccess') readonly addSuccess!: (message: string) => void;

  async mounted() {
    if (!this.initialized) {
      return;
    }

    this.parseRouteSearchQuery();
  }

  parseRouteSearchQuery() {
    const { search, tagFilter, assetTypeFilter, assetSortField, assetSortOrder } = this.$route.query;

    this.searchState = {
      searchQuery: search as string || '',
      assetSortField: RouteUtils.parseEnumFromQuery<AssetSortField>(assetSortField) || this.searchState.assetSortField,
      assetSortOrder: RouteUtils.parseEnumFromQuery<AssetSortOrder>(assetSortOrder) || this.searchState.assetSortOrder,
      assetTypeFilter: RouteUtils.parseEnumFromQuery<AssetTypeFilter>(assetTypeFilter) || this.searchState.assetTypeFilter,
      selectedTags: (tagFilter as string)?.split(',')
        .map(tagId => this.tags.find(tag => tag.id === tagId))
        .filter(Boolean) as AssetTag[] || []
    };
    this.searchInput = this.searchState.searchQuery;
    this.isLoading = false;
  }

  onSearch = _debounce(this.onSearchDebounced, 500);
  onSearchDebounced() {
    this.searchState = { ...this.searchState, searchQuery: this.searchInput };
  }

  setViewMode(viewMode: ViewMode) {
    this.viewMode = viewMode;
    localStorage.set(LocalStorageKeys.AssetViewMode, viewMode.toString());
  }

  toggleAsset(asset: Asset) {
    const assetIndex = this.selectedAssets.indexOf(asset);
    assetIndex !== -1 ? this.selectedAssets.splice(assetIndex, 1) : this.selectedAssets.push(asset);
  }

  toggleAllAssets() {
    this.allAssetsSelected ? this.selectedAssets = [] : this.selectedAssets = [...this.allAssets];
  }

  sortAssets(assetSortField: AssetSortField) {
    const isDifferentField = this.searchState.assetSortField !== assetSortField;
    this.searchState = {
      ...this.searchState,
      assetSortOrder: !isDifferentField && this.searchState.assetSortOrder === AssetSortOrder.Ascending ? AssetSortOrder.Descending : AssetSortOrder.Ascending,
      assetSortField,
    }
  }

  async search() {
    try {
      this.isLoading = true;
      await this.searchAssets();
    } catch (e) {
      this.addError(`Er is iets misgegaan met het ophalen van de assets! <br> ${e.message}`)
    } finally {
      this.isLoading = false;
    }
  }

  async addSelectedTags() {
    try {
      await this.addTags({
        tagIds: this.selectedMultipleAssetsTags.map(tag => tag.id),
        assetIds: this.selectedAssets.map(asset => asset.id)
      });

      await this.search();

      this.selectedAssets = [];
      this.selectedMultipleAssetsTags = [];
      this.addSuccess('De tags zijn toegevoegd aan de assets');
    } catch (e) {
      this.addError(`Er is iets misgegaan met het toevoegen van de tags! <br> ${e.message}`);
    }
  }

  async deleteSelectedAssets() {
    try {
      const assetText = this.selectedAssets.length > 1 ? 'assets' : 'asset';
      await this.dialog.confirm(`${_upperFirst(assetText)} verwijderen`, `Weet je zeker dat je deze ${assetText} wilt verwijderen?`);

      try {
        await this.deleteAssets(this.selectedAssets.map(asset => asset.id));
        this.selectedAssets = [];
        this.addSuccess(`De ${assetText} zijn verwijderd`);
      } catch (e) {
        this.addError(`Verwijderen is niet gelukt! <br> ${e.message}`);
      }
    } catch {
      // intentionally empty for removing console error when dismissing dialog
    }
  }

  resetFilters() {
    this.searchState = _cloneDeep(INITIAL_SEARCH_STATE);
  }

  get hasSelectedAssets(): boolean {
    return this.selectedAssets.length > 0 && this.allAssets.length !== 0;
  }

  get hasSelectedFilters(): boolean {
    return !_isEqual(this.searchState, INITIAL_SEARCH_STATE);
  }

  get hasResults(): boolean {
    return this.allAssets.length > 0;
  }

  get allAssetsSelected(): boolean {
    return this.selectedAssets.length === this.allAssets.length;
  }

  get assetTypeFilters(): { [key in AssetTypeFilter]: string } {
    return {
      [AssetTypeFilter.All]: 'Alles',
      [AssetTypeFilter.Image]: 'Afbeelding',
      [AssetTypeFilter.Video]: 'Video',
      [AssetTypeFilter.PDF]: 'PDF',
      [AssetTypeFilter.Other]: 'Overige'
    };
  }

  get permissions(): PermissionListModel {
    return this.getPermissions('CmsAsset');
  }

  @Watch('searchState', { deep: true })
  async onSearchStateChange() {
    if (this.editable) {
      const { searchQuery, selectedTags, assetTypeFilter, assetSortField, assetSortOrder } = this.searchState;

      await RouteUtils.persistToQuery(this.$router, this.$route, {
        page: this.pagination.currentPage === 1 ? undefined : this.pagination.currentPage.toString(),
        search: searchQuery || undefined,
        tagFilter: selectedTags.map(tag => tag.id).join(',') || undefined,
        assetTypeFilter: assetTypeFilter.toString(),
        assetSortField: assetSortField?.toString(),
        assetSortOrder: assetSortOrder?.toString()
      });
    }
    this.setSearchState(this.searchState);
    await this.search();
  }

  @Watch('initialized')
  onInitializedChange() {
    this.parseRouteSearchQuery();
  }
}
</script>

<style lang="scss" scoped>
.asset-overview {
  .card-header {
    $header-height: 60px;

    height: $header-height;
    min-height: $header-height;
  }

  tbody {
    transition: opacity .3s ease-in-out;

    &.loading {
      opacity: 0.5;
    }
  }
}
</style>
