<script setup lang="ts">
import { ref, computed, type Component } from "vue";
import { usePage } from "@inertiajs/vue3";
import { registry } from "../../registry";
import { useBuilderPrefix } from "../../composables/useBuilderPrefix";
import { useCsrfToken } from "../../composables/useCsrfToken";
import type { FieldDefinition } from "./types";

type UploadedFile = {
  path: string;
  url: string;
  filename: string;
  size: number;
  mime: string;
};

const props = defineProps<{
  field: FieldDefinition;
  modelValue?: unknown;
  error?: string;
}>();

const emit = defineEmits<{
  "update:modelValue": [value: string | string[] | number | number[] | null];
}>();

const page = usePage();
const hasMedia = computed(
  () => !!(page.props as Record<string, unknown>).hasMedia,
);

const prefix = useBuilderPrefix();

// Media mode: modelValue is media ID(s)
const selectedMediaIds = ref<number[]>(
  (() => {
    if (!hasMedia.value) return [];
    if (!props.modelValue) return [];
    if (Array.isArray(props.modelValue))
      return (props.modelValue as number[]).filter(
        (v) => typeof v === "number",
      );
    if (typeof props.modelValue === "number") return [props.modelValue];
    return [];
  })(),
);

// File upload mode: modelValue is file paths
const dragging = ref(false);
const uploading = ref(false);
const uploadProgress = ref<Record<string, number>>({});
const uploadErrors = ref<string[]>([]);

const uploaded = ref<UploadedFile[]>(
  (() => {
    if (hasMedia.value) return [];
    if (!props.modelValue) return [];
    if (Array.isArray(props.modelValue))
      return props.modelValue as UploadedFile[];
    return [];
  })(),
);

// Active tab when media is available
const activeTab = ref<"upload" | "browse">("upload");

// Resolve MediaPickerModal dynamically from module registry
const MediaPickerModal = computed<Component | null>(() => {
  return registry.resolveComponent("MediaPickerModal");
});

function acceptAttr(): string {
  return props.field.accept.join(",");
}

function isImage(mime: string): boolean {
  return mime.startsWith("image/");
}

function formatSize(bytes: number): string {
  if (bytes < 1024) return `${bytes} B`;
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}

function emitFileValue() {
  if (props.field.multiple) {
    emit(
      "update:modelValue",
      uploaded.value.map((f) => f.path),
    );
  } else {
    emit("update:modelValue", uploaded.value[0]?.path ?? null);
  }
}

function emitMediaValue(ids: number[]) {
  if (props.field.multiple) {
    emit("update:modelValue", ids);
  } else {
    emit("update:modelValue", ids[0] ?? null);
  }
}

function onMediaSelected(ids: number | number[] | null) {
  if (ids === null) {
    selectedMediaIds.value = [];
  } else if (Array.isArray(ids)) {
    selectedMediaIds.value = ids;
  } else {
    selectedMediaIds.value = [ids];
  }
  emitMediaValue(selectedMediaIds.value);
}

function removeMediaId(index: number) {
  selectedMediaIds.value.splice(index, 1);
  emitMediaValue(selectedMediaIds.value);
}

async function uploadFile(file: File): Promise<void> {
  const key = file.name + file.size;
  uploadProgress.value[key] = 0;

  const formData = new FormData();
  formData.append("file", file);

  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open("POST", `/${prefix}/upload`);
    xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");

    // Include CSRF token
    const csrf = useCsrfToken();
    if (csrf) {
      xhr.setRequestHeader("X-CSRF-TOKEN", csrf);
    }

    xhr.upload.onprogress = (e) => {
      if (e.lengthComputable) {
        uploadProgress.value[key] = Math.round((e.loaded / e.total) * 100);
      }
    };

    xhr.onload = () => {
      delete uploadProgress.value[key];
      if (xhr.status === 200 || xhr.status === 201) {
        const data: UploadedFile = JSON.parse(xhr.responseText);
        uploaded.value.push(data);
        emitFileValue();
        resolve();
      } else {
        let msg = "Upload failed";
        try {
          const err = JSON.parse(xhr.responseText);
          msg = err.message ?? err.errors?.file?.[0] ?? msg;
        } catch {
          // ignore
        }
        uploadErrors.value.push(msg);
        reject(new Error(msg));
      }
    };

    xhr.onerror = () => {
      delete uploadProgress.value[key];
      const msg = "Network error during upload";
      uploadErrors.value.push(msg);
      reject(new Error(msg));
    };

    xhr.send(formData);
  });
}

async function handleFiles(files: FileList | File[]) {
  uploadErrors.value = [];
  uploading.value = true;

  const list = Array.from(files);

  if (!props.field.multiple) {
    // single mode: replace
    uploaded.value = [];
  }

  await Promise.allSettled(list.map(uploadFile));
  uploading.value = false;
}

function onInputChange(event: Event) {
  const input = event.target as HTMLInputElement;
  if (input.files?.length) {
    handleFiles(input.files);
    input.value = "";
  }
}

function onDrop(event: DragEvent) {
  dragging.value = false;
  const files = event.dataTransfer?.files;
  if (files?.length) {
    handleFiles(files);
  }
}

function removeFile(index: number) {
  uploaded.value.splice(index, 1);
  emitFileValue();
}
</script>

