<script setup>
import { setPageLayout } from "#app"
import "gridstack/dist/gridstack.min.css"

import DisplayIncomeAndExpenditure from "~/components/displayIncomeAndExpenditure.vue"
import DisplayOpenBalances from "~/components/displayOpenBalances.vue"
import DisplayBankaccounts from "~/components/displayBankaccounts.vue"
import DisplayProjectsInPhases from "~/components/displayProjectsInPhases.vue"
import DisplayOpenTasks from "~/components/displayOpenTasks.vue"

setPageLayout("default")

const toast = useToast()
const tempStore = useTempStore()

const gridElement = ref(null)
const grid = shallowRef(null)
const isEditMode = ref(false)
const manageCardsOpen = ref(false)
const widgets = ref([])

let gridStackClass = null
let persistTimeout = null
const isSyncingGrid = ref(false)

const DASHBOARD_WIDGETS = [
  {
    id: "income-expense",
    title: "Einnahmen und Ausgaben",
    description: "Umsatz- und Ausgabenentwicklung",
    component: markRaw(DisplayIncomeAndExpenditure),
    defaultLayout: { x: 0, y: 0, w: 12, h: 4 },
    minW: 4,
    minH: 4
  },
  {
    id: "open-balances",
    title: "Buchhaltung",
    description: "Offene Rechnungen und Entwurfsstatus",
    component: markRaw(DisplayOpenBalances),
    defaultLayout: { x: 0, y: 4, w: 4, h: 3 },
    minW: 3,
    minH: 3
  },
  {
    id: "bankaccounts",
    title: "Bank",
    description: "Kontostand und offene Zuordnungen",
    component: markRaw(DisplayBankaccounts),
    defaultLayout: { x: 4, y: 4, w: 4, h: 3 },
    minW: 3,
    minH: 3
  },
  {
    id: "projects",
    title: "Projekte",
    description: "Aktuelle Projektphasen",
    component: markRaw(DisplayProjectsInPhases),
    defaultLayout: { x: 8, y: 4, w: 4, h: 3 },
    minW: 3,
    minH: 3
  },
  {
    id: "tasks",
    title: "Aufgaben",
    description: "Offene Aufgaben des aktuellen Nutzers",
    component: markRaw(DisplayOpenTasks),
    defaultLayout: { x: 0, y: 7, w: 4, h: 3 },
    minW: 3,
    minH: 3
  }
]

const widgetDefinitions = Object.fromEntries(DASHBOARD_WIDGETS.map((widget) => [widget.id, widget]))

function normalizeNumber(value, fallback) {
  const parsed = Number(value)
  return Number.isFinite(parsed) ? parsed : fallback
}

function serializeWidgets(input) {
  return input.map((widget) => ({
    id: widget.id,
    x: widget.x,
    y: widget.y,
    w: widget.w,
    h: widget.h,
    visible: widget.visible
  }))
}

function normalizeDashboardWidgets(storedWidgets) {
  const storedById = new Map(
    (Array.isArray(storedWidgets) ? storedWidgets : [])
      .filter((widget) => widget?.id && widgetDefinitions[widget.id])
      .map((widget) => [widget.id, widget])
  )

  return DASHBOARD_WIDGETS.map((definition) => {
    const stored = storedById.get(definition.id) || {}

    return {
      id: definition.id,
      x: normalizeNumber(stored.x, definition.defaultLayout.x),
      y: normalizeNumber(stored.y, definition.defaultLayout.y),
      w: normalizeNumber(stored.w, definition.defaultLayout.w),
      h: normalizeNumber(stored.h, definition.defaultLayout.h),
      visible: typeof stored.visible === "boolean" ? stored.visible : true
    }
  })
}

function widgetsSignature(input) {
  return JSON.stringify(serializeWidgets(input))
}

function getWidgetLayout(id) {
  return widgets.value.find((widget) => widget.id === id)
}

