<!--
Assistant de création d'image

@see https://norserium.github.io/vue-advanced-cropper/
-->
<template>
  <div class="input-image" >
    <UiImage
        ref="imageElement"
        :image-id="modelValue"
        :default-image="defaultImage"
        :width="width"
        :height="height"
        class="image"
        overlay-icon="fas fa-upload"
        @overlay-clicked="openModal()"
    />

    <LazyLayoutDialog :show="showModal">
      <template #dialogType>{{ $t('image_assistant') }}</template>
      <template #dialogTitle>{{ $t('modif_picture') }}</template>
      <template #dialogText>
        <div class="upload">
          <v-row
            v-if="pending"
            class="fill-height ma-0 loading"
            align="center"
            justify="center"
          >
            <v-progress-circular
              :indeterminate="true"
              color="neutral">
            </v-progress-circular>
          </v-row>

          <div v-else >
            <div class="upload__cropper-wrapper">
              <Cropper
                ref="cropper"
                class="upload__cropper"
                check-orientation
                :src="uploadedImage.src"
                :default-position="defaultPosition"
                :default-size="defaultSize"
                @change="onCropperChange"
              />

              <div
                  v-if="uploadedImage.src"
                  class="upload__reset-button"
                  title="Reset Image"
                  @click="reset()"
              >
                <v-icon>fas fa-trash</v-icon>
              </div>
            </div>
            <div class="upload__buttons-wrapper">
              <button class="upload__button" @click="fileInput?.click()">
                <input ref="fileInput" type="file" accept="image/*" @change="uploadImage($event)" />
                {{$t('upload_image')}}
              </button>
            </div>
            <span class="max-size-label">{{ $t('max_size_4_mb') }}</span>
          </div>

        </div>
      </template>
      <template #dialogBtn>
        <v-btn class="mr-4 submitBtn theme-neutral-strong" @click="cancel">
          {{ $t('cancel') }}
        </v-btn>
        <v-btn class="submitBtn theme-primary" @click="save" :disabled="pending">
          {{ $t('save') }}
        </v-btn>
      </template>
    </LazyLayoutDialog>
  </div>
</template>

<script setup lang="ts">

import {ref, Ref} from "@vue/reactivity";
import File from '~/models/Core/File'
import {PropType, watch} from "@vue/runtime-core";
import {useEntityManager} from "~/composables/data/useEntityManager";
import {AnyJson} from "~/types/data";
import ApiResource from "~/models/ApiResource";
import {useImageManager} from "~/composables/data/useImageManager";
import { Cropper } from 'vue-advanced-cropper'
import 'vue-advanced-cropper/dist/style.css';
import {FILE_FOLDER, FILE_STATUS, FILE_TYPE, FILE_VISIBILITY, TYPE_ALERT} from "~/types/enum/enums";
import {usePageStore} from "~/stores/page";

const props = defineProps({
  /**
   * Id de l'objet File, ou null
   */
  modelValue: {
    type: Number as PropType<number | null>,
    required: false,
    default: null
  },
  /**
   * Label du champ
   * Si non défini, c'est le nom de propriété qui est utilisé
   */
  field: {
    type: String,
    required: true
  },
  /**
   * Image par défaut en cas d'absence d'une image uploadée
   */
  defaultImage: {
    type: String,
    required: false
  },
  /**
   * Hauteur de l'image à l'écran (en px)
   */
  height: {
    type: Number,
    required: false
  },
  /**
   * Largeur de l'image à l'écran (en px)
   */
  width: {
    type: Number,
    required: false
  },
  /**
   * TODO: completer
   */
  ownerId: {
    type: Number,
    required: false
  }
})

const { em } = useEntityManager()
const { imageManager } = useImageManager()
const pageStore = usePageStore()

const emit = defineEmits(['update:modelValue', 'update:image', 'reset'])

/**
 * Références à des composants
 */
const fileInput: Ref<null | any> = ref(null)
const cropper: Ref<any> = ref(null)
const imageElement: Ref<any> = ref(null)