<template>
  <div class="space-y-2">
    <!-- Tab bar when media package is active -->
    <div v-if="hasMedia" class="flex rounded-md border border-input text-sm">
      <button
        type="button"
        :class="[
          'flex-1 rounded-l-md px-3 py-1.5 font-medium transition-colors',
          activeTab === 'upload'
            ? 'bg-primary text-primary-foreground'
            : 'text-muted-foreground hover:bg-muted',
        ]"
        @click="activeTab = 'upload'"
      >
        Upload new
      </button>
      <button
        type="button"
        :class="[
          'flex-1 rounded-r-md px-3 py-1.5 font-medium transition-colors',
          activeTab === 'browse'
            ? 'bg-primary text-primary-foreground'
            : 'text-muted-foreground hover:bg-muted',
        ]"
        @click="activeTab = 'browse'"
      >
        Browse Library
      </button>
    </div>

    <!-- Browse Library tab (media mode) -->
    <template v-if="hasMedia && activeTab === 'browse'">
      <component
        :is="MediaPickerModal"
        v-if="MediaPickerModal"
        :field="field"
        :model-value="
          field.multiple ? selectedMediaIds : (selectedMediaIds[0] ?? null)
        "
        @update:model-value="onMediaSelected"
      />
      <p v-else class="text-sm text-muted-foreground">
        Media library not available.
      </p>
      <!-- Selected media chips -->
      <div v-if="selectedMediaIds.length > 0" class="flex flex-wrap gap-2">
        <div
          v-for="(id, i) in selectedMediaIds"
          :key="id"
          class="group relative flex items-center gap-1 rounded-md border bg-muted px-2 py-1 text-xs"
        >
          <span class="text-muted-foreground">Media #{{ id }}</span>
          <button
            type="button"
            class="ml-1 text-muted-foreground hover:text-destructive"
            :aria-label="`Remove media ${id}`"
            @click="removeMediaId(i)"
          >
            ×
          </button>
        </div>
      </div>
    </template>

    <!-- Upload tab (file mode, or always shown when no media package) -->
    <template v-if="!hasMedia || activeTab === 'upload'">
      <!-- Drop zone -->
      <label
        :for="`${field.name}_input`"
        :class="[
          'flex cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed p-6 text-sm transition-colors',
          dragging
            ? 'border-primary bg-primary/5'
            : 'border-input hover:border-primary/50 hover:bg-muted/30',
          uploading && 'opacity-60 cursor-wait',
        ]"
        @dragover.prevent="dragging = true"
        @dragleave="dragging = false"
        @drop.prevent="onDrop"
      >
        <svg
          class="mb-2 h-8 w-8 text-muted-foreground"
          fill="none"
          viewBox="0 0 24 24"
          stroke="currentColor"
        >
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            stroke-width="1.5"
            d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5"
          />
        </svg>
        <span class="font-medium text-foreground">
          {{ uploading ? "Uploading…" : "Drop files here or click to browse" }}
        </span>
        <span
          v-if="field.accept.length"
          class="mt-1 text-xs text-muted-foreground"
        >
          Accepted: {{ field.accept.join(", ") }}
        </span>
        <input
          :id="`${field.name}_input`"
          type="file"
          class="sr-only"
          :name="field.name"
          :multiple="field.multiple"
          :accept="acceptAttr()"
          :disabled="uploading"
          @change="onInputChange"
        />
      </label>

      <!-- Upload progress -->
      <div v-if="Object.keys(uploadProgress).length > 0" class="space-y-1">
        <div
          v-for="(pct, key) in uploadProgress"
          :key="key"
          class="flex items-center gap-2 text-xs text-muted-foreground"
        >
          <div class="h-1.5 flex-1 overflow-hidden rounded-full bg-muted">
            <div
              class="h-full bg-primary transition-all"
              :style="{ width: `${pct}%` }"
            />
          </div>
          <span>{{ pct }}%</span>
        </div>
      </div>

      <!-- Upload errors -->
      <p
        v-for="(err, i) in uploadErrors"
        :key="i"
        class="text-sm text-destructive"
      >
        {{ err }}
      </p>

      <!-- Uploaded files -->
      <div v-if="uploaded.length > 0" class="flex flex-wrap gap-2">
        <div
          v-for="(file, i) in uploaded"
          :key="file.path"
          class="group relative overflow-hidden rounded-md border bg-muted"
        >
          <!-- Image preview -->
          <img
            v-if="isImage(file.mime)"
            :src="file.url"
            :alt="file.filename"
            class="h-20 w-20 object-cover"
          />
          <!-- File chip -->
          <div v-else class="flex h-20 w-40 flex-col justify-center px-3 py-2">
            <span class="truncate text-xs font-medium text-foreground">{{
              file.filename
            }}</span>
            <span class="text-xs text-muted-foreground">{{
              formatSize(file.size)
            }}</span>
          </div>

          <!-- Remove button -->
          <button
            type="button"
            class="absolute right-1 top-1 flex h-5 w-5 items-center justify-center rounded-full bg-destructive text-destructive-foreground opacity-0 transition-opacity group-hover:opacity-100"
            :aria-label="`Remove ${file.filename}`"
            @click="removeFile(i)"
          >
            <svg
              class="h-3 w-3"
              fill="none"
              viewBox="0 0 24 24"
              stroke="currentColor"
              stroke-width="2.5"
            >
              <path
                stroke-linecap="round"
                stroke-linejoin="round"
                d="M6 18L18 6M6 6l12 12"
              />
            </svg>
          </button>
        </div>
      </div>
    </template>
  </div>
</template>
