<template>
  <form @submit.prevent="saveAsset" class="h-100">
    <div class="row h-100 px-3">
      <div class="col-sm-4 p-4">
        <asset-detail-editor ref="assetDetailEditor">
          <template v-slot:main>
            <fieldset class="asset-form-container mt-n3 pt-3">
              <field label="Titel">
                <input type="text" class="form-control" v-model="asset.title">
              </field>
              <field label="Alt tekst" v-if="!useMultiLanguage">
                <input type="text" class="form-control" v-model="asset.alt">
              </field>
              <field label="Tags">
                <relation-picker v-model="asset.tagIds" :source="tags" display-field="title" :multi="true"></relation-picker>
              </field>
              <field label="Bestand" v-if="isNew && !fromDragDrop">
                <input ref="file" type="file" @change="selectFile($event.target.files[0])" class="form-control-file"/>
              </field>
              <button v-if="useMultiLanguage" class="btn btn-block form-editor-property" @click.prevent="assetDetailEditor.open('alt')">
                Alt tekst
                <chevron-right-icon />
              </button>

              <ul class="asset-meta-list list-group list-group-flush">
                <li class="list-group-item" v-if="!isNew">
                  <edited-data label="Aangemaakt" :date="createdDate" :username="asset.createdBy" />
                </li>
                <li class="list-group-item" v-if="!isNew">
                  <edited-data label="Gewijzigd" :date="modifiedDate" :username="asset.modifiedBy" />
                </li>
                <li class="list-group-item d-flex justify-content-between" v-if="asset.blobstoreUrl">
                  <div class="flex-shrink-0 mr-4">Bestandstype</div>
                  <div>{{ asset.contentType }}</div>
                </li>
                <li class="list-group-item d-flex justify-content-between" v-if="asset.width">
                  <div class="flex-shrink-0 mr-4">Afmetingen</div>
                  <div>{{ imageDimensions }}</div>
                </li>
                <li class="list-group-item d-flex justify-content-between" v-if="asset.size">
                  <div class="flex-shrink-0 mr-4">Bestandsgrootte</div>
                  <div>{{ fileSize }}</div>
                </li>
                <li class="list-group-item d-flex justify-content-between" v-if="!imageUrl && asset.blobstoreUrl">
                  <div class="flex-shrink-0 mr-4">Download URL</div>
                  <div>{{ asset.blobstoreUrl }}</div>
                </li>
              </ul>

              <div class="form-group border-top border-bottom py-3" v-if="assetProp">
                <label for="generateUrl">Genereer URL</label>
                <div class="row mx-0">
                  <input id="generateUrl" type="number" class="form-control col-sm-4" v-model="generateDimension" placeholder="Aantal pixels">
                  <div class="col-sm-4">
                    <input id="width" type="radio" value="width" v-model="generateWidthOrHeight"><label for="width">breed</label>
                    <input id="height" type="radio" value="height" v-model="generateWidthOrHeight"><label for="height">hoog</label>
                  </div>
                  <button type="button" class="btn btn-primary col-sm-4" @click="generate">Genereer</button>
                </div>
                <input v-if="!!generatedUrl" type="text" class="form-control mt-2" v-model="generatedUrl" disabled>
              </div>
            </fieldset>
          </template>
          <template v-slot:properties="slotProps">
            <asset-detail-alts-property v-model="asset.alts" :activeId="slotProps.activeId" :close="slotProps.close" />
          </template>
        </asset-detail-editor>
      </div>
      <div class="col-sm-8 p-4 bg-light">
        <iframe v-if="!imageUrl && isPdf && (asset.blobstoreUrl || selectedFileDataUrl)" :src="asset.blobstoreUrl || selectedFileDataUrl" width="100%" height="100%" />
        <div v-else-if="!imageUrl && isVideo && (asset.blobstoreUrl || selectedFileDataUrl)">
          <video :src="asset.blobstoreUrl || selectedFileDataUrl" width="100%" controls />
        </div>
        <div v-else>
          <loader :is-loading="isLoading" class="mb-3">
            <h4 v-if="imageUrl">Origineel</h4>
            <div class="preview-wrapper" v-if="imageUrl">
              <img ref="img" :src="imageUrl" @load="updateAssetData" crossorigin="anonymous" v-if="imageUrl != asset.blobstoreUrl"/>
              <img :src="imageUrl" v-if="imageUrl == asset.blobstoreUrl"/>
            </div>
          </loader>
          <div class="image-editor" v-if="imageUrl && asset && asset.centerPoint">
            <asset-detail-center-point :image-url="imageUrl" :asset="asset" v-model="asset.centerPoint"/>
            <asset-detail-variants :image-url="imageUrl" :asset="asset"/>
          </div>
        </div>
      </div>
    </div>

    <bottom-toolbar>
      <button slot="right" @click.prevent="cancel" class="btn btn-light mr-2">Annuleren</button>
      <button slot="right" @click.prevent="remove" class="btn btn-danger mr-2" :disabled="!permissions.canDelete" v-if="!isNew">
        Verwijderen
      </button>
      <p class="d-inline-block mx-2" slot="right" v-if="bulkUpload">{{ bulkUpload.label }}</p>
      <button slot="right" type="submit" class="btn btn-primary mr-2" :disabled="!permissions.canUpdate || !canSubmit">
        {{ saveButtonText }}
      </button>
    </bottom-toolbar>

    <confirm-dialog ref="dialog"></confirm-dialog>
  </form>
