<script setup>
import { castArray } from "lodash-es";
import { ref, shallowRef, watch, computed } from "vue";
import { VTextField } from "vuetify/components/VTextField";
import { useAlert } from "../../composables/alert";
import {} from "../../composables/assetFile";
import { useAssetFile } from "../../composables/assetFile";
const alert = useAlert();
const { hydrateAssetFile, base64ToFile, fileToBase64, downloadBase64File } = useAssetFile();
const fileToBase64WithMaxSize = (file) => fileToBase64(file, props.maxSize);
const props = defineProps({
  accept: { type: String, required: false, default: "*" },
  multiple: { type: Boolean, required: false, default: false },
  maxSize: { type: Number, required: false, default: 5 },
  modelValue: { type: [Object, Array, null], required: false },
  downloadable: { type: Boolean, required: false, default: false },
  autoHydrate: { type: Boolean, required: false, default: false }
});
const emit = defineEmits(["update:modelValue"]);
const fileInput = ref(null);
const assets = ref([]);
const files = shallowRef([]);
const fileCache = /* @__PURE__ */ new WeakMap();
let internalSync = false;
function base64FileKey(x) {
  if (x.id != null) return `id:${x.id}`;
  const name = x.fileName ?? "";
  const len = x.base64String?.length ?? 0;
  return `n:${name}|l:${len}`;
}
function uniqByKey(arr) {
  const seen = /* @__PURE__ */ new Set();
  const out = [];
  for (const it of arr) {
    const k = base64FileKey(it);
    if (!seen.has(k)) {
      seen.add(k);
      out.push(it);
    }
  }
  return out;
}
function arraysEqualByKey(a, b) {
  if (a === b) return true;
  if (a.length !== b.length) return false;
  for (let i = 0; i < a.length; i++) {
    if (base64FileKey(a[i]) !== base64FileKey(b[i])) return false;
  }
  return true;
}
async function syncFromModel() {
  const mv = props.modelValue;
  if (!mv) {
    assets.value = [];
    files.value = [];
    return;
  }
  const asArray = castArray(mv);
  const incomingAssets = asArray.filter((a) => a.id != null);
  const inlineBase64 = asArray.filter((a) => a.id == null && a.base64String);
  const inlineFiles = [];
  for (const item of inlineBase64) {
    const f = base64ToFile(item.base64String, item.fileName);
    if (f) inlineFiles.push(f);
  }
  assets.value = incomingAssets;
  files.value = inlineFiles;
  if (props.autoHydrate) {
    const needsHydration = assets.value.filter((a) => a.id != null && !a.base64String);
    if (needsHydration.length) {
      await Promise.allSettled(needsHydration.map(hydrateAssetFile));
    }
  }
}
function openWindowUpload() {
  if (props.multiple || assets.value.length === 0 && files.value.length === 0) {
    fileInput.value?.click();
  }
}
function addFiles(payload) {
  if (Array.isArray(payload)) files.value = [...files.value, ...payload];
  else files.value = [...files.value, payload];
}
function removeFileByIndex(i) {
  const idx = Number(i);
  if (idx >= 0 && idx < files.value.length) {
    const copy = files.value.slice();
    copy.splice(idx, 1);
    files.value = copy;
  }
}
function removeAssetByIndex(i) {
  const idx = Number(i);
  if (idx >= 0 && idx < assets.value.length) {
    const copy = assets.value.slice();
    copy.splice(idx, 1);
    assets.value = copy;
  }
}
async function filesToBase64Files(list) {
  const tasks = list.map((f) => {
    let p = fileCache.get(f);
    if (!p) {
      p = fileToBase64WithMaxSize(f);
      fileCache.set(f, p);
    }
    return p;
  });
  return Promise.all(tasks);
}
const combinedArray = ref([]);
const isDirty = computed(
  () => props.multiple ? combinedArray.value.length > 0 : combinedArray.value[0] != null
);
watch(
  () => props.modelValue,
  () => {
    if (internalSync) return;
    void syncFromModel();
  },
  { deep: true, immediate: true }
);
watch([assets, files], async () => {
  try {
    const base64FromFiles = await filesToBase64Files(files.value);
    const mergedArray = props.multiple ? [...assets.value, ...base64FromFiles] : assets.value[0] ?? base64FromFiles[0] ?? null ? [assets.value[0] ?? base64FromFiles[0]] : [];
    const nextCombined = props.multiple ? uniqByKey(mergedArray) : mergedArray;
    if (!arraysEqualByKey(combinedArray.value, nextCombined)) {
      combinedArray.value = nextCombined;
      internalSync = true;
      try {
        if (props.multiple) {
          emit("update:modelValue", combinedArray.value);
        } else {
          emit("update:modelValue", combinedArray.value[0] ?? null);
        }
      } finally {
        queueMicrotask(() => {
          internalSync = false;
        });
      }
    }
  } catch (error) {
    alert?.addAlert({ message: String(error), alertType: "error" });
    files.value = [];
    const fallback = props.multiple ? [...assets.value] : assets.value[0] ? [assets.value[0]] : [];
    if (!arraysEqualByKey(combinedArray.value, fallback)) {
      combinedArray.value = fallback;
      internalSync = true;
      try {
        if (props.multiple) emit("update:modelValue", combinedArray.value);
        else emit("update:modelValue", combinedArray.value[0] ?? null);
      } finally {
        queueMicrotask(() => {
          internalSync = false;
        });
      }
    }
  }
}, { deep: true, immediate: true });
</script>

<template>
  <v-text-field
      v-bind="$attrs"
      label="Upload files"
      readonly
      :dirty="isDirty"
      v-on="isDirty && !props.multiple ? {} : { click: openWindowUpload }"
  >
    <template #default>
      <!-- Server/asset items -->
      <v-chip
          v-for="(asset, index) in assets"
          :key="`${asset.id ?? asset.fileName}-${index}`"
          color="green"
          variant="flat"
          closable
          @click:close="removeAssetByIndex(index)"
      >
        {{ asset.originalFileName || asset.fileName }}
        <template #append v-if="props.downloadable">
          <slot name="download" :item="asset">
            <v-icon
                v-if="asset.base64String"
                @click.stop="downloadBase64File(asset.base64String, asset.originalFileName || asset.fileName)"
            >
              mdi mdi-download
            </v-icon>
          </slot>
        </template>
      </v-chip>

      <!-- Local uploaded files -->
      <v-chip
          v-for="(file, index) in files"
          :key="`${file.name}-${file.size}-${index}`"
          color="primary"
          variant="flat"
          closable
          @click:close="removeFileByIndex(index)"
      >
        {{ file.name }}
      </v-chip>
    </template>

    <!-- Add more button for multi mode -->
    <template v-if="props.multiple && combinedArray.length > 0" #append-inner>
      <VBtn variant="text" :icon="true" @click="openWindowUpload">
        <v-icon>mdi mdi-plus</v-icon>
      </VBtn>
    </template>
  </v-text-field>

  <v-file-input
      ref="fileInput"
      :accept="props.accept"
      :multiple="props.multiple"
      style="display: none"
      @update:model-value="addFiles"
  />
</template>
