<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
      v-model="model"
      v-bind="inputAttrs"
      borderless
      hide-bottom-space
      :rules="rules"
      :error="error"
      :error-message="errorMessage"
      class="image-preview-container"
    >
      <div
        class=""
        :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>

        <q-img
          v-else-if="type === 'image'"
          :src="preview"
          fit="cover"
          class="full-height"
        />

        <div v-else class="position-relative column full-height flex-center">
          <q-icon name="mdi-file-check" size="48px" color="grey-6" />
          <div
            class="absolute-bottom text-caption text-grey-6 text-center q-mb-sm q-px-md"
          >
            {{ model.name }}
          </div>
        </div>

        <div v-if="preview" class="absolute-top-right q-ma-xs">
          <q-btn
            flat
            dense
            round
            color="negative"
            icon="mdi-close"
            @click.stop="clearFile"
          />
        </div>
      </div>

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

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

defineOptions({
  inheritAttrs: false,
});

const { label, rules, accept, type, initialImage } = 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 preview = ref(initialImage || null);
const required = ref(false);

let objectUrl = null;

const cleanupObjectURL = () => {
  if (objectUrl) {
    URL.revokeObjectURL(objectUrl);
    objectUrl = null;
  }
};

const generateBase64 = (file) => {
  if (!file) {
    base64File.value = null;
    return;
  }
  const reader = new FileReader();
  reader.onload = (e) => {
    base64File.value = e.target.result;
  };
  reader.onerror = () => {
    console.error("FileReader failed to read file.");
    base64File.value = null;
  };
  reader.readAsDataURL(file);
};

const pickFile = () => {
  fileInputRef.value?.pickFiles();
};

const clearFile = () => {
  model.value = null;
};

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

const handleDragLeave = () => {
  isDragging.value = false;
};

const handleDrop = (event) => {
  event.preventDefault();
  isDragging.value = false;

  const file = event.dataTransfer?.files?.[0];
  if (!file) return;

  const acceptedMime = accept;

  if (acceptedMime.endsWith("/*")) {
    const baseMime = acceptedMime.replace("/*", "");
    if (file.type.startsWith(baseMime + "/")) {
      model.value = file;
    }
  } else {
    if (
      acceptedMime
        .split(",")
        .map((m) => m.trim())
        .includes(file.type)
    ) {
      model.value = file;
    }
  }
};

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

watch(model, (newFile) => {
  cleanupObjectURL();

  if (newFile && newFile instanceof File) {
    if (type === "image") {
      objectUrl = URL.createObjectURL(newFile);
      preview.value = objectUrl;
    } else {
      preview.value = "file_selected";
    }
    generateBase64(newFile);
  } else {
    preview.value = initialImage || null;
    base64File.value = null;
  }
});

watch(
  () => rules,
  (values) => {
    values.forEach((r) => {
      if (r?.$id === "required") return (required.value = true);
    });
  },
);

onBeforeMount(() => {
  rules.forEach((r) => {
    if (r?.$id === "required") return (required.value = true);
  });
});

onUnmounted(cleanupObjectURL);
</script>

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

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

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

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

    display: flex;
    flex-direction: column;
    transition: all 0.3s;

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

    &.has-image {
      border-style: solid;
      margin: auto;
    }
  }
  .q-field__control {
    height: 100%;
    min-height: 200px;
    border: 2px dashed var(--image-border-color);
    border-radius: 8px;
    cursor: pointer;
  }

  .q-field__control-container {
    justify-content: center;
  }

  .q-field__append {
    position: absolute;
    top: -5px;
    right: 10px;
  }
}
</style>