</template>

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

// @ts-ignore
import { compress } from '../tools/image-preview.js';
// @ts-ignore
import * as assetUtils from '../shared/asset-utils';
import { getPreviewUrlForAsset } from '../api/asset-api-helpers';
import { handleFetch } from '../api/helpers';
import { Asset, AssetTag, AssetVariantCrop } from '../types/api';
import { PermissionListModel } from '../stores/types/PermissionStoreModel';

import ConfirmDialog from '../shared/form/confirm-dialog.vue';
import Field from '../shared/form/field.vue';
import Loader from '../shared/loader.vue';
import AssetDetailCenterPoint from './asset-detail-center-point.vue';
import AssetDetailVariants from './asset-detail-variants.vue';
import AssetDetailEditor from './asset-detail-editor.vue';
import AssetDetailAltsProperty from './asset-detail-alts-property.vue';
import BottomToolbar from '../shared/form/bottom-toolbar.vue';
import RelationPicker from '../shared/form/relation-picker.vue';
import DateUtils from "../tools/date-utils";
import { AssetBulkUpload } from './asset-detail-page.vue';
import EditedData from '../shared/edited-data.vue';
import CrudDataMixin from '../shared/mixins/crud-data-mixin';

// @ts-ignore
const StackBlur = require("stackblur-canvas/src/stackblur.js");
const AssetStore = namespace('assets');
const AssetTagStore = namespace('cmsTag');
const PermissionStore = namespace('permissions');
const LanguageStore = namespace('languages');

const EXTENSION_REGEX = /\.[^.]*$/;
const MaxFileSizeInKB = 1000;

@Component({
  components: {
    ConfirmDialog,
    Field,
    Loader,
    AssetDetailCenterPoint,
    AssetDetailVariants,
    AssetDetailEditor,
    AssetDetailAltsProperty,
    EditedData,
    RelationPicker,
    BottomToolbar
  },
  mixins: [CrudDataMixin('cmsTag')]
})
export default class AssetDetail extends Vue {
  @Prop(Object) readonly value!: Asset | null;
  @Prop(Object) readonly assetProp!: Asset;
  @Prop(Array) readonly defaultImageCrops!: AssetVariantCrop[];
  @Prop(Object) readonly bulkUpload!: AssetBulkUpload;

  @Ref() readonly dialog!: any; // TODO: Replace with typescript class once rewritten
  @Ref() readonly assetDetailEditor!: AssetDetailEditor;
  @Ref() readonly img!: HTMLImageElement;
  @Ref() readonly file!: HTMLInputElement;

  actionRunning = false;
  selectedFile: File | null = null;
  selectedFileDataUrl: string | null = null;
  isLoading = false;
  generatedUrl: string | null = null;
  generateDimension: number | null = null;
  generateWidthOrHeight = 'width';
  asset: Asset = this.assetProp || this.createDefaultAsset();
  selectedTags: AssetTag[] = [];
  fromDragDrop = false;

  @AssetTagStore.State('items') readonly tags!: AssetTag[];
  @AssetStore.Action('createAsset') readonly createAsset!: (data: FormData) => Promise<Asset>;
  @AssetStore.Action('deleteAsset') readonly deleteAsset!: (id: string) => Promise<Response>;
  @AssetStore.Action('updateAsset') readonly updateAsset!: (asset: Asset) => Promise<Asset>;
  @PermissionStore.Getter('getPermissions') readonly getPermissions!: (itemName: string) => PermissionListModel;
  @LanguageStore.State('useMultiLanguage') readonly useMultiLanguage!: boolean;

  createDefaultAsset(): Asset {
    return {
      title: '',
      alt: '',
      blobstoreUrl: '',
      previewData: '',
      centerPoint: undefined,
      tagIds: this.selectedTags || [],
      variants: this.defaultImageCrops.map(c => ({ ...c, isDefaultVariant: true, isUntouched: true }))
    } as unknown as Asset;
  }