function updateWidget(id, patch) {
  widgets.value = widgets.value.map((widget) => {
    if (widget.id !== id) return widget
    return { ...widget, ...patch }
  })
}

function persistWidgets() {
  tempStore.modifySettings("dashboard", {
    version: 1,
    widgets: serializeWidgets(widgets.value)
  })
}

function schedulePersistWidgets() {
  clearTimeout(persistTimeout)
  persistTimeout = setTimeout(() => {
    persistWidgets()
  }, 120)
}

async function ensureGridStack() {
  if (gridStackClass) return gridStackClass
  const gridStackModule = await import("gridstack")
  gridStackClass = gridStackModule.GridStack
  return gridStackClass
}

function gridOptionsFor(widget) {
  const definition = widgetDefinitions[widget.id]

  return {
    x: widget.x,
    y: widget.y,
    w: widget.w,
    h: widget.h,
    minW: definition.minW,
    minH: definition.minH,
    id: widget.id
  }
}

function handleGridChange(_event, items = []) {
  if (isSyncingGrid.value || !isEditMode.value) return

  let hasChanges = false

  items.forEach((item) => {
    const widgetId = item.el?.dataset?.widgetId || item.id
    if (!widgetId) return

    const current = getWidgetLayout(widgetId)
    if (!current) return

    const nextPatch = {
      x: normalizeNumber(item.x, current.x),
      y: normalizeNumber(item.y, current.y),
      w: normalizeNumber(item.w, current.w),
      h: normalizeNumber(item.h, current.h)
    }

    if (current.x === nextPatch.x && current.y === nextPatch.y && current.w === nextPatch.w && current.h === nextPatch.h) {
      return
    }

    updateWidget(widgetId, nextPatch)
    hasChanges = true
  })

  if (hasChanges) schedulePersistWidgets()
}

function syncGridInteractivity() {
  if (!grid.value) return
  grid.value.enableMove(isEditMode.value)
  grid.value.enableResize(isEditMode.value)
}

async function syncGridWithDom() {
  if (!import.meta.client) return

  await nextTick()

  if (!gridElement.value) return

  const GridStack = await ensureGridStack()

  if (!grid.value) {
    isSyncingGrid.value = true
    grid.value = GridStack.init(
      {
        column: 12,
        float: true,
        margin: 16,
        cellHeight: 96,
        disableOneColumnMode: false,
        handle: ".dashboard-widget-drag-handle",
        resizable: {
          handles: "all"
        }
      },
      gridElement.value
    )
    grid.value.on("change", handleGridChange)
    syncGridInteractivity()
    isSyncingGrid.value = false
  }

  const visibleIds = new Set(visibleWidgets.value.map((widget) => widget.id))

  isSyncingGrid.value = true
  grid.value.batchUpdate()

  ;[...grid.value.engine.nodes].forEach((node) => {
    const widgetId = node.el?.dataset?.widgetId || node.id
    if (!node.el || !node.el.isConnected || !visibleIds.has(widgetId)) {
      if (node.el) grid.value.removeWidget(node.el, false, false)
    }
  })

  const domWidgets = [...gridElement.value.querySelectorAll(".grid-stack-item")]

  domWidgets.forEach((element) => {
    const widget = visibleWidgets.value.find((item) => item.id === element.dataset.widgetId)
    if (!widget) return

    if (!element.gridstackNode) {
      grid.value.makeWidget(element)
    }

    grid.value.update(element, gridOptionsFor(widget))
  })

  grid.value.batchUpdate(false)
  isSyncingGrid.value = false
}

function addWidget(id) {
  const widget = getWidgetLayout(id)
  if (!widget || widget.visible) return

  updateWidget(id, { visible: true })
  persistWidgets()
}

function toggleEditMode() {
  isEditMode.value = !isEditMode.value
  if (!isEditMode.value) manageCardsOpen.value = false
}