/**
 * L'objet File contenant les informations de l'image
 */
const file: Ref<ApiResource | null> = ref(null)

/**
 * L'image elle-même
 */
const image: Ref<ArrayBuffer | string | null> = ref(null)

/**
 * L'objet File ou l'image sont en cours de chargement
 */
const pending: Ref<boolean> = ref(false)

/**
 * Affiche la modale d'upload / modification de l'image
 */
const showModal = ref(false)

/**
 * Données d'une nouvelle image uploadée par l'utilisateur
 */
const uploadedImage: Ref<AnyJson> = ref({
  id: null,
  src: null,
  file: null,
  name: null
})

const MAX_FILE_SIZE = 4 * 1024 * 1024

/**
 * Coordonnées du cropper
 */
const coordinates: Ref<{ left?: number, top?: number, height?: number, width?: number }> = ref({})

/**
 * @see https://advanced-cropper.github.io/vue-advanced-cropper/components/cropper.html#defaultposition
 */
const defaultPosition = () => {
  return { left : coordinates.value.left, top : coordinates.value.top }
}

/**
 * @see https://advanced-cropper.github.io/vue-advanced-cropper/components/cropper.html#defaultsize
 */
const defaultSize = (params: any): { width: number, height: number } | null => {
  if (!params) {
    return null
  }
  const { imageSize, visibleArea } = params

  return {
    width: coordinates.value.width ?? (visibleArea || imageSize).width,
    height: coordinates.value.height ?? (visibleArea || imageSize).height
  }
}

/**
 * Affiche la modale d'upload / modification de l'image
 */
const openModal = async () => {
  showModal.value = true
  pending.value = true

  if (props.modelValue !== null) {
    // Un objet File existe déjà: on le récupère
    file.value = await em.fetch(File, props.modelValue)
    image.value = await imageManager.get(props.modelValue)

    if (file.value.config) {
      const config = JSON.parse(file.value.config)
      coordinates.value.left = config.x
      coordinates.value.top = config.y
      coordinates.value.height = config.height
      coordinates.value.width = config.width
    }

    uploadedImage.value.name = file.value.name
    uploadedImage.value.id = file.value.id
    uploadedImage.value.src = image.value

  } else {
    // Nouveau File
    file.value = em.newInstance(File)
  }

  pending.value = false
}

/**
 * Réinitialise l'image sélectionnée
 */
const reset = () => {
  uploadedImage.value.src = null
  uploadedImage.value.file = null
  uploadedImage.value.name = null
  uploadedImage.value.id = null
  URL.revokeObjectURL(uploadedImage.value.src)
}

/**
 * Upload une image depuis le poste client
 * @param event
 */
const uploadImage = (event: any) => {
  const { files } = event.target

  if (!files || !files[0]) {
    return
  }

  const uploadedFile = files[0]

  if (uploadedFile.size > MAX_FILE_SIZE) {
    pageStore.alerts.push({type: TYPE_ALERT.ALERT, messages: ['file_too_large'] })
    return
  }

  reset()

  uploadedImage.value.name = files[0].name
  uploadedImage.value.src = URL.createObjectURL(files[0])
  uploadedImage.value.file = files[0]

  coordinates.value.top = 0
  coordinates.value.left = 0
  coordinates.value.height = uploadedImage.value.height
  coordinates.value.width = uploadedImage.value.width
}

/**
 * Lorsque le cropper change de position / taille, on met à jour les coordonnées
 * @param newCoordinates
 */
const onCropperChange = ({ coordinates: newCoordinates } : any) => {
  coordinates.value = newCoordinates;
}

/**
 * Annule l'upload et les modifications, et ferme la modale
 */
const cancel = () => {
  reset()
  showModal.value = false
}

/**
 * Construit la nouvelle config du fichier à partir des réglages actuels
 */