  async saveAsset() {
    try {
      this.actionRunning = true;

      const asset = this.isNew
        ? await this.createAsset(this.createFormDataObject(this.asset, this.selectedFile))
        : await this.updateAsset(this.asset);

      this.$emit('input', asset); // vue syntax: update prop 'value' in the parent
      this.$store.dispatch('alerts/addSuccess', 'De asset is opgeslagen');

      if (this.bulkUpload) {
        this.asset = this.createDefaultAsset();
      }
    } catch (e) {
      this.$store.dispatch('alerts/addError', `Opslaan is niet gelukt! <br> ${e.message}`);
    } finally {
      this.actionRunning = false;
    }
  }

  createFormDataObject(asset: Asset, fileData: File | null) {
    const centerPointX = asset.centerPoint && asset.centerPoint.x;
    const centerPointY = asset.centerPoint && asset.centerPoint.y;

    const data = new FormData();
    data.append("file", fileData || '');
    data.append("title", asset.title);
    data.append("alt", asset.alt || '');
    data.append("previewData", asset.previewData);
    data.append("centerPointX", centerPointX?.toString() || '');
    data.append("centerPointY", centerPointY?.toString() || '');

    if (asset.width && asset.height) {
      data.append("width", asset.width.toString());
      data.append("height", asset.height.toString());

      for (let i = 0; i < asset.variants.length; i++) {
        const variant = asset.variants[i];
        const useGlobalCenterPoint = variant.isDefaultVariant && variant.isUntouched;
        data.append(`variants[${i}].id`, variant.id);
        data.append(`variants[${i}].fixedAspectRatio`, variant.fixedAspectRatio?.toString() || '');
        data.append(`variants[${i}].name`, variant.name || '');
        data.append(`variants[${i}].crop.centerPoint.X`, useGlobalCenterPoint ? centerPointX?.toString() || '' : variant.crop.centerPoint.x.toString());
        data.append(`variants[${i}].crop.centerPoint.Y`, useGlobalCenterPoint ? centerPointY?.toString() || '' : variant.crop.centerPoint.y.toString());
        data.append(`variants[${i}].crop.width`, variant.crop.width.toString());
        data.append(`variants[${i}].crop.height`, variant.crop.height.toString());
        data.append(`variants[${i}].isUntouched`, variant.isUntouched?.toString() || '');
        data.append(`variants[${i}].isDefaultVariant`, variant.isDefaultVariant?.toString() || '');
      }
    }

    for (let i = 0; i < asset.tagIds.length; i++) {
      data.append(`tagIds[${i}]`, asset.tagIds[i]);
    }

    return data;
  }

  selectFile(file: File, fromDragDrop = false) {
    this.fromDragDrop = fromDragDrop;
    this.selectedFile = file;

    if (this.selectedFile) {
      if (file.type.includes('image')) { 
        this.validateFileSize(file);
      }

      if (!this.asset.title) {
        this.asset.title = this.selectedFile.name.replace(EXTENSION_REGEX, '');
      }

      const reader = new FileReader();
      reader.onloadstart = () => {
        this.isLoading = true;
      };
      reader.onloadend = () => {
        this.isLoading = false;
      };
      reader.onload = (e) => {
        this.selectedFileDataUrl = e.target?.result as string | null;
      };
      reader.readAsDataURL(this.selectedFile);
    }
  }

  validateFileSize(file: File) { 
    const fileSizeInKB = Math.round((file.size/1024)*100)/100;
    if (fileSizeInKB > MaxFileSizeInKB) {
      this.$store.dispatch('alerts/addWarning', `De bestandsgrootte is groter dan ${MaxFileSizeInKB} KB`);
    }
  }

  async remove() {
    try {
      await this.dialog.confirm("Asset verwijderen", "Weet je zeker dat je deze asset wilt verwijderen?");

      try {
        await this.deleteAsset(this.asset.id);

        this.$emit('input', null);
        this.$store.dispatch('alerts/addSuccess', 'De asset is verwijderd');
      } catch (error) {
        if (error.isReferenceError) {
          this.$emit('delete-error', error);
        }

        this.$store.dispatch('alerts/addError', `Verwijderen is niet gelukt! <br> ${error.message}`);

        throw error;
      }
    } catch (error) {
      // intentionally empty for removing console error when dismissing dialog
    }
  }

  cancel() {
    this.$emit('input', null); // vue syntax: update prop 'value' in the parent
    this.$emit('cancel');
  }

