// frontend/src/pages/DashboardPage.tsx

import { useEffect, useMemo, useRef, useState, type PointerEvent } from 'react'
import {
  CheckIcon,
  Cog6ToothIcon,
  PencilSquareIcon,
  TrashIcon,
} from '@heroicons/react/20/solid'
import { Squares2X2Icon } from '@heroicons/react/24/outline'
import WidgetSettingsModal from '../components/dashboard/WidgetSettingsModal'
import Button from '../components/Button'
import LoadingSpinner from '../components/LoadingSpinner'
import DashboardWidgetRenderer from '../components/dashboard/DashboardWidgetRenderer'
import WidgetPickerModal from '../components/dashboard/WidgetPickerModal'
import type {
  DashboardWidget,
  DashboardWidgetDefinition,
  DashboardWidgetType,
} from '../components/dashboard/types'
import EmptyState from '../components/EmptyState'

const API_URL = import.meta.env.VITE_API_URL ?? 'http://localhost:8080'

const GRID_COLUMNS = 12
const GRID_ROW_HEIGHT = 88
const GRID_GAP = 16
const GRID_PADDING = 16 

type ResizeDirection = 'nw' | 'ne' | 'sw' | 'se'

type GridPlacement = {
  x: number
  y: number
  w: number
  h: number
}

type GridCell = {
  x: number
  y: number
}

type DashboardInteraction =
  | {
      type: 'move'
      pointerId: number
      widgetId: string
      offsetX: number
      offsetY: number
    }
  | {
      type: 'resize'
      pointerId: number
      widgetId: string
      direction: ResizeDirection
      startClientX: number
      startClientY: number
      startWidget: DashboardWidget
    }
  | {
      type: 'draw'
      pointerId: number
      startCell: GridCell
    }

function getResizeHandleClass(direction: ResizeDirection) {
  switch (direction) {
    case 'nw':
      return '-top-1.5 -left-1.5 cursor-nwse-resize'
    case 'ne':
      return '-top-1.5 -right-1.5 cursor-nesw-resize'
    case 'sw':
      return '-bottom-1.5 -left-1.5 cursor-nesw-resize'
    case 'se':
      return '-right-1.5 -bottom-1.5 cursor-nwse-resize'
  }
}

function hasWidgetChanged(widget: DashboardWidget, nextWidget: DashboardWidget) {
  return (
    widget.x !== nextWidget.x ||
    widget.y !== nextWidget.y ||
    widget.w !== nextWidget.w ||
    widget.h !== nextWidget.h
  )
}

type DashboardPageProps = {
  username: string
}

type DashboardSettings = {
  isEditingDashboard?: boolean
}

const widgetTypes: DashboardWidgetDefinition[] = [
  {
    id: 'operationChanges',
    label: 'Letzte Einsatzänderungen',
    description: 'Zeigt die letzten Änderungen an Einsätzen ohne Journal-Einträge.',
    defaultTitle: 'Letzte Einsatzänderungen',
    defaultSize: {
      w: 6,
      h: 4,
    },
    defaultConfig: {
      limit: 8,
    },
  },
  {
    id: 'operationMap',
    label: 'Einsatzkarte',
    description: 'Zeigt Einsatzorte auf einer Karte inklusive KW und Zielobjekt.',
    defaultTitle: 'Einsatzkarte',
    defaultSize: {
      w: 8,
      h: 5,
    },
    defaultConfig: {
      defaultLayer: 'nrw-dop',
      showLabelsOverlay: true,
      showLocateControl: false,
      showCurrentLocationMarker: true,
      initialAddress: 'Tiefenbroicher Weg 32, 40472 Düsseldorf',
      initialZoom: 14,
    },
  },
]

function classNames(...classes: Array<string | false | null | undefined>) {
  return classes.filter(Boolean).join(' ')
}

function clamp(value: number, min: number, max: number) {
  return Math.min(Math.max(value, min), max)
}

function widgetsCollide(a: DashboardWidget, b: DashboardWidget) {
  return !(
    a.x + a.w <= b.x ||
    b.x + b.w <= a.x ||
    a.y + a.h <= b.y ||
    b.y + b.h <= a.y
  )
}

