<script setup>
import { ref, computed, onMounted } from "vue";
import { useRouter } from "vue-router";
import { api } from "../api";
import { prefs } from "../prefs";
import { formatDate, formatKm, serviceStatus } from "../lib/format.js";
import { t, tSplit } from "../i18n";
import CarFormModal from "../components/CarFormModal.vue";
import CarImportModal from "../components/CarImportModal.vue";

const router = useRouter();
const cars = ref([]);
const loading = ref(true);
const error = ref("");
const showAdd = ref(false);
const showImport = ref(false);

// Importing only makes sense once a manufacturer account is connected, so the
// button appears only then rather than leading to a dead end.
const canImport = ref(false);

async function load() {
  loading.value = true;
  error.value = "";
  try {
    const list = await api.listCars();
    // For each car, fetch its latest service record to derive next-due status.
    cars.value = await Promise.all(
      list.map(async (car) => {
        const services = await api.listCarServices(car.id);
        const latest = services[0] || null; // API sorts newest-first
        return { ...car, latest, count: services.length };
      })
    );
  } catch (e) {
    error.value = e.message;
  } finally {
    loading.value = false;
  }
}

// --- Drag to rearrange the garage ---
//
// The order is a per-user list of car ids on the profile, so it also covers
// cars shared with you and never reorders anybody else's garage. The API
// already returns the list in that order; a drop just sends the new one back.
// Hand-rolled on the native HTML5 drag events rather than pulling in a drag
// library for one screen — which does mean it is pointer-only, as touch
// browsers don't fire these.
//
// The rail's lock holds the garage still, for the reader who keeps nudging a
// card while scrolling. It is that user's own setting, so it hides the cursor
// and the hint as well as the drag itself.
const canArrangeCars = computed(() => !prefs.dragLocked && cars.value.length > 1);
const dragId = ref(""); // card being dragged
const dropId = ref(""); // card it is currently hovering over
const orderError = ref("");
let moved = false; // the grid changed during this drag and isn't saved yet

function onDragStart(car, e) {
  dragId.value = car.id;
  moved = false;
  e.dataTransfer.effectAllowed = "move";
  // Firefox only starts a drag once something is on the transfer.
  e.dataTransfer.setData("text/plain", car.id);
}

// Reorder live as the pointer crosses cards, so the grid shows the arrangement
// you are about to get instead of only settling after the drop. dragenter fires
// again for every child element the pointer touches inside the same card, so
// the card being hovered is remembered and only a genuinely new one moves
// anything — otherwise a slow drag across one card would shuffle it repeatedly.
function onDragEnter(car) {
  if (!dragId.value || car.id === dragId.value || dropId.value === car.id) return;
  dropId.value = car.id;
  const list = cars.value;
  const from = list.findIndex((c) => c.id === dragId.value);
  const to = list.findIndex((c) => c.id === car.id);
  if (from < 0 || to < 0) return;
  // `to` is the target's index before the removal, which lands the card in the
  // target's slot when dragging backwards and just past it when dragging
  // forwards — in both cases where it was dropped.
  list.splice(to, 0, ...list.splice(from, 1));
  moved = true;
}

// Save whatever the grid now shows. Called from both drop and dragend: a card
// released over a gap between cards never produces a drop, and leaving that
// arrangement unsaved would quietly undo itself on the next load.
async function commitOrder() {
  dragId.value = "";
  dropId.value = "";
  if (!moved) return;
  moved = false;
  orderError.value = "";
  try {
    await api.updateMe({ carOrder: cars.value.map((c) => c.id) });
  } catch (e) {
    // The arrangement didn't stick; say so and reload the stored one rather
    // than leaving the screen showing an order the server doesn't have.
    orderError.value = e.message;
    await load();
  }
}

function onSaved(car) {
  showAdd.value = false;
  router.push({ name: "car", params: { id: car.id } });
}