function removeWidget(id) {
  if (visibleWidgets.value.length <= 1) {
    toast.add({
      title: "Letzte Karte",
      description: "Mindestens eine Dashboard-Karte sollte sichtbar bleiben.",
      color: "orange"
    })
    return
  }

  updateWidget(id, { visible: false })
  persistWidgets()
}

function resetDashboard() {
  widgets.value = normalizeDashboardWidgets()
  persistWidgets()
}

const visibleWidgets = computed(() =>
  widgets.value
    .filter((widget) => widget.visible)
    .map((widget) => ({
      ...widgetDefinitions[widget.id],
      ...widget
    }))
)

const hiddenWidgets = computed(() =>
  DASHBOARD_WIDGETS.filter((definition) => !getWidgetLayout(definition.id)?.visible)
)

watch(
  () => tempStore.settings?.dashboard,
  (storedDashboard) => {
    const nextWidgets = normalizeDashboardWidgets(storedDashboard?.widgets)
    if (widgetsSignature(nextWidgets) === widgetsSignature(widgets.value)) return
    widgets.value = nextWidgets
  },
  { immediate: true, deep: true }
)

watch(
  visibleWidgets,
  async () => {
    await syncGridWithDom()
  },
  { deep: true }
)

watch(isEditMode, () => {
  syncGridInteractivity()
})

onMounted(async () => {
  if (!widgets.value.length) widgets.value = normalizeDashboardWidgets()
  await syncGridWithDom()
})

onBeforeUnmount(() => {
  clearTimeout(persistTimeout)
  if (grid.value) {
    grid.value.off("change", handleGridChange)
    grid.value.destroy(false)
    grid.value = null
  }
})
</script>