const updateFileConfig = () => {
  file.value!!.config = JSON.stringify({
    x: coordinates.value.left,
    y: coordinates.value.top,
    height: coordinates.value.height,
    width: coordinates.value.width
  })
}

/**
 * Enregistre une nouvelle image
 */
const saveNewImage = async () => {
  if (!file.value) {
    throw new Error('No File object defined')
  }

  // On créé l'objet File à sauvegarder
  file.value.name = uploadedImage.value.name
  file.value.imgFieldName = props.field
  file.value.visibility = FILE_VISIBILITY.EVERYBODY
  file.value.folder = FILE_FOLDER.IMAGES
  file.value.status = FILE_STATUS.READY
  file.value.type = FILE_TYPE.UPLOADED

  updateFileConfig()

  if (props.ownerId) {
    // TODO: revoir
    file.value.ownerId = props.ownerId
  }

  // TODO: A revoir, on doit pouvoir persister l'image aussi
  const returnedFile = await em.persist(File, file.value)
  // await imageManager.persist(file.value, uploadedImage.src)

  //On émet un évent afin de mettre à jour le formulaire de départ
  emit('update:modelValue', returnedFile.id)
}

/**
 * Met à jour l'image existante
 */
const saveExistingImage = async () => {
  if (!file.value) {
    throw new Error('No File object defined')
  }

  updateFileConfig()

  await em.persist(File, file.value) // TODO: à revoir
}

/**
 * Lorsque l'on sauvegarde l'image
 */
// TODO: Voir si tout ou partie de cette fonction peut passer dans le useImageFetch, imageManager ou imageUtils
const save = async () => {
  if (!file.value) {
    throw new Error('No File object defined')
  }
  pageStore.loading = true

  if (uploadedImage.value.src && uploadedImage.value.src !== image.value) {
    // Une nouvelle image a été uploadée
    await saveNewImage()
  } else if (uploadedImage.value.id) {
    // L'image existante a été modifiée
    await saveExistingImage()
  } else {
    // On a reset l'image
    emit('update:modelValue', null)
  }

  imageElement.value.refresh()

  showModal.value = false
  pageStore.loading = false
}

/**
 * Lorsqu'on démonte le component on supprime le watcher et on revoke l'objet URL
 */
onUnmounted(() => {
  if (uploadedImage.value && uploadedImage.value.src) {
    URL.revokeObjectURL(uploadedImage.value.src)
  }
})
</script>

<style scoped lang="scss">
  :deep(.vue-advanced-cropper__stretcher) {
    height: auto !important;
    width: auto !important;
  }

  .loading{
    height: 300px;
  }

  .upload {
    user-select: none;
    padding: 20px;
    display: block;
    &__cropper {
       border: solid 1px rgb(var(--v-theme-on-neutral-strong));;
       min-height: 300px;
       max-height: 300px;
     }
    &__cropper-wrapper {
       position: relative;
     }
    &__reset-button {
      position: absolute;
      right: 20px;
      bottom: 20px;
      cursor: pointer;
      display: flex;
      align-items: center;
      justify-content: center;
      height: 42px;
      width: 42px;
      background: rgb(var(--v-theme-neutral));
      transition: background 0.5s;
      &:hover {
        background: rgb(var(--v-theme-primary-alt));
      }
    }
    &__buttons-wrapper {
       display: flex;
       justify-content: center;
       margin-top: 17px;
     }
    &__button {
       border: none;
       outline: solid transparent;
       color: rgb(var(--v-theme-on-neutral));
       font-size: 16px;
       padding: 10px 20px;
       background: rgb(var(--v-theme-neutral));
       cursor: pointer;
       transition: background 0.5s;
       margin: 0 16px;
      &:hover,
      &:focus {
         background: rgb(var(--v-theme-primary-alt));
       }
      input {
        display: none;
      }
    }
  }

  .max-size-label {
    display: block;
    width: 100%;
    text-align: center;
    font-size: 13px;
    color: rgb(var(--v-theme-on-neutral-soft));
    margin-top: 6px;
  }
</style>