// An imported car lands on its own page like a hand-added one. A warning means a
// field the service couldn't supply (an odometer it doesn't report); the car is
// created either way, so say what to fill in rather than block the import.
function onImported(car, warnings) {
  showImport.value = false;
  if (warnings?.includes("odometer")) alert(t("forms.import.warningOdometer"));
  router.push({ name: "car", params: { id: car.id } });
}

// Service-life progress: how far the car is through its km service interval.
// Returns a { pct, tone } or null when there isn't enough data to compute it.
const TONE_COLOR = {
  ok: "var(--success-600)",
  soon: "var(--warning-600)",
  overdue: "var(--danger-600)",
  unknown: "var(--ink-300)",
};
function serviceLife(car) {
  const interval = Number(car.serviceIntervalKm);
  const nextKm = Number(car.latest?.nextServiceKm);
  const currentKm = Number(car.currentKm);
  // A new car reads 0 and belongs at 0% of its interval, not hidden entirely.
  if (!interval || !nextKm || !Number.isFinite(currentKm)) return null;
  const remaining = nextKm - currentKm;
  const pct = Math.max(0, Math.min(100, Math.round((1 - remaining / interval) * 100)));
  return { pct, tone: TONE_COLOR[serviceStatus(car.latest, car).key] || TONE_COLOR.unknown };
}

onMounted(() => {
  load();
  // Failure-tolerant: a plugin problem must not stop the garage from rendering.
  api
    .listVehicleProviders()
    .then((list) => (canImport.value = list.some((p) => p.connected)))
    .catch(() => {});
});
</script>