<template>
  <div>
    <UDashboardNavbar title="Home">
      <template #right>
        <div class="flex items-center gap-2">
          <UButton
            :icon="isEditMode ? 'i-heroicons-check' : 'i-heroicons-pencil-square'"
            :color="isEditMode ? 'primary' : 'gray'"
            :variant="isEditMode ? 'solid' : 'ghost'"
            @click="toggleEditMode"
          >
            {{ isEditMode ? "Bearbeitung beenden" : "Dashboard bearbeiten" }}
          </UButton>
          <UButton
            v-if="isEditMode && hiddenWidgets.length > 0"
            icon="i-heroicons-plus"
            color="white"
            variant="soft"
            @click="manageCardsOpen = true"
          >
            Karte hinzufügen
          </UButton>
          <UButton
            v-if="isEditMode"
            icon="i-heroicons-squares-2x2"
            color="gray"
            variant="ghost"
            @click="manageCardsOpen = true"
          >
            Karten verwalten
          </UButton>
        </div>
      </template>
    </UDashboardNavbar>

    <UDashboardPanelContent>
    <div v-if="visibleWidgets.length > 0" ref="gridElement" class="grid-stack dashboard-grid">
      <div
          v-for="widget in visibleWidgets"
          :key="widget.id"
          :class="['grid-stack-item', isEditMode ? 'dashboard-widget-editing' : '']"
          :data-widget-id="widget.id"
          :gs-x="widget.x"
          :gs-y="widget.y"
          :gs-w="widget.w"
          :gs-h="widget.h"
          :gs-min-w="widget.minW"
          :gs-min-h="widget.minH"
      >
          <div class="grid-stack-item-content dashboard-grid-item">
            <div class="dashboard-widget-card border border-gray-200 dark:border-gray-800">
              <div class="dashboard-widget-header border-b border-gray-200 dark:border-gray-800">
              <div class="flex items-start justify-between gap-3">
                <div class="min-w-0">
                  <div :class="['dashboard-widget-drag-handle font-semibold', isEditMode ? 'cursor-move' : 'cursor-default']">
                    {{ widget.title }}
                  </div>
                  <p class="mt-1 text-sm">
                    {{ widget.description }}
                  </p>
                </div>
                <div class="dashboard-widget-header-actions">
                  <div :id="`dashboard-widget-header-actions-${widget.id}`" class="dashboard-widget-header-target" />
                  <UButtonGroup v-if="isEditMode" size="xs">
                    <UButton
                        color="gray"
                        variant="soft"
                        icon="i-heroicons-arrows-pointing-out"
                        class="dashboard-widget-drag-handle"
                    />
                    <UButton
                        color="gray"
                        variant="soft"
                        icon="i-heroicons-x-mark"
                        :disabled="visibleWidgets.length <= 1"
                        @click="removeWidget(widget.id)"
                    />
                  </UButtonGroup>
                </div>
              </div>
            </div>
            <div class="dashboard-widget-body">
              <component
                  :is="widget.component"
                  v-bind="widget.id === 'income-expense'
                      ? { headerTarget: `#dashboard-widget-header-actions-${widget.id}` }
                      : {}"
              />
            </div>
          </div>
        </div>
      </div>
    </div>

    <div v-else class="rounded-xl border border-dashed border-gray-300 dark:border-gray-700 p-10 text-center">
      <p class="text-sm">
        Es sind aktuell keine Dashboard-Karten sichtbar.
      </p>
      <UButton v-if="isEditMode" class="mt-4" icon="i-heroicons-plus" @click="manageCardsOpen = true">
        Karte hinzufügen
      </UButton>
    </div>
    </UDashboardPanelContent>

    <UModal v-model="manageCardsOpen">
      <UCard>
        <template #header>
          <div class="flex items-center justify-between gap-3">
            <div>
              <h2 class="font-semibold">Dashboard-Karten</h2>
              <p class="text-sm">
                Karten ein- oder ausblenden und bei Bedarf auf das Standardlayout zurücksetzen.
              </p>
            </div>
            <UButton color="gray" variant="ghost" icon="i-heroicons-arrow-path" @click="resetDashboard">
              Zurücksetzen
            </UButton>
          </div>
        </template>

        <div class="space-y-3">
          <div
            v-for="definition in DASHBOARD_WIDGETS"
            :key="definition.id"
            class="flex items-center justify-between gap-3 rounded-lg border border-gray-200 dark:border-gray-800 px-4 py-3"
          >
            <div>
              <p class="font-medium">{{ definition.title }}</p>
              <p class="text-sm">{{ definition.description }}</p>
            </div>

            <UButton
              v-if="getWidgetLayout(definition.id)?.visible"
              color="gray"
              variant="soft"
              icon="i-heroicons-minus"
              :disabled="visibleWidgets.length <= 1"
              @click="removeWidget(definition.id)"
            >
              Entfernen
            </UButton>
            <UButton
              v-else
              color="primary"
              variant="soft"
              icon="i-heroicons-plus"
              @click="addWidget(definition.id)"
            >
              Hinzufügen
            </UButton>
          </div>
        </div>
      </UCard>
    </UModal>
  </div>
</template>

<style scoped>
.dashboard-grid {
  min-height: 60vh;
}

.dashboard-grid-item {
  height: 100%;
  padding: 0.25rem;
}

.dashboard-widget-card {
  height: 100%;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border-radius: 0.75rem;
  box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
}

.dashboard-widget-header {
  flex: 0 0 auto;
  padding: 1rem 1rem 0.875rem;
}

.dashboard-widget-header-actions {
  display: flex;
  flex: 0 0 auto;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: flex-end;
  gap: 0.5rem;
}

.dashboard-widget-header-target {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 0.5rem;
}

.dashboard-widget-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
  padding: 1rem;
}

:deep(.grid-stack-item-content) {
  inset: 0;
  background: transparent;
  overflow-y: hidden !important;
}

:deep(.grid-stack-item:not(.dashboard-widget-editing) .ui-resizable-handle) {
  display: none;
}

</style>
