<script setup lang="ts">
import { computed, onMounted, ref, watch } from "vue";
import { useRoute } from "vue-router";
import {
  createObject,
  deleteObject,
  fetchObjects,
  updateObject,
  uploadObjectMedia,
} from "../api/objects";
import { fetchEvents } from "../api/admin";
import ContextMenu from "../components/ContextMenu.vue";
import CreateObjectModal from "../components/CreateObjectModal.vue";
import EditObjectModal from "../components/EditObjectModal.vue";
import MapView from "../components/MapView.vue";
import ObjectPanel from "../components/ObjectPanel.vue";
import TimelineBar from "../components/TimelineBar.vue";
import type { MapObject, MapObjectCreate, ObjectType } from "../types/object";

const route = useRoute();

const objects = ref<MapObject[]>([]);
const selectedObject = ref<MapObject | null>(null);
const loading = ref(true);
const error = ref("");
const timelinePosition = ref(Date.now());

const contextMenu = ref<{
  visible: boolean;
  x: number;
  y: number;
  latitude: number;
  longitude: number;
  target: "map" | "object";
  object: MapObject | null;
}>({
  visible: false,
  x: 0,
  y: 0,
  latitude: 0,
  longitude: 0,
  target: "map",
  object: null,
});

const createModal = ref({
  visible: false,
  latitude: 0,
  longitude: 0,
});

const editModal = ref({
  visible: false,
  object: null as MapObject | null,
});

const selectedId = computed(() => selectedObject.value?.id ?? null);

function objectTime(obj: MapObject): number {
  return new Date(obj.created_at).getTime();
}

function replaceObject(updated: MapObject) {
  objects.value = objects.value.map((obj) => (obj.id === updated.id ? updated : obj));
  if (selectedObject.value?.id === updated.id) {
    selectedObject.value = updated;
  }
}

const timelineBounds = computed(() => {
  if (objects.value.length === 0) {
    const now = Date.now();
    return { min: now, max: now };
  }

  const times = objects.value.map(objectTime);
  return {
    min: Math.min(...times),
    max: Math.max(...times),
  };
});

const visibleObjects = computed(() =>
  objects.value.filter((obj) => objectTime(obj) <= timelinePosition.value),
);

function syncTimelineToMax() {
  timelinePosition.value = timelineBounds.value.max;
}

function selectByEventId(eventId: string | undefined) {
  if (!eventId) return;
  const id = Number(eventId);
  if (Number.isNaN(id)) return;

  const match = objects.value.find((obj) => obj.event_id === id);
  if (match) {
    selectedObject.value = match;
    timelinePosition.value = Math.max(timelinePosition.value, objectTime(match));
    return;
  }

  void fetchEvents({ limit: 500 }).then((response) => {
    const event = response.items.find((item) => item.id === id);
    if (!event || event.latitude == null || event.longitude == null) {
      error.value = "Событие не найдено на карте";
      return;
    }

    const eventTime = event.event_date
      ? new Date(event.event_date).getTime()
      : Date.now();
    timelinePosition.value = Math.max(timelinePosition.value, eventTime);

    const byCoords = objects.value.find(
      (obj) =>
        Math.abs(obj.latitude - event.latitude!) < 1e-4 &&
        Math.abs(obj.longitude - event.longitude!) < 1e-4,
    );
    if (byCoords) {
      selectedObject.value = byCoords;
    } else {
      error.value = "Объект карты для события ещё не создан";
    }
  });
}

async function loadObjects() {
  loading.value = true;
  error.value = "";
  try {
    objects.value = await fetchObjects();
    syncTimelineToMax();
    selectByEventId(route.query.eventId as string | undefined);
  } catch (err) {
    error.value = err instanceof Error ? err.message : "Не удалось загрузить объекты";
  } finally {
    loading.value = false;
  }
}

function handleSelectObject(obj: MapObject) {
  selectedObject.value = obj;
}

function handleMapContextMenu(payload: {
  x: number;
  y: number;
  latitude: number;
  longitude: number;
  object: MapObject | null;
}) {
  contextMenu.value = {
    visible: true,
    x: payload.x,
    y: payload.y,
    latitude: payload.latitude,
    longitude: payload.longitude,
    target: payload.object ? "object" : "map",
    object: payload.object,
  };

  if (payload.object) {
    selectedObject.value = payload.object;
  }
}

function closeContextMenu() {
  contextMenu.value.visible = false;
}

function openCreateModal() {
  createModal.value = {
    visible: true,
    latitude: contextMenu.value.latitude,
    longitude: contextMenu.value.longitude,
  };
  closeContextMenu();
}

