<script setup>
import { ref, watch, computed } from "vue";
import { isEqual } from "lodash-es";
import { VInput } from "vuetify/components/VInput";
import { assetUrl } from "#imports";
import { useAlert } from "../../../composables/alert";
import { useAssetFile } from "../../../composables/assetFile";
const emit = defineEmits(["update:modelValue"]);
const alert = useAlert();
const { fileToBase64, hydrateAssetFile } = useAssetFile();
const props = defineProps({
  modelValue: { type: Array, required: false, default: () => [] },
  readonly: { type: Boolean, required: false },
  label: { type: String, required: false },
  accept: { type: String, required: false, default: ".jpg,.jpeg,.png,.webp,.gif,.bmp,.tiff,.tif" },
  autoHydrate: { type: Boolean, required: false, default: false },
  maxFileSize: { type: Number, required: false, default: 10 }
});
const images = ref([]);
const uploadImages = ref([]);
const dialog = ref(false);
const dialogUpdate = ref(false);
const dataUpdateIndex = ref(null);
const dialogImageFullScreen = ref(false);
const imageFullScreen = ref({ title: "", image: "" });
let internalSync = false;
let lastEmittedSig = "";
function imageKey(im) {
  const id = im.imageData?.id;
  if (id != null) return `id:${id}`;
  const title = im.imageTitle ?? "";
  const len = im.imageData?.base64String?.length ?? 0;
  return `t:${title}|l:${len}`;
}
function signature(arr) {
  return arr.map(imageKey).join("|");
}
function emitNow(next) {
  const sig = signature(next);
  if (sig === lastEmittedSig) return;
  internalSync = true;
  try {
    emit("update:modelValue", next);
    lastEmittedSig = sig;
  } finally {
    queueMicrotask(() => {
      internalSync = false;
    });
  }
}
const addImage = (img) => {
  images.value.push({
    imageData: img.imageData ?? {},
    imageTitle: img.imageTitle ?? "",
    imageProps: img.imageProps ?? {}
  });
  dialog.value = false;
};
const remove = (index) => {
  images.value.splice(index, 1);
};
const setDataUpdate = (index) => {
  if (index < 0 || index >= images.value.length) return;
  dataUpdateIndex.value = index;
  dialogUpdate.value = true;
};
const closeUpdateDialog = () => {
  dialogUpdate.value = false;
  dataUpdateIndex.value = null;
};
const checkDuplicationName = (name) => images.value.some(({ imageTitle }) => isEqual(imageTitle, name));
const isImageDataUrl = (dataUrl) => /^data:image\//i.test(dataUrl);
const imageSrcFromImageData = (imageData) => {
  if (imageData?.imageData?.base64String) return useAssetFile().ensureDataUrl(imageData?.imageData?.base64String.trim(), (imageData?.imageData).fileType || "image/png");
  if (imageData?.imageData?.id != null) return assetUrl(imageData.imageData.id);
  return void 0;
};
const fileToBase64Image = async (file) => {
  try {
    const base64 = await fileToBase64(file, props.maxFileSize);
    const dataUrl = base64.base64String || "";
    if (!isImageDataUrl(dataUrl)) {
      alert?.addAlert({ message: `File "${base64.fileName}" is not supported image type.`, alertType: "error" });
      return null;
    }
    return {
      imageData: { base64String: dataUrl },
      imageTitle: base64.fileName,
      imageProps: {}
    };
  } catch (e) {
    alert?.addAlert({ message: String(e), alertType: "error" });
    return null;
  }
};
const uploadImageFile = async () => {
  const duplicated = [];
  for (const file of uploadImages.value) {
    if (checkDuplicationName(file.name)) {
      duplicated.push(file.name);
      continue;
    }
    const base64Image = await fileToBase64Image(file);
    if (base64Image) addImage(base64Image);
  }
  uploadImages.value = [];
  if (duplicated.length) {
    alert?.addAlert({ message: `File(s) are duplicated. ${duplicated.join(", ")}`, alertType: "error" });
  }
};
const modelData = ref();
const captureImage = (payload, cb) => {
  const dataUrl = payload?.imageCapture ?? "";
  if (!dataUrl || !isImageDataUrl(dataUrl)) {
    alert?.addAlert({ message: "Invalid image.", alertType: "error" });
    return;
  }
  addImage({
    imageData: { base64String: dataUrl },
    imageTitle: Math.random().toString(36).slice(2, 11),
    imageProps: {}
  });
  cb?.done?.();
};
const openImageFullScreen = (img) => {
  dialogImageFullScreen.value = true;
  imageFullScreen.value.title = img.imageTitle ?? "";
  imageFullScreen.value.image = imageSrcFromImageData(img);
};
watch(
  () => props.modelValue,
  async (val) => {
    if (internalSync) return;
    const next = Array.isArray(val) ? [...val] : [];
    const nextSig = signature(next);
    if (nextSig !== signature(images.value)) {
      images.value = next;
      if (props.autoHydrate && images.value.length) {
        const targets = images.value.filter(
          (im) => im.imageData?.id != null && !im.imageData?.base64String
        );
        if (targets.length) {
          await Promise.allSettled(targets.map((im) => hydrateAssetFile(im.imageData)));
          emitNow(images.value);
        }
      }
      lastEmittedSig = signature(images.value);
    }
  },
  { deep: true, immediate: true }
);
watch(
  () => signature(images.value),
  () => {
    if (internalSync) return;
    emitNow(images.value);
  },
  { immediate: false }
);
const inputRef = ref(null);
const isValid = computed(() => inputRef.value?.isValid);
const errorMessages = computed(() => inputRef.value?.errorMessages);
defineExpose({
  errorMessages,
  isValid,
  reset: () => inputRef.value?.reset(),
  resetValidation: () => inputRef.value?.resetValidation(),
  validate: () => inputRef.value?.validate()
});
</script>