function findFreePosition(
  widgets: DashboardWidget[],
  candidate: DashboardWidget,
) {
  let nextWidget = {
    ...candidate,
    x: clamp(candidate.x, 0, GRID_COLUMNS - candidate.w),
    y: Math.max(0, candidate.y),
  }

  while (
    widgets.some(
      (widget) =>
        widget.id !== nextWidget.id && widgetsCollide(widget, nextWidget),
    )
  ) {
    nextWidget = {
      ...nextWidget,
      y: nextWidget.y + 1,
    }
  }

  return nextWidget
}

function getNextY(widgets: DashboardWidget[]) {
  if (widgets.length === 0) {
    return 0
  }

  return Math.max(...widgets.map((widget) => widget.y + widget.h))
}

function getWidgetTypeDefinition(type: DashboardWidgetType) {
  return widgetTypes.find((widgetType) => widgetType.id === type) ?? widgetTypes[0]
}

export default function DashboardPage({ username }: DashboardPageProps) {
  const [widgets, setWidgets] = useState<DashboardWidget[]>([])
  const [isEditingDashboard, setIsEditingDashboard] = useState(false)
  const [isWidgetPickerOpen, setIsWidgetPickerOpen] = useState(false)
  const [pendingWidgetPlacement, setPendingWidgetPlacement] = useState<GridPlacement | null>(null)
  const [drawPlacement, setDrawPlacement] = useState<GridPlacement | null>(null)
  const [activeWidgetId, setActiveWidgetId] = useState<string | null>(null)
  const [previewWidget, setPreviewWidget] = useState<DashboardWidget | null>(null)
  const [settingsWidget, setSettingsWidget] = useState<DashboardWidget | null>(null)
  const [isLoading, setIsLoading] = useState(true)
  const [isSaving, setIsSaving] = useState(false)
  const [error, setError] = useState<string | null>(null)

  const [dashboardSettings, setDashboardSettings] = useState<DashboardSettings>({})

  const gridRef = useRef<HTMLDivElement | null>(null)

  const interactionRef = useRef<DashboardInteraction | null>(null)
  const previewWidgetRef = useRef<DashboardWidget | null>(null)

  const drawPlacementRef = useRef<GridPlacement | null>(null)

  useEffect(() => {
    drawPlacementRef.current = drawPlacement
  }, [drawPlacement])

  useEffect(() => {
    previewWidgetRef.current = previewWidget
  }, [previewWidget])

  const dashboardRows = useMemo(() => {
    return Math.max(
      4,
      ...widgets.map((widget) => widget.y + widget.h),
      drawPlacement ? drawPlacement.y + drawPlacement.h : 0,
      previewWidget ? previewWidget.y + previewWidget.h : 0,
    )
  }, [drawPlacement, previewWidget, widgets])

  const dashboardHeight = useMemo(() => {
    return dashboardRows * GRID_ROW_HEIGHT + Math.max(0, dashboardRows - 1) * GRID_GAP
  }, [dashboardRows])

  useEffect(() => {
    void loadDashboard()
  }, [])

  async function loadWidgets() {
    setError(null)

    try {
      const response = await fetch(`${API_URL}/dashboard/widgets`, {
        credentials: 'include',
      })

      if (!response.ok) {
        const errorData = await response.json().catch(() => null)
        throw new Error(errorData?.error ?? 'Dashboard konnte nicht geladen werden')
      }

      const data = await response.json()
      setWidgets(data.widgets ?? [])
    } catch (error) {
      setError(
        error instanceof Error
          ? error.message
          : 'Dashboard konnte nicht geladen werden',
      )
    }
  }

  async function loadDashboard() {
    setIsLoading(true)
    setError(null)

    try {
      await Promise.all([
        loadWidgets(),
        loadDashboardSettings(),
      ])
    } finally {
      setIsLoading(false)
    }
  }

  async function loadDashboardSettings() {
    try {
      const response = await fetch(`${API_URL}/dashboard/settings`, {
        credentials: 'include',
      })

      if (!response.ok) {
        const errorData = await response.json().catch(() => null)
        throw new Error(
          errorData?.error ?? 'Dashboard-Einstellungen konnten nicht geladen werden',
        )
      }

      const data = await response.json()
      const settings = data.dashboardSettings?.settings ?? {}

      setDashboardSettings(settings)

      if (typeof settings.isEditingDashboard === 'boolean') {
        setIsEditingDashboard(settings.isEditingDashboard)
      }
    } catch (error) {
      setError(
        error instanceof Error
          ? error.message
          : 'Dashboard-Einstellungen konnten nicht geladen werden',
      )
    }
  }

  async function saveDashboardSettings(nextSettings: DashboardSettings) {
    setDashboardSettings(nextSettings)

    const response = await fetch(`${API_URL}/dashboard/settings`, {
      method: 'PUT',
      headers: {
        'Content-Type': 'application/json',
      },
      credentials: 'include',
      body: JSON.stringify({
        settings: nextSettings,
      }),
    })

    if (!response.ok) {
      const errorData = await response.json().catch(() => null)
      throw new Error(
        errorData?.error ?? 'Dashboard-Einstellungen konnten nicht gespeichert werden',
      )
    }

    const data = await response.json()
    setDashboardSettings(data.dashboardSettings?.settings ?? nextSettings)
  }

  async function saveWidget(widget: DashboardWidget) {
    const response = await fetch(`${API_URL}/dashboard/widgets/${widget.id}`, {
      method: 'PUT',
      headers: {
        'Content-Type': 'application/json',
      },
      credentials: 'include',
      body: JSON.stringify({
        type: widget.type,
        title: widget.title,
        x: widget.x,
        y: widget.y,
        w: widget.w,
        h: widget.h,
        config: widget.config,
      }),
    })

    if (!response.ok) {
      const errorData = await response.json().catch(() => null)
      throw new Error(errorData?.error ?? 'Widget konnte nicht gespeichert werden')
    }

    const data = await response.json()
    return data.widget as DashboardWidget
  }

  async function createWidget(widgetTypeId: DashboardWidgetType) {
    const widgetType = getWidgetTypeDefinition(widgetTypeId)
    const placement = pendingWidgetPlacement

    setIsSaving(true)
    setError(null)

    try {
      const response = await fetch(`${API_URL}/dashboard/widgets`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        credentials: 'include',
        body: JSON.stringify({
          type: widgetType.id,
          title: widgetType.defaultTitle,
          x: placement?.x ?? 0,
          y: placement?.y ?? getNextY(widgets),
          w: placement?.w ?? widgetType.defaultSize.w,
          h: placement?.h ?? widgetType.defaultSize.h,
          config: widgetType.defaultConfig,
        }),
      })

      if (!response.ok) {
        const errorData = await response.json().catch(() => null)
        throw new Error(errorData?.error ?? 'Widget konnte nicht erstellt werden')
      }

      const data = await response.json()

      setWidgets((currentWidgets) => [...currentWidgets, data.widget])
      setPendingWidgetPlacement(null)
      setIsWidgetPickerOpen(false)
    } catch (error) {
      setError(
        error instanceof Error
          ? error.message
          : 'Widget konnte nicht erstellt werden',
      )
    } finally {
      setIsSaving(false)
    }
  }

  async function updateWidget(nextWidget: DashboardWidget) {
    const adjustedWidget = findFreePosition(
      widgets.filter((widget) => widget.id !== nextWidget.id),
      nextWidget,
    )

    setWidgets((currentWidgets) =>
      currentWidgets.map((widget) =>
        widget.id === adjustedWidget.id ? adjustedWidget : widget,
      ),
    )

    try {
      const savedWidget = await saveWidget(adjustedWidget)

      setWidgets((currentWidgets) =>
        currentWidgets.map((widget) =>
          widget.id === savedWidget.id ? savedWidget : widget,
        ),
      )
    } catch (error) {
      setError(
        error instanceof Error
          ? error.message
          : 'Widget konnte nicht gespeichert werden',
      )
      void loadWidgets()
    }
  }

  async function updateWidgetSettings(
    widget: DashboardWidget,
    nextValues: {
      title: string
      config: Record<string, unknown>
    },
  ) {
    const nextWidget: DashboardWidget = {
      ...widget,
      title: nextValues.title,
      config: nextValues.config,
    }

    setIsSaving(true)
    setError(null)

    setWidgets((currentWidgets) =>
      currentWidgets.map((currentWidget) =>
        currentWidget.id === nextWidget.id ? nextWidget : currentWidget,
      ),
    )

    try {
      const savedWidget = await saveWidget(nextWidget)

      setWidgets((currentWidgets) =>
        currentWidgets.map((currentWidget) =>
          currentWidget.id === savedWidget.id ? savedWidget : currentWidget,
        ),
      )

      setSettingsWidget(null)
    } catch (error) {
      setError(
        error instanceof Error
          ? error.message
          : 'Widget-Einstellungen konnten nicht gespeichert werden',
      )

      void loadWidgets()
    } finally {
      setIsSaving(false)
    }
  }

  async function deleteWidget(widgetId: string) {
    setError(null)

    try {
      const response = await fetch(`${API_URL}/dashboard/widgets/${widgetId}`, {
        method: 'DELETE',
        credentials: 'include',
      })

      if (!response.ok) {
        const errorData = await response.json().catch(() => null)
        throw new Error(errorData?.error ?? 'Widget konnte nicht gelöscht werden')
      }

      setWidgets((currentWidgets) =>
        currentWidgets.filter((widget) => widget.id !== widgetId),
      )
    } catch (error) {
      setError(
        error instanceof Error
          ? error.message
          : 'Widget konnte nicht gelöscht werden',
      )
    }
  }

  function getGridMetrics() {
    if (!gridRef.current) {
      return null
    }

    const rect = gridRef.current.getBoundingClientRect()
    const styles = window.getComputedStyle(gridRef.current)

    const paddingLeft = Number.parseFloat(styles.paddingLeft) || 0
    const paddingTop = Number.parseFloat(styles.paddingTop) || 0
    const paddingRight = Number.parseFloat(styles.paddingRight) || 0

    const contentLeft = rect.left + paddingLeft
    const contentTop = rect.top + paddingTop
    const contentWidth = rect.width - paddingLeft - paddingRight
    const columnWidth =
      (contentWidth - (GRID_COLUMNS - 1) * GRID_GAP) / GRID_COLUMNS

    return {
      contentLeft,
      contentTop,
      columnWidth,
    }
  }

  function getGridCellFromPointer(clientX: number, clientY: number): GridCell | null {
    const metrics = getGridMetrics()

    if (!metrics) {
      return null
    }

    const relativeX = clientX - metrics.contentLeft
    const relativeY = clientY - metrics.contentTop

    return {
      x: clamp(
        Math.floor(relativeX / (metrics.columnWidth + GRID_GAP)),
        0,
        GRID_COLUMNS - 1,
      ),
      y: Math.max(
        0,
        Math.floor(relativeY / (GRID_ROW_HEIGHT + GRID_GAP)),
      ),
    }
  }

  function getDrawPlacement(startCell: GridCell, endCell: GridCell): GridPlacement {
    const x = Math.min(startCell.x, endCell.x)
    const y = Math.min(startCell.y, endCell.y)

    const w = clamp(Math.abs(endCell.x - startCell.x) + 1, 2, GRID_COLUMNS - x)
    const h = clamp(Math.abs(endCell.y - startCell.y) + 1, 1, 8)

    return {
      x,
      y,
      w,
      h,
    }
  }

  function handleGridPointerDown(event: PointerEvent<HTMLDivElement>) {
    if (!isEditingDashboard) {
      return
    }

    if (event.button !== 0) {
      return
    }

    if (event.target !== event.currentTarget) {
      return
    }

    const startCell = getGridCellFromPointer(event.clientX, event.clientY)

    if (!startCell || !gridRef.current) {
      return
    }

    interactionRef.current = {
      type: 'draw',
      pointerId: event.pointerId,
      startCell,
    }

    const initialPlacement: GridPlacement = {
      x: startCell.x,
      y: startCell.y,
      w: 2,
      h: 1,
    }

    setDrawPlacement(initialPlacement)
    gridRef.current.setPointerCapture(event.pointerId)
  }

  function openWidgetPickerForPlacement(placement: GridPlacement | null) {
    setPendingWidgetPlacement(placement)
    setIsWidgetPickerOpen(true)
  }

  function handleWidgetPickerOpenChange(open: boolean) {
    setIsWidgetPickerOpen(open)

    if (!open) {
      setPendingWidgetPlacement(null)
    }
  }

  function getMovedWidget(
    widget: DashboardWidget,
    clientX: number,
    clientY: number,
    offsetX: number,
    offsetY: number,
  ) {
    const metrics = getGridMetrics()

    if (!metrics) {
      return widget
    }

    const relativeX = clientX - metrics.contentLeft - offsetX
    const relativeY = clientY - metrics.contentTop - offsetY

    const nextX = clamp(
      Math.round(relativeX / (metrics.columnWidth + GRID_GAP)),
      0,
      GRID_COLUMNS - widget.w,
    )

    const nextY = Math.max(
      0,
      Math.round(relativeY / (GRID_ROW_HEIGHT + GRID_GAP)),
    )

    return {
      ...widget,
      x: nextX,
      y: nextY,
    }
  }

  function getResizedWidget(
    interaction: Extract<DashboardInteraction, { type: 'resize' }>,
    clientX: number,
    clientY: number,
  ) {
    const metrics = getGridMetrics()

    if (!metrics) {
      return interaction.startWidget
    }

    const columnDelta = Math.round(
      (clientX - interaction.startClientX) / (metrics.columnWidth + GRID_GAP),
    )

    const rowDelta = Math.round(
      (clientY - interaction.startClientY) / (GRID_ROW_HEIGHT + GRID_GAP),
    )

    const startWidget = interaction.startWidget

    let nextX = startWidget.x
    let nextY = startWidget.y
    let nextW = startWidget.w
    let nextH = startWidget.h

    if (interaction.direction.includes('e')) {
      nextW = clamp(startWidget.w + columnDelta, 2, GRID_COLUMNS - startWidget.x)
    }

    if (interaction.direction.includes('s')) {
      nextH = clamp(startWidget.h + rowDelta, 1, 8)
    }

    if (interaction.direction.includes('w')) {
      const maxX = startWidget.x + startWidget.w - 2

      nextX = clamp(startWidget.x + columnDelta, 0, maxX)
      nextW = startWidget.x + startWidget.w - nextX
    }

    if (interaction.direction.includes('n')) {
      const maxY = startWidget.y + startWidget.h - 1

      nextY = clamp(startWidget.y + rowDelta, 0, maxY)
      nextH = startWidget.y + startWidget.h - nextY
    }

    return {
      ...startWidget,
      x: nextX,
      y: nextY,
      w: nextW,
      h: nextH,
    }
  }

  function startMovingWidget(
    event: PointerEvent<HTMLElement>,
    widget: DashboardWidget,
  ) {
    if (event.button !== 0) {
      return
    }

    if (!isEditingDashboard) {
      return
    }

    const widgetElement = event.currentTarget.closest('[data-dashboard-widget]')

    if (!widgetElement || !gridRef.current) {
      return
    }

    const rect = widgetElement.getBoundingClientRect()

    interactionRef.current = {
      type: 'move',
      pointerId: event.pointerId,
      widgetId: widget.id,
      offsetX: event.clientX - rect.left,
      offsetY: event.clientY - rect.top,
    }

    setActiveWidgetId(widget.id)
    setPreviewWidget(widget)

    gridRef.current.setPointerCapture(event.pointerId)
  }

  function startResizingWidget(
    event: PointerEvent<HTMLButtonElement>,
    widget: DashboardWidget,
    direction: ResizeDirection,
  ) {
    event.preventDefault()
    event.stopPropagation()

    if (!isEditingDashboard) {
      return
    }

    if (!gridRef.current) {
      return
    }

    interactionRef.current = {
      type: 'resize',
      pointerId: event.pointerId,
      widgetId: widget.id,
      direction,
      startClientX: event.clientX,
      startClientY: event.clientY,
      startWidget: widget,
    }

    setActiveWidgetId(widget.id)
    setPreviewWidget(widget)

    gridRef.current.setPointerCapture(event.pointerId)
  }

  function handleGridPointerMove(event: PointerEvent<HTMLDivElement>) {
    const interaction = interactionRef.current

    if (!interaction) {
      return
    }

    if (interaction.type === 'draw') {
      const currentCell = getGridCellFromPointer(event.clientX, event.clientY)

      if (!currentCell) {
        return
      }

      setDrawPlacement(getDrawPlacement(interaction.startCell, currentCell))
      return
    }

    const widget =
      interaction.type === 'resize'
        ? interaction.startWidget
        : widgets.find((item) => item.id === interaction.widgetId)

    if (!widget) {
      return
    }

    const rawNextWidget =
      interaction.type === 'move'
        ? getMovedWidget(
            widget,
            event.clientX,
            event.clientY,
            interaction.offsetX,
            interaction.offsetY,
          )
        : getResizedWidget(interaction, event.clientX, event.clientY)

    const adjustedWidget = findFreePosition(
      widgets.filter((item) => item.id !== rawNextWidget.id),
      rawNextWidget,
    )

    setPreviewWidget(adjustedWidget)
  }

  function handleGridPointerEnd() {
    const interaction = interactionRef.current
    const nextWidget = previewWidgetRef.current
    const nextDrawPlacement = drawPlacementRef.current

    if (interaction && gridRef.current?.hasPointerCapture(interaction.pointerId)) {
      gridRef.current.releasePointerCapture(interaction.pointerId)
    }

    interactionRef.current = null
    setActiveWidgetId(null)
    setPreviewWidget(null)
    setDrawPlacement(null)

    if (interaction?.type === 'draw') {
      if (nextDrawPlacement) {
        openWidgetPickerForPlacement(nextDrawPlacement)
      }

      return
    }

    if (!nextWidget) {
      return
    }

    const currentWidget = widgets.find((widget) => widget.id === nextWidget.id)

    if (!currentWidget || !hasWidgetChanged(currentWidget, nextWidget)) {
      return
    }

    void updateWidget(nextWidget)
  }

  function openEmptyDashboardWidgetPicker() {
    setIsEditingDashboard(true)
    openWidgetPickerForPlacement(null)

    void saveDashboardSettings({
      ...dashboardSettings,
      isEditingDashboard: true,
    }).catch((error) => {
      setError(
        error instanceof Error
          ? error.message
          : 'Dashboard-Einstellungen konnten nicht gespeichert werden',
      )
    })
  }

  if (isLoading) {
    return (
      <div className="px-4 py-10 sm:px-6 lg:px-8">
        <LoadingSpinner
          size="lg"
          label="Dashboard wird geladen..."
          className="text-indigo-600 dark:text-indigo-400"
          center
        />
      </div>
    )
  }

  return (
    <div className="px-4 py-10 sm:px-6 lg:px-8">
      <div className="mb-8 sm:flex sm:items-start sm:justify-between sm:gap-x-6">
        <div>
          <h1 className="text-2xl font-semibold text-gray-900 dark:text-white">
            Dashboard
          </h1>

          <p className="mt-2 text-sm text-gray-500 dark:text-gray-400">
            Willkommen zurück, {username}. Du kannst Widgets hinzufügen, verschieben und skalieren.
          </p>
        </div>

        {widgets.length > 0 && (
          <div className="mt-4 flex items-center gap-x-2 sm:mt-0">
            <Button
              type="button"
              variant={isEditingDashboard ? 'primary' : 'secondary'}
              color={isEditingDashboard ? 'emerald' : 'gray'}
              leadingIcon={
                isEditingDashboard ? (
                  <CheckIcon aria-hidden="true" className="size-4" />
                ) : (
                  <PencilSquareIcon aria-hidden="true" className="size-4" />
                )
              }
              onClick={() => {
                const nextValue = !isEditingDashboard

                setIsEditingDashboard(nextValue)
                setPreviewWidget(null)
                setDrawPlacement(null)
                setPendingWidgetPlacement(null)

                void saveDashboardSettings({
                  ...dashboardSettings,
                  isEditingDashboard: nextValue,
                }).catch((error) => {
                  setError(
                    error instanceof Error
                      ? error.message
                      : 'Dashboard-Einstellungen konnten nicht gespeichert werden',
                  )
                })
              }}
            >
              {isEditingDashboard ? 'Fertig' : 'Dashboard bearbeiten'}
            </Button>
          </div>
        )}
      </div>

      {error && (
        <div className="mb-6 rounded-md bg-red-50 p-4 text-sm text-red-700 dark:bg-red-900/30 dark:text-red-300">
          {error}
        </div>
      )}

      {widgets.length === 0 ? (
        <div className="rounded-lg border border-dashed border-gray-300 bg-white px-6 py-16 dark:border-white/10 dark:bg-gray-900">
          <EmptyState
            icon={Squares2X2Icon}
            iconClassName="text-indigo-400 dark:text-indigo-500"
            title="Noch keine Widgets"
            description="Füge dein erstes Widget hinzu und baue dir dein persönliches Dashboard."
            actionLabel="Widget hinzufügen"
            onAction={openEmptyDashboardWidgetPicker}
          />
        </div>
      ) : (
        <div
          ref={gridRef}
          onPointerDown={handleGridPointerDown}
          onPointerMove={handleGridPointerMove}
          onPointerUp={handleGridPointerEnd}
          onPointerCancel={handleGridPointerEnd}
          className={classNames(
            'group/dashboard relative grid rounded-lg border p-4 transition',
            isEditingDashboard
              ? 'cursor-crosshair border-gray-200 bg-gray-50 dark:border-white/10 dark:bg-gray-950/40'
              : 'border-transparent bg-transparent',
          )}
          style={{
            gridTemplateColumns: `repeat(${GRID_COLUMNS}, minmax(0, 1fr))`,
            gridAutoRows: `${GRID_ROW_HEIGHT}px`,
            gap: `${GRID_GAP}px`,
            minHeight: dashboardHeight + GRID_PADDING * 2,
          }}
        >
          {isEditingDashboard && (
            <div
              aria-hidden="true"
              className="pointer-events-none absolute inset-4 z-0 grid overflow-hidden rounded-lg"
              style={{
                gridTemplateColumns: `repeat(${GRID_COLUMNS}, minmax(0, 1fr))`,
                gridAutoRows: `${GRID_ROW_HEIGHT}px`,
                gap: `${GRID_GAP}px`,
              }}
            >
              {Array.from({ length: dashboardRows * GRID_COLUMNS }).map((_, index) => (
                <div
                  key={index}
                  className="rounded-md border border-indigo-500/20 bg-indigo-500/[0.015]"
                />
              ))}
            </div>
          )}

          {drawPlacement && (
            <div
              className="pointer-events-none z-10 rounded-lg border-2 border-dashed border-indigo-600 bg-indigo-500/10 ring-4 ring-indigo-500/10"
              style={{
                gridColumn: `${drawPlacement.x + 1} / span ${drawPlacement.w}`,
                gridRow: `${drawPlacement.y + 1} / span ${drawPlacement.h}`,
              }}
            />
          )}

          {previewWidget && (
            <div
              className="pointer-events-none z-10 rounded-lg border-2 border-dashed border-indigo-500 bg-indigo-500/10 ring-4 ring-indigo-500/10"
              style={{
                gridColumn: `${previewWidget.x + 1} / span ${previewWidget.w}`,
                gridRow: `${previewWidget.y + 1} / span ${previewWidget.h}`,
              }}
            />
          )}
          {widgets.map((widget) => (
            <section
              key={widget.id}
              data-dashboard-widget
              className={classNames(
                'group relative z-20 flex min-h-0 flex-col overflow-hidden rounded-lg bg-white shadow-sm ring-1 ring-gray-200 transition dark:bg-gray-900 dark:ring-white/10',
                activeWidgetId === widget.id && 'opacity-60 ring-2 ring-indigo-300 dark:ring-indigo-500',
              )}
              style={{
                gridColumn: `${widget.x + 1} / span ${widget.w}`,
                gridRow: `${widget.y + 1} / span ${widget.h}`,
              }}
            >
              <div
                onPointerDown={(event) => startMovingWidget(event, widget)}
                className={classNames(
                  'flex shrink-0 touch-none items-center justify-between gap-x-3 border-b border-gray-200 px-4 py-3 select-none dark:border-white/10',
                  isEditingDashboard ? 'cursor-move' : 'cursor-default',
                )}
              >
                <div className="min-w-0">
                  <h2 className="truncate text-sm font-semibold text-gray-900 dark:text-white">
                    {widget.title}
                  </h2>

                  <p className="text-xs text-gray-500 dark:text-gray-400">
                    {getWidgetTypeDefinition(widget.type).label}
                  </p>
                </div>

                <div className="flex shrink-0 items-center gap-x-1">
                  {isEditingDashboard && (
                    <>
                      <button
                        type="button"
                        title="Widget konfigurieren"
                        aria-label="Widget konfigurieren"
                        onPointerDown={(event) => {
                          event.stopPropagation()
                        }}
                        onPointerUp={(event) => {
                          event.stopPropagation()
                        }}
                        onClick={(event) => {
                          event.stopPropagation()
                          setSettingsWidget(widget)
                        }}
                        className="inline-flex size-8 items-center justify-center rounded-md text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/10 dark:hover:text-gray-200"
                      >
                        <Cog6ToothIcon aria-hidden="true" className="size-4" />
                      </button>

                      <button
                        type="button"
                        title="Widget löschen"
                        aria-label="Widget löschen"
                        onPointerDown={(event) => {
                          event.stopPropagation()
                        }}
                        onPointerUp={(event) => {
                          event.stopPropagation()
                        }}
                        onClick={(event) => {
                          event.stopPropagation()
                          void deleteWidget(widget.id)
                        }}
                        className="inline-flex size-8 items-center justify-center rounded-md text-red-600 hover:bg-red-50 hover:text-red-700 dark:text-red-400 dark:hover:bg-red-500/10 dark:hover:text-red-300"
                      >
                        <TrashIcon aria-hidden="true" className="size-4" />
                      </button>
                    </>
                  )}
                </div>
              </div>

              <div
                className={classNames(
                  'min-h-0 flex-1',
                  widget.type === 'operationMap'
                    ? 'overflow-hidden p-0'
                    : 'overflow-auto p-4',
                )}
              >
                <DashboardWidgetRenderer
                  widget={widget}
                  username={username}
                  apiUrl={API_URL}
                  widgetCount={widgets.length}
                />
              </div>

              {isEditingDashboard &&
                (['nw', 'ne', 'sw', 'se'] as ResizeDirection[]).map((direction) => (
                  <button
                    key={direction}
                    type="button"
                    aria-label="Widget skalieren"
                    onPointerDown={(event) => startResizingWidget(event, widget, direction)}
                    className={classNames(
                      'absolute z-20 size-4 rounded-full border-2 border-white bg-indigo-600 shadow-sm opacity-0 ring-1 ring-black/10 transition group-hover:opacity-100 focus:opacity-100 dark:border-gray-900',
                      'touch-none',
                      getResizeHandleClass(direction),
                    )}
                  />
                ))}
            </section>
          ))}
        </div>
      )}

      <WidgetPickerModal
        open={isWidgetPickerOpen}
        setOpen={handleWidgetPickerOpenChange}
        widgetTypes={widgetTypes}
        isSaving={isSaving}
        onCreateWidget={createWidget}
      />

      <WidgetSettingsModal
        open={settingsWidget !== null}
        setOpen={(open) => {
          if (!open) {
            setSettingsWidget(null)
          }
        }}
        widget={settingsWidget}
        isSaving={isSaving}
        onSave={updateWidgetSettings}
      />
    </div>
  )
}