function closeCreateModal() {
  createModal.value.visible = false;
}

function openEditModal() {
  if (!contextMenu.value.object) return;

  editModal.value = {
    visible: true,
    object: contextMenu.value.object,
  };
  closeContextMenu();
}

function closeEditModal() {
  editModal.value.visible = false;
  editModal.value.object = null;
}

async function handleCreateObject(payload: {
  name: string;
  description: string;
  type: ObjectType;
  created_at?: string;
  files: File[];
}) {
  const data: MapObjectCreate = {
    name: payload.name,
    description: payload.description,
    type: payload.type,
    latitude: createModal.value.latitude,
    longitude: createModal.value.longitude,
    created_at: payload.created_at,
  };

  const created = await createObject(data);

  for (const file of payload.files) {
    await uploadObjectMedia(created.id, file);
  }

  objects.value = [...objects.value, created];
  selectedObject.value = created;
  timelinePosition.value = objectTime(created);
  closeCreateModal();
}

async function handleEditObject(payload: {
  name: string;
  description: string;
  type: ObjectType;
  created_at: string;
}) {
  if (!editModal.value.object) return;

  const updated = await updateObject(editModal.value.object.id, payload);
  replaceObject(updated);
  timelinePosition.value = objectTime(updated);
  closeEditModal();
}

async function handleDeleteObject() {
  const object = contextMenu.value.object;
  if (!object) return;

  const confirmed = window.confirm(`Удалить объект «${object.name}»?`);
  if (!confirmed) return;

  closeContextMenu();

  try {
    await deleteObject(object.id);
    objects.value = objects.value.filter((item) => item.id !== object.id);

    if (selectedObject.value?.id === object.id) {
      selectedObject.value = null;
    }
  } catch (err) {
    error.value = err instanceof Error ? err.message : "Не удалось удалить объект";
  }
}

async function handleMoveObject(payload: {
  object: MapObject;
  latitude: number;
  longitude: number;
}) {
  try {
    const updated = await updateObject(payload.object.id, {
      latitude: payload.latitude,
      longitude: payload.longitude,
    });
    replaceObject(updated);
  } catch (err) {
    error.value = err instanceof Error ? err.message : "Не удалось переместить объект";
  }
}

watch(visibleObjects, (visible) => {
  if (
    selectedObject.value &&
    !visible.some((obj) => obj.id === selectedObject.value?.id)
  ) {
    selectedObject.value = null;
  }
});

watch(
  () => route.query.eventId,
  (eventId) => selectByEventId(eventId as string | undefined),
);

onMounted(loadObjects);
</script>

<template>
  <div class="map-page" @click="closeContextMenu">
    <div class="map-status">
      <span v-if="loading" class="status">Загрузка...</span>
      <span v-else-if="error" class="status error">{{ error }}</span>
      <span v-else class="status">
        {{ visibleObjects.length }} / {{ objects.length }} объектов на карте
      </span>
    </div>

    <div class="map-content">
      <MapView
        :objects="visibleObjects"
        :selected-id="selectedId"
        @select="handleSelectObject"
        @contextmenu="handleMapContextMenu"
        @move="handleMoveObject"
      />
      <ObjectPanel :object="selectedObject" />
    </div>

    <TimelineBar
      v-if="!loading && objects.length > 0"
      v-model="timelinePosition"
      :min="timelineBounds.min"
      :max="timelineBounds.max"
      :objects="objects"
      :visible-count="visibleObjects.length"
    />

    <ContextMenu
      v-if="contextMenu.visible"
      :x="contextMenu.x"
      :y="contextMenu.y"
      :target="contextMenu.target"
      :object-name="contextMenu.object?.name"
      @create="openCreateModal"
      @edit="openEditModal"
      @delete="handleDeleteObject"
    />

    <CreateObjectModal
      v-if="createModal.visible"
      :latitude="createModal.latitude"
      :longitude="createModal.longitude"
      @close="closeCreateModal"
      @submit="handleCreateObject"
    />

    <EditObjectModal
      v-if="editModal.visible && editModal.object"
      :object="editModal.object"
      @close="closeEditModal"
      @submit="handleEditObject"
    />
  </div>
</template>

<style scoped>
.map-page {
  display: flex;
  flex-direction: column;
  height: 100%;
}

.map-status {
  padding: 0.4rem 1.25rem;
  background: #fafafa;
  border-bottom: 1px solid #eee;
  font-size: 0.875rem;
}

.status {
  color: #666;
}

.status.error {
  color: #c62828;
}

.map-content {
  position: relative;
  display: flex;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}
</style>
