<template>
  <div
    :class="attrs.class"
    :style="attrs.style"
    @click="pickFile"
    @dragover="handleDragOver"
    @dragleave="handleDragLeave"
    @drop="handleDrop"
  >
    <div v-if="label || $slots.label" class="q-pl-xs">
      <slot name="label">
        <span>{{ label }}</span>
      </slot>
      <span v-if="required" class="text-negative q-ml-xs">*</span>
    </div>
    <q-field
      :model-value="preview"
      v-bind="inputAttrs"
      borderless
      hide-bottom-space
      :rules="rules"
      :error="error"
      :error-message="errorMessage"
      class="image-preview-container"
    >
      <div
        class="file-picker-inner"
        :class="{
          'has-image': preview,
          'is-dragging': isDragging,
        }"
      >
        <template v-if="!preview">
          <q-icon
            :name="
              isDragging
                ? 'mdi-upload'
                : type === 'image'
                  ? 'mdi-image-plus'
                  : 'mdi-file-plus'
            "
            size="48px"
            color="grey-6"
            class="absolute-center"
          />
          <div class="text-caption text-grey-6 text-center absolute-bottom q-pb-sm q-px-md">
            {{
              isDragging
                ? $t("common.ui.file.drag_and_drop")
                : type === "image"
                  ? $t("common.ui.file.click_select_image")
                  : $t("common.ui.file.click_select")
            }}
          </div>
        </template>

        <div
          v-else-if="type === 'image'"
          class="file-picker-image"
          :style="{ backgroundImage: `url('${preview}')` }"
        />

        <div v-else class="column flex-center" style="height: 100%">
          <q-icon name="mdi-file-check" size="48px" color="grey-6" />
          <div class="text-caption text-grey-6 text-center q-mt-sm q-px-md">
            {{ internalFile?.name }}
          </div>
        </div>
      </div>

      <q-file
        v-show="false"
        ref="fileInputRef"
        v-model="internalFile"
        :accept="accept"
      />
    </q-field>
  </div>
</template>

<script setup>
import { ref, watch, computed, onUnmounted, useTemplateRef, useAttrs } from "vue";

defineOptions({
  inheritAttrs: false,
});

const props = defineProps({
  label: {
    type: String,
    default: "Select Image",
  },
  rules: {
    type: Array,
    default: () => [],
  },
  accept: {
    type: String,
    default: "image/*",
  },
  type: {
    type: String,
    default: "image",
  },
  initialImage: {
    type: String,
    default: null,
  },
  error: {
    type: Boolean,
    default: false,
  },
  errorMessage: {
    type: String,
    default: "",
  },
});

const attrs = useAttrs();
const fileInputRef = useTemplateRef("fileInputRef");

const model = defineModel({ type: [File, String, null], default: null });
const base64File = defineModel("base64File", { type: String, default: null });

const isDragging = ref(false);
const internalFile = ref(null);
const objectUrl = ref(null);

const required = computed(() => props.rules.some((r) => r?.$id === "required"));

const preview = computed(() => {
  if (internalFile.value instanceof File) {
    return props.type === "image" ? objectUrl.value : "file_selected";
  }
  return props.initialImage || null;
});

const inputAttrs = computed(() => {
  // eslint-disable-next-line
  const { class: _, style: __, ...rest } = attrs;
  return rest;
});

function pickFile() {
  fileInputRef.value?.pickFiles();
}

function handleDragOver(event) {
  event.preventDefault();
  event.dataTransfer.dropEffect = "copy";
  isDragging.value = true;
}

function handleDragLeave() {
  isDragging.value = false;
}

function handleDrop(event) {
  event.preventDefault();
  isDragging.value = false;
  const file = event.dataTransfer?.files?.[0];
  if (!file) return;
  const acceptedMime = props.accept;
  if (acceptedMime.endsWith("/*")) {
    const baseMime = acceptedMime.replace("/*", "");
    if (file.type.startsWith(baseMime + "/")) internalFile.value = file;
  } else {
    if (acceptedMime.split(",").map((m) => m.trim()).includes(file.type)) {
      internalFile.value = file;
    }
  }
}

function generateBase64(file) {
  if (!file) { base64File.value = null; return; }
  const reader = new FileReader();
  reader.onload = (e) => { base64File.value = e.target.result; };
  reader.onerror = () => { base64File.value = null; };
  reader.readAsDataURL(file);
}

watch(internalFile, (newFile) => {
  if (objectUrl.value) {
    URL.revokeObjectURL(objectUrl.value);
    objectUrl.value = null;
  }
  if (newFile instanceof File) {
    if (props.type === "image") {
      objectUrl.value = URL.createObjectURL(newFile);
    }
    model.value = newFile;
    generateBase64(newFile);
  } else {
    model.value = null;
    base64File.value = null;
  }
});

watch(model, (val) => {
  if (!val && internalFile.value) {
    internalFile.value = null;
  }
});

onUnmounted(() => {
  if (objectUrl.value) {
    URL.revokeObjectURL(objectUrl.value);
    objectUrl.value = null;
  }
});
</script>

<style lang="scss">
@use "sass:map";
@use "src/css/quasar.variables.scss";

.image-preview-container {
  display: flex;
  justify-content: center;
  align-items: stretch;

  .q-field__inner {
    .body--light & {
      --image-bg-color: #{map.get($colors, "surface")};
      --image-border-color: #{map.get($colors, "violet-normal")};
      --image-border-hover-color: #{map.get($colors, "violet-normal-hover")};
    }

    height: 100%;
    display: flex;
    flex-direction: column;
    transition: border-color 0.3s;
  }

  .q-field__control {
    height: 100%;
    min-height: 200px;
    border: 2px dashed var(--image-border-color);
    border-radius: 8px;
    cursor: pointer;
    overflow: hidden;

    &:has(.has-image) {
      border-style: solid;
      border-color: var(--image-border-color);
    }

    &:has(.is-dragging) {
      border-color: var(--image-border-hover-color);
      opacity: 0.8;
    }
  }

  .q-field__control-container {
    height: 100%;
    justify-content: stretch;
    align-items: stretch;
  }

  .q-field__append {
    position: absolute;
    top: -5px;
    right: 10px;
  }

  .file-picker-inner {
    position: relative;
    width: 100%;
    height: 100%;
    min-height: 196px;
  }

  .file-picker-image {
    position: absolute;
    inset: 0;
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    border-radius: 6px;
  }
}
</style>