<template>
  <div>
    <div class="mb-6 flex items-end justify-between gap-4">
      <div>
        <p class="eyebrow">{{ t("dashboard.eyebrow") }}</p>
        <h1 class="text-3xl font-bold tracking-[-0.03em] text-strong">{{ t("dashboard.title") }}</h1>
        <p class="mt-1 text-sm text-muted">{{ t("dashboard.subtitle") }}</p>
      </div>
      <div class="flex flex-wrap items-center gap-2">
        <button v-if="canImport" class="dh-btn dh-btn-ghost" @click="showImport = true">
          <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" class="h-4 w-4"><path stroke-linecap="round" stroke-linejoin="round" d="M12 3v12m0 0-4-4m4 4 4-4M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2" /></svg>
          {{ t("dashboard.importCar") }}
        </button>
        <button class="dh-btn dh-btn-primary" @click="showAdd = true">
          <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" class="h-4 w-4"><path stroke-linecap="round" stroke-linejoin="round" d="M12 5v14M5 12h14" /></svg>
          {{ t("dashboard.addCar") }}
        </button>
      </div>
    </div>

    <p v-if="error" class="mb-4 rounded-control bg-danger-soft px-4 py-3 text-sm font-medium text-danger">{{ error }}</p>
    <p v-if="orderError" class="mb-4 rounded-control bg-danger-soft px-4 py-3 text-sm font-medium text-danger">{{ orderError }}</p>
    <p v-if="loading" class="text-muted">{{ t("common.loading") }}</p>

    <div v-else-if="cars.length === 0" class="rounded-card border border-dashed border-default p-12 text-center text-muted">
      {{ tSplit("dashboard.empty", "action").before
      }}<strong class="text-strong">{{ t("dashboard.addCar") }}</strong>{{ tSplit("dashboard.empty", "action").after }}
    </div>

    <!-- A card carries a name, a badge naming two service triggers and four
         readings, so the column count follows from the width a card needs
         rather than from breakpoints: three of them fit the 1024px shell at
         328px each, which truncates the name and wraps the badge mid-phrase.
         min() so a phone, narrower than one track, still gets a single column. -->
    <div v-else class="grid gap-5 grid-cols-[repeat(auto-fill,minmax(min(24rem,100%),1fr))]">
      <RouterLink
        v-for="car in cars"
        :key="car.id"
        :to="{ name: 'car', params: { id: car.id } }"
        :draggable="canArrangeCars"
        :title="canArrangeCars ? t('dashboard.dragHint') : ''"
        class="dh-card group block p-5 transition-shadow duration-150 hover:shadow-pop"
        :class="[
          canArrangeCars ? 'cursor-grab active:cursor-grabbing' : '',
          dragId === car.id ? 'opacity-50' : '',
          dropId === car.id ? 'ring-2 ring-accent' : '',
        ]"
        @dragstart="onDragStart(car, $event)"
        @dragenter.prevent="onDragEnter(car)"
        @dragover.prevent
        @drop.prevent="commitOrder"
        @dragend="commitOrder"
      >
        <div class="flex items-start justify-between gap-3">
          <div class="min-w-0">
            <h2 class="truncate text-lg font-bold tracking-[-0.02em] text-strong">{{ car.name }}</h2>
            <p class="truncate text-xs text-muted">{{ [car.make, car.model, car.year || ''].filter(Boolean).join(' ') }}</p>
          </div>
          <!-- Never broken mid-phrase: with two triggers the label reads
               "OK · 353d · 13.612 km", and a wrap puts the unit on its own
               line. The name beside it truncates instead. -->
          <span class="whitespace-nowrap" :class="serviceStatus(car.latest, car).classes">
            {{ serviceStatus(car.latest, car).label }}
          </span>
        </div>

        <span
          v-if="car.access && car.access !== 'owner'"
          class="dh-badge dh-badge-neutral mt-2"
        >
          {{ car.access === 'read' ? t("dashboard.sharedReadOnly") : t("dashboard.shared") }}
        </span>

        <!-- Service-life bar: fraction of the km interval used up. -->
        <div v-if="serviceLife(car)" class="mt-4">
          <div class="mb-1.5 flex items-center justify-between">
            <span class="eyebrow">{{ t("dashboard.serviceLife") }}</span>
            <span class="data text-xs font-medium text-strong">{{ serviceLife(car).pct }}%</span>
          </div>
          <div class="h-2 overflow-hidden rounded-pill bg-sunken">
            <div class="h-full rounded-pill" :style="{ width: serviceLife(car).pct + '%', background: serviceLife(car).tone }" />
          </div>
        </div>

        <dl class="mt-4 space-y-2 text-sm">
          <div class="flex items-center justify-between gap-2">
            <dt class="eyebrow">{{ t("dashboard.lastService") }}</dt>
            <dd class="data font-medium text-strong">{{ formatDate(car.latest?.date) }}</dd>
          </div>
          <div class="flex items-center justify-between gap-2">
            <dt class="eyebrow">{{ t("dashboard.odometer") }}</dt>
            <dd class="data font-medium text-strong">{{ formatKm(car.currentKm) }}</dd>
          </div>
          <div class="flex items-center justify-between gap-2">
            <dt class="eyebrow">{{ t("dashboard.nextDue") }}</dt>
            <dd class="data font-medium text-strong">{{ formatDate(car.latest?.nextServiceDate) }}</dd>
          </div>
          <div class="flex items-center justify-between gap-2">
            <dt class="eyebrow">{{ t("dashboard.nextDueKm") }}</dt>
            <dd class="data font-medium text-strong">{{ formatKm(car.latest?.nextServiceKm) }}</dd>
          </div>
        </dl>

        <p class="mt-4 border-t border-subtle pt-3 text-xs text-muted">
          {{ t("dashboard.serviceRecords", { n: car.count }) }}
        </p>
      </RouterLink>
    </div>

    <CarFormModal v-if="showAdd" @saved="onSaved" @close="showAdd = false" />
    <CarImportModal
      v-if="showImport"
      @saved="onImported"
      @open-car="(id) => router.push({ name: 'car', params: { id } })"
      @close="showImport = false"
    />
  </div>
</template>