  updateAssetData() {
    const cnv = document.createElement('canvas');
    const ctx = cnv.getContext('2d');

    // add width/height for new images only
    if (this.isNew) {
      const width = this.img.naturalWidth;
      const height = this.img.naturalHeight;
      this.asset.width = width;
      this.asset.height = height;

      this.asset.centerPoint = {
        x: Math.round(width / 2),
        y: Math.round(height / 2)
      };

      this.asset.variants = this.asset.variants.map(v => ({
        ...v,
        crop: assetUtils.getCropForAspectRatio(this.asset, v.fixedAspectRatio)
      }));
    }

    const maxSize = 32;
    const ratio = this.img.naturalWidth / this.img.naturalHeight;
    const width = cnv.width = Math.min(maxSize, Math.round(maxSize * ratio));
    const height = cnv.height = Math.min(maxSize, Math.round(maxSize / ratio));

    ctx?.drawImage(this.img, 0, 0, width, height);
    StackBlur.canvasRGBA(cnv, 0, 0, width, height, 5);

    this.asset.previewData = compress(cnv);
  }

  async generate() {
    const path = `/assets/generate-url/${this.asset.id}?${this.generateWidthOrHeight}=${this.generateDimension}`;
    try {
      const response = await handleFetch(path, {}, false);
      this.generatedUrl = await response.json();
    } catch (error) {
      this.$store.dispatch('alerts/addError', 'Er ging helaas wat mis bij het genereren van de url.');
    }
  }

  isType(type: string): boolean {
    const assetIsType = this.asset?.contentType?.includes(type);
    const selectedFileIsType = this.selectedFileDataUrl?.includes(type);
    return !!(assetIsType || selectedFileIsType);
  }

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

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

  get isPdf(): boolean {
    return this.isType('pdf')
  }

  get isVideo(): boolean {
    return this.isType('video');
  }

  get imageUrl(): string {
    if (this.selectedFileDataUrl && this.selectedFile?.type.startsWith('image/')) {
      return this.selectedFileDataUrl;
    }
    return getPreviewUrlForAsset(this.asset);
  }

  get canSubmit(): boolean {
    return !!((this.asset.blobstoreUrl || this.selectedFile) && this.asset.title && !this.actionRunning && (this.asset.alt || this.useMultiLanguage));
  }

  get imageDimensions(): string {
    return this.asset.width + ' x ' + this.asset.height;
  }

  get fileSize(): string {
    const fileSizeExtension = ['Bytes', 'KB', 'MB', 'GB']
    let size = this.asset.size;
    let i = 0;
    while(size>900){
      size = size/1024;
      i++;
    }
    const calculatedSize = Math.round(size*100)/100;

    return `${calculatedSize} ${fileSizeExtension[i]}`;
  }

  get createdDate(): string {
    return DateUtils.formatDateString(this.asset.created);
  }

  get modifiedDate(): string {
    return DateUtils.formatDateString(this.asset.modified);
  }

  get saveButtonText(): string {
    return this.bulkUpload ? `Opslaan & ${this.bulkUpload.currentFileIndex < this.bulkUpload.files.length ? 'volgende' : 'klaar'}` : 'Opslaan';
  }

  @Watch('asset.tagIds')
  onAssetChange(tagIds: AssetTag[]) {
    this.selectedTags = tagIds;
  }
}
</script>

<style lang="scss" scoped>
img {
  max-width: 100%;
  max-height: 250px;
}

.preview-wrapper {
  background: rgba(204, 204, 204, .3) repeating-linear-gradient(45deg, transparent, transparent 5px, rgba(255, 255, 255, .2) 5px, rgba(255, 255, 255, .2) 10px);
  display: block; // override flex from bootstrap
  text-align: center;
}

.form-controls {
  display: flex;

  button:first-child {
    margin-left: auto;
  }
}

.asset-form-container {
  position: sticky;
  top: 0;
}

.asset-meta-list li div {
  word-break: break-all;
}

.form-row-url {
  display: flex;
  justify-content: space-between;

  &-input {
    flex: 1 1 auto;
    margin-right: 10px;
  }

  &-button {
    width: 100px;
    display: flex;
    justify-content: flex-end;
    align-items: center;

    input {
      margin-right: 5px;
    }

    label {
      margin-bottom: 0;
    }
  }
}

.form-editor-property {
  $margin: 1.5rem;

  display: flex;
  justify-content: space-between;
  border: 1px solid rgba(0, 0, 0, 0.125);
  border-left: 0;
  border-right: 0;
  border-radius: 0;
  width: calc(100% + #{$margin * 2});
  margin: 0 (-$margin) 1rem (-$margin);
  padding: .5rem $margin;

  .material-design-icon {
    transition: transform .2s ease-in-out;
  }

  &:hover .material-design-icon {
    transform: translateX(5px);
  }
}

.image-editor {
  margin-bottom: 10px;
}
</style>