<template>
  <v-input v-model="images" v-bind="$attrs" ref="inputRef">
    <template #default="{ isReadonly, isDisabled }">
      <v-container fluid>
        <v-row>
          <v-col class="pa-0">
            <VCard>
              <VToolbar density="compact">
                <VToolbarTitle>{{ label }}</VToolbarTitle>
                <v-spacer />
                <VToolbarItems v-if="!readonly">
                  <FileBtn
                      v-model="uploadImages"
                      :accept="accept"
                      color="primary"
                      icon="mdi:mdi-image-plus"
                      icon-only
                      multiple
                      variant="text"
                      @update:model-value="uploadImageFile"
                      :disabled="isDisabled?.value" :readonly="isReadonly?.value"
                  />
                  <v-btn color="primary" icon @click="dialog = true" :disabled="isDisabled?.value" :readonly="isReadonly?.value">
                    <v-icon>mdi mdi-camera-plus</v-icon>
                  </v-btn>
                </VToolbarItems>
              </VToolbar>

              <VCardText>
                <VRow density="compact" justify="start">
                  <VCol v-for="(image, index) in images" :key="`${imageKey(image)}-${index}`" cols="4">
                    <VCard>
                      <VToolbar density="compact">
                        <VToolbarTitle>
                          {{ image.imageTitle }}
                        </VToolbarTitle>
                        <VSpacer />
                        <VToolbarItems v-if="!readonly">
                          <v-btn icon @click="remove(index)" :disabled="isDisabled?.value" :readonly="isReadonly?.value">
                            <v-icon>mdi mdi-delete-outline</v-icon>
                          </v-btn>
                          <v-btn color="primary" icon @click="setDataUpdate(index)" v-if="!image.imageData?.id" :disabled="isDisabled?.value" :readonly="isReadonly?.value">
                            <v-icon>mdi mdi-image-edit-outline</v-icon>
                          </v-btn>
                        </VToolbarItems>
                      </VToolbar>

                      <v-img
                          :src="imageSrcFromImageData(image)"
                          height="250"
                          @click="() => {
  props.readonly || image.imageData?.id || isReadonly?.value ? openImageFullScreen(image) : setDataUpdate(index);
}"
                          :disabled="isDisabled?.value"
                      />
                    </VCard>
                  </VCol>
                </VRow>
              </VCardText>
            </VCard>
          </v-col>
        </v-row>
      </v-container>

      <!-- Edit dialog -->
      <VDialog v-model="dialogUpdate" fullscreen transition="dialog-bottom-transition">
        <FormImagesPad
          v-if="dialogUpdate && dataUpdateIndex !== null && images[dataUpdateIndex]"
          v-model="images[dataUpdateIndex].imageData.base64String"
          @closedDialog="closeUpdateDialog"
        />
      </VDialog>

      <!-- Capture dialog -->
      <FormDialog v-model="dialog" :form-data="modelData" @create="captureImage">
        <template #default="{ data }">
          <FormImagesCapture v-model="data.imageCapture" />
        </template>
      </FormDialog>

      <!-- Fullscreen preview -->
      <v-dialog v-model="dialogImageFullScreen">
        <v-toolbar :title="imageFullScreen.title">
          <v-spacer />
          <v-btn icon="mdi mdi-close" @click="dialogImageFullScreen = false" />
        </v-toolbar>
        <v-card height="80vh">
          <v-img :src="imageFullScreen.image" />
        </v-card>
      </v-dialog>
    </template>
  </v-input>
</template>
