<script setup lang="ts">
import { computed, onMounted, reactive, ref, watch } from 'vue'
import { IotDeviceApi } from '@/api/pms/device'
import * as DeptApi from '@/api/system/dept'
import DeviceMonitorDrawer from '@/views/pms/map/DeviceMonitorDrawer.vue'
import { DICT_TYPE, getDictLabel } from '@/utils/dict'
import { defaultProps, handleTree } from '@/utils/tree'

type MapMode = 'TMAP_NORMAL_MAP' | 'TMAP_SATELLITE_MAP'
type StatusFilter = 'all' | 'online' | 'offline'

type MapDevice = {
  id: number
  deviceCode: string
  deviceName: string
  lat: number
  lng: number
  location: string
  deptId: number
  ifInline: number
  lastInlineTime: string
  deviceStatus: string
}

type MarkerGlyphName = 'mdi:access-point' | 'mdi:access-point-off'

type MarkerTheme = {
  key: 'online' | 'offline'
  iconName: MarkerGlyphName
  startColor: string
  endColor: string
  ringColor: string
  badgeColor: string
  statusColor: string
  shadowColor: string
  iconColor: string
}

const { t } = useI18n()
const message = useMessage()

const loading = ref(false)
const deptList = ref<any[]>([])
const treeList = ref<any[]>([])
const mapRef = ref<any>(null)
const activeDevice = ref<MapDevice | null>(null)
const infoWindowTarget = ref<[number, number] | null>(null)
const showDrawer = ref<any>(null)
const drawerVisible = ref(false)
const deviceId = ref<number | undefined>(undefined)
const drawerDeviceName = ref('')
const lastLineTime = ref('')
const ifLine = ref<number | undefined>(undefined)
const dept = ref('')
const deviceCode = ref('')

const keyword = ref('')
const statusFilter = ref<StatusFilter>('all')
const mapMode = ref<MapMode>('TMAP_NORMAL_MAP')

const RUIHENG_XINGYU_DEPT_ID = 157
const DEFAULT_DEPT_ID = 156
const initialCenter: [number, number] = [104.114129, 36.550339]
const initialZoom = 5
const mapControls = ['Scale']
const isZhzqSource = () => sessionStorage.getItem('LOGIN_SOURCE') === 'zhzq'
const isZhlySource = () => sessionStorage.getItem('LOGIN_SOURCE') === 'zhly'

const queryParams = reactive({
  deptId: isZhzqSource() ? RUIHENG_XINGYU_DEPT_ID : DEFAULT_DEPT_ID
})

const devices = ref<MapDevice[]>([])
const currentZoom = ref(initialZoom)
const markerIconCache = new Map<
  string,
  { iconUrl: string; iconSize: [number, number]; iconAnchor: [number, number] }
>()

const markerGlyphBodyMap: Record<MarkerGlyphName, string> = {
  'mdi:access-point':
    '<path fill="currentColor" d="M4.93 4.93A9.97 9.97 0 0 0 2 12c0 2.76 1.12 5.26 2.93 7.07l1.41-1.41A7.94 7.94 0 0 1 4 12c0-2.21.89-4.22 2.34-5.66zm14.14 0l-1.41 1.41A7.96 7.96 0 0 1 20 12c0 2.22-.89 4.22-2.34 5.66l1.41 1.41A9.97 9.97 0 0 0 22 12c0-2.76-1.12-5.26-2.93-7.07M7.76 7.76A5.98 5.98 0 0 0 6 12c0 1.65.67 3.15 1.76 4.24l1.41-1.41A4 4 0 0 1 8 12c0-1.11.45-2.11 1.17-2.83zm8.48 0l-1.41 1.41A4 4 0 0 1 16 12c0 1.11-.45 2.11-1.17 2.83l1.41 1.41A5.98 5.98 0 0 0 18 12c0-1.65-.67-3.15-1.76-4.24M12 10a2 2 0 0 0-2 2a2 2 0 0 0 2 2a2 2 0 0 0 2-2a2 2 0 0 0-2-2"/>',
  'mdi:access-point-off':
    '<path fill="currentColor" d="M20.84 22.73L12.1 14H12a2 2 0 0 1-2-2v-.1l-1.6-1.61c-.25.52-.4 1.09-.4 1.71c0 1.11.45 2.11 1.17 2.83l-1.41 1.41A5.98 5.98 0 0 1 6 12c0-1.17.34-2.26.93-3.18L5.5 7.37C4.55 8.67 4 10.27 4 12c0 2.22.89 4.22 2.34 5.66l-1.41 1.41A9.97 9.97 0 0 1 2 12c0-2.28.77-4.37 2.06-6.05L1.11 3l1.28-1.27l19.72 19.73zm-4.91-10l1.6 1.6c.3-.72.47-1.5.47-2.33c0-1.65-.67-3.15-1.76-4.24l-1.41 1.41a3.99 3.99 0 0 1 1.1 3.56m3.1 3.1l1.47 1.45c.94-1.53 1.5-3.34 1.5-5.28c0-2.76-1.12-5.26-2.93-7.07l-1.41 1.41A7.96 7.96 0 0 1 20 12c0 1.39-.35 2.7-.97 3.83"/>'
}

const markerThemeMap: Record<'online' | 'offline', MarkerTheme> = {
  online: {
    key: 'online',
    iconName: 'mdi:access-point',
    startColor: '#0f766e',
    endColor: '#2dd4bf',
    ringColor: '#ccfbf1',
    badgeColor: '#f8fffd',
    statusColor: '#10b981',
    shadowColor: 'rgba(15, 118, 110, 0.3)',
    iconColor: '#0f766e'
  },
  offline: {
    key: 'offline',
    iconName: 'mdi:access-point-off',
    startColor: '#ea580c',
    endColor: '#fb7185',
    ringColor: '#ffe4e6',
    badgeColor: '#fffaf8',
    statusColor: '#f97316',
    shadowColor: 'rgba(234, 88, 12, 0.28)',
    iconColor: '#c2410c'
  }
}

const statusOptions = [
  { label: '全部', value: 'all' },
  { label: '在线', value: 'online' },
  { label: '离线', value: 'offline' }
] as const

const totalCount = computed(() => devices.value.length)
const onlineCount = computed(() => devices.value.filter((item) => isOnline(item)).length)
const offlineCount = computed(() => devices.value.filter((item) => !isOnline(item)).length)

const filteredDevices = computed(() => {
  const text = keyword.value.trim().toLowerCase()
  return devices.value.filter((item) => {
    const matchKeyword =
      !text ||
      [item.deviceName, item.deviceCode, item.location]
        .filter(Boolean)
        .some((value) => String(value).toLowerCase().includes(text))

    const matchStatus =
      statusFilter.value === 'all' ||
      (statusFilter.value === 'online' && isOnline(item)) ||
      (statusFilter.value === 'offline' && !isOnline(item))

    return matchKeyword && matchStatus
  })
})

const filteredCount = computed(() => filteredDevices.value.length)
const markerDevices = computed(() =>
  filteredDevices.value
    .filter((item) => hasValidPosition(item))
    .map((item) => ({
      ...item,
      position: [Number(item.lng), Number(item.lat)] as [number, number],
      icon: getMarkerIcon(item)
    }))
)
const clusterMarkers = computed(() =>
  markerDevices.value.map((item) => ({
    position: item.position,
    icon: item.icon,
    title: item.deviceName || item.deviceCode || '',
    zIndexOffset: isOnline(item) ? 240 : 180,
    extData: item
  }))
)
const deptNameMap = computed(() => {
  return deptList.value.reduce((acc, cur) => {
    acc.set(Number(cur.id), String(cur.name).trim())
    return acc
  }, new Map<number, string>())
})

onMounted(async () => {
  await Promise.all([loadDeptTree(), loadDevices()])
})

const loadDeptTree = async () => {
  const res = isZhzqSource()
    ? await DeptApi.specifiedSimpleDepts(RUIHENG_XINGYU_DEPT_ID)
    : await DeptApi.getSimpleDeptList()
  deptList.value = res || []
  treeList.value = handleTree(res || [])
}

const loadDevices = async () => {
  loading.value = true
  try {
    const res = await IotDeviceApi.getMapDevice({
      ...queryParams,
      ...(isZhlySource() ? { source: 'ly' } : {})
    })
    devices.value = res || []
  } catch (error) {
    message.error('地图设备加载失败，请稍后重试')
  } finally {
    loading.value = false
  }
}

const handleDeptChange = () => {
  return loadDevices()
}

const handleMapInit = (map: any) => {
  mapRef.value = map
  applyMapMode()
  syncCurrentZoom()
}

const syncCurrentZoom = () => {
  currentZoom.value = mapRef.value?.getZoom?.() ?? initialZoom
}

const zoomIn = () => {
  mapRef.value?.zoomIn?.()
}

const zoomOut = () => {
  mapRef.value?.zoomOut?.()
}

const switchMapMode = (mode: MapMode) => {
  mapMode.value = mode
  applyMapMode()
}

const applyMapMode = () => {
  if (!mapRef.value) return
  ;(mapRef.value as any).setMapType?.((window as any)[mapMode.value])
}

const isOnline = (device?: MapDevice | null) => {
  return Number(device?.ifInline) === 3
}

const hasValidPosition = (device?: MapDevice | null) => {
  const lng = Number(device?.lng)
  const lat = Number(device?.lat)

  return Number.isFinite(lng) && Number.isFinite(lat) && lng !== 0 && lat !== 0
}

const getMarkerIcon = (device?: MapDevice | null) => {
  const theme = isOnline(device) ? markerThemeMap.online : markerThemeMap.offline
  const cachedIcon = markerIconCache.get(theme.key)

  if (cachedIcon) return cachedIcon

  const markerIcon = {
    iconUrl: svgToDataUri(buildMarkerSvg(theme)),
    iconSize: [46, 58] as [number, number],
    iconAnchor: [23, 49] as [number, number]
  }

  markerIconCache.set(theme.key, markerIcon)
  return markerIcon
}

const buildMarkerSvg = (theme: MarkerTheme) => {
  const glyph = renderMarkerGlyph(theme.iconName, 18)

  return `
    <svg xmlns="http://www.w3.org/2000/svg" width="46" height="58" viewBox="0 0 46 58" fill="none">
      <defs>
        <linearGradient id="marker-gradient-${theme.key}" x1="10" y1="6" x2="35" y2="45" gradientUnits="userSpaceOnUse">
          <stop stop-color="${theme.startColor}" />
          <stop offset="1" stop-color="${theme.endColor}" />
        </linearGradient>
        <filter id="marker-shadow-${theme.key}" x="2" y="1" width="42" height="54" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
          <feDropShadow dx="0" dy="6" stdDeviation="4" flood-color="${theme.shadowColor}" />
        </filter>
      </defs>
      <ellipse cx="23" cy="54.5" rx="8.5" ry="2.5" fill="rgba(15, 23, 42, 0.12)" />
      <g filter="url(#marker-shadow-${theme.key})">
        <path
          d="M23 3C14.72 3 8 9.72 8 18c0 11.39 11.46 23.89 14.02 30.68a1.06 1.06 0 0 0 1.96 0C26.54 41.89 38 29.39 38 18C38 9.72 31.28 3 23 3Z"
          fill="url(#marker-gradient-${theme.key})"
        />
        <path
          d="M23 3C14.72 3 8 9.72 8 18c0 11.39 11.46 23.89 14.02 30.68a1.06 1.06 0 0 0 1.96 0C26.54 41.89 38 29.39 38 18C38 9.72 31.28 3 23 3Z"
          stroke="rgba(255, 255, 255, 0.74)"
          stroke-width="1.2"
        />
        <path
          d="M14 10.5C16.72 7.56 19.82 6.09 23.31 6.09c2.98 0 5.81 1.02 8.48 3.06c2.66 2.04 4 4.44 4 7.18c0 1.09-.2 2.18-.6 3.28c-.4-4.09-1.85-7.16-4.35-9.23c-2.49-2.06-5.18-3.1-8.08-3.1c-3.09 0-6 1.08-8.76 3.22Z"
          fill="rgba(255, 255, 255, 0.22)"
        />
        <circle cx="23" cy="18" r="11.4" fill="${theme.badgeColor}" />
        <circle cx="23" cy="18" r="11.4" stroke="${theme.ringColor}" stroke-width="1.8" />
        <g transform="translate(14 9)" color="${theme.iconColor}" fill="${theme.iconColor}">
          ${glyph}
        </g>
        <circle cx="31.2" cy="26.6" r="3.7" fill="${theme.statusColor}" stroke="white" stroke-width="1.4" />
      </g>
    </svg>
  `.trim()
}

const renderMarkerGlyph = (iconName: MarkerGlyphName, boxSize = 18) => {
  const iconBody = markerGlyphBodyMap[iconName]
  const iconScale = boxSize / 24

  return `
    <g transform="scale(${iconScale})">
      ${iconBody}
    </g>
  `.trim()
}

const svgToDataUri = (svg: string) => {
  return `data:image/svg+xml;charset=UTF-8,${encodeURIComponent(svg)}`
}

const clusterStyles = [
  {
    size: 80,
    textColor: '#ffffff',
    textSize: 14,
    range: [0, 20]
  },
  {
    size: 80,
    textColor: '#ffffff',
    textSize: 14,
    range: [20, 100]
  },
  {
    size: 80,
    textColor: '#ffffff',
    textSize: 14,
    range: [100, 99999]
  }
]

const handleMarkerClick = (e: any) => {
  const device = e?.layer?.extData as MapDevice | undefined

  if (!device) return

  openInfoWindow(device, resolveLngLat(e?.lnglat))
}

const handleInfoWindowTargetChange = (target: [number, number] | null) => {
  infoWindowTarget.value = target

  if (!target) {
    activeDevice.value = null
  }
}

const openInfoWindow = (device: MapDevice, target?: [number, number] | null) => {
  if (!target && !hasValidPosition(device)) return

  activeDevice.value = device
  infoWindowTarget.value = target ?? [Number(device.lng), Number(device.lat)]
}

const closeInfoWindow = () => {
  activeDevice.value = null
  infoWindowTarget.value = null
}

const resolveLngLat = (lnglat: any): [number, number] | null => {
  const lng = Number(lnglat?.lng ?? lnglat?.getLng?.())
  const lat = Number(lnglat?.lat ?? lnglat?.getLat?.())

  if (!Number.isFinite(lng) || !Number.isFinite(lat)) {
    return null
  }

  return [lng, lat]
}

const getDeptName = (device?: MapDevice | null) => {
  return deptNameMap.value.get(Number(device?.deptId)) || '-'
}

const getDeviceStatusText = (device?: MapDevice | null) => {
  return getDictLabel(DICT_TYPE.PMS_DEVICE_STATUS, device?.deviceStatus) || '-'
}

const getDeviceOnlineText = (device?: MapDevice | null) => {
  return (
    getDictLabel(DICT_TYPE.IOT_DEVICE_STATUS, device?.ifInline) ||
    (isOnline(device) ? '在线' : '离线')
  )
}

const getDeviceLocationText = (device?: MapDevice | null) => {
  const location = String(device?.location || '')
    .replaceAll('"', '')
    .trim()

  return location || '-'
}

const openDeviceDrawer = () => {
  if (!activeDevice.value) return

  drawerVisible.value = true
  deviceId.value = activeDevice.value.id
  deviceCode.value = activeDevice.value.deviceCode || ''
  drawerDeviceName.value = activeDevice.value.deviceName || ''
  ifLine.value = activeDevice.value.ifInline
  lastLineTime.value = activeDevice.value.lastInlineTime || ''
  dept.value = getDeptName(activeDevice.value)
  showDrawer.value?.openDrawer?.()
}

watch(filteredDevices, (list) => {
  if (!activeDevice.value) return

  const nextDevice = list.find(
    (item) => item.id === activeDevice.value?.id && hasValidPosition(item)
  )

  if (!nextDevice) {
    closeInfoWindow()
    return
  }

  activeDevice.value = nextDevice
  infoWindowTarget.value = [Number(nextDevice.lng), Number(nextDevice.lat)]
})

const handleClusterInit = (clusterer: any) => {
  clusterer?.setMaxZoom?.(16)
}
</script>

<template>
  <div v-loading="loading" class="device-map-page">
    <tdt-map
      :center="initialCenter"
      :zoom="initialZoom"
      :controls="mapControls"
      :min-zoom="3"
      @init="handleMapInit"
      @zoomend="syncCurrentZoom"
    >
      <tdt-marker-clusterer
        :markers="clusterMarkers"
        :styles="clusterStyles"
        @click="handleMarkerClick"
        @init="handleClusterInit"
      />

      <tdt-infowindow
        :target="infoWindowTarget"
        :min-width="280"
        :max-width="360"
        :offset="[0, -30]"
        :auto-pan="true"
        :close-on-click="true"
        @update:target="handleInfoWindowTargetChange"
      >
        <div v-if="activeDevice" class="device-infowindow">
          <div class="device-infowindow__list">
            <div class="device-infowindow__row">
              <span>历史编码</span>
              <strong>{{ activeDevice.deviceCode || '-' }}</strong>
            </div>
            <div class="device-infowindow__row">
              <span>设备名称</span>
              <strong>{{ activeDevice.deviceName || '-' }}</strong>
            </div>
            <div class="device-infowindow__row">
              <span>所在部门</span>
              <strong>{{ getDeptName(activeDevice) }}</strong>
            </div>
            <div class="device-infowindow__row">
              <span>位置</span>
              <strong>{{ getDeviceLocationText(activeDevice) }}</strong>
            </div>
            <div class="device-infowindow__row">
              <span>状态</span>
              <strong>{{ getDeviceStatusText(activeDevice) }}</strong>
            </div>
            <div class="device-infowindow__row">
              <span>在线状态</span>
              <strong>{{ getDeviceOnlineText(activeDevice) }}</strong>
            </div>
            <div class="device-infowindow__row">
              <span>最后在线时间</span>
              <strong>{{ activeDevice.lastInlineTime || '暂无数据' }}</strong>
            </div>
          </div>

          <div class="device-infowindow__footer">
            <button type="button" class="device-infowindow__button" @click="openDeviceDrawer">
              查看监测详情
            </button>
          </div>
        </div>
      </tdt-infowindow>

      <tdt-control position="topleft" visible>
        <div
          class="floating-panel hero-panel"
          @click.stop
          @dblclick.stop
          @mousedown.stop
          @mouseup.stop
          @mousemove.stop
          @pointerdown.stop
          @pointerup.stop
          @pointermove.stop
          @touchstart.stop
          @touchmove.stop
          @touchend.stop
          @wheel.stop
        >
          <div class="hero-panel__head">
            <div>
              <div class="hero-panel__kicker">PMS · TianDiTu</div>
              <div class="hero-panel__title">设备智览地图</div>
              <div class="hero-panel__desc">
                设备位置、在线状态与监测入口，支持部门筛选、聚合放大和一键追踪。
              </div>
            </div>
            <div
              class="hero-panel__map-mode -translate-y-1"
              :class="mapMode === 'TMAP_SATELLITE_MAP' ? 'is-satellite' : 'is-map'"
            >
              <button
                type="button"
                class="mode-chip text-gray-6"
                :class="{ 'is-active': mapMode === 'TMAP_NORMAL_MAP' }"
                @click="switchMapMode('TMAP_NORMAL_MAP')"
              >
                地图
              </button>
              <button
                type="button"
                class="mode-chip text-gray-6"
                :class="{ 'is-active': mapMode === 'TMAP_SATELLITE_MAP' }"
                @click="switchMapMode('TMAP_SATELLITE_MAP')"
              >
                卫星
              </button>
            </div>
          </div>

          <div class="hero-stats">
            <div class="hero-stat hero-stat--primary">
              <span>设备总数</span>
              <strong>{{ totalCount }}</strong>
            </div>
            <div class="hero-stat hero-stat--success">
              <span>在线设备</span>
              <strong>{{ onlineCount }}</strong>
            </div>
            <div class="hero-stat hero-stat--danger">
              <span>离线设备</span>
              <strong>{{ offlineCount }}</strong>
            </div>
            <div class="hero-stat hero-stat--neutral">
              <span>筛选结果</span>
              <strong>{{ filteredCount }}</strong>
            </div>
          </div>
        </div>
      </tdt-control>

      <tdt-control position="topright" visible>
        <div
          class="floating-panel toolbar-panel p-4!"
          @click.stop
          @dblclick.stop
          @mousedown.stop
          @mouseup.stop
          @mousemove.stop
          @pointerdown.stop
          @pointerup.stop
          @pointermove.stop
          @touchstart.stop
          @touchmove.stop
          @touchend.stop
          @wheel.stop
        >
          <el-form size="default" label-position="top">
            <el-form-item label="关键词">
              <el-input v-model="keyword" clearable placeholder="设备名称 / 编码 / 位置">
                <template #prefix>
                  <Icon icon="ep:search" />
                </template>
              </el-input>
            </el-form-item>

            <el-form-item label="部门筛选">
              <el-tree-select
                v-model="queryParams.deptId"
                :data="treeList"
                :props="defaultProps"
                check-strictly
                node-key="id"
                filterable
                clearable
                :placeholder="t('deviceForm.deptHolder')"
                @change="handleDeptChange"
              />
            </el-form-item>

            <el-form-item label="设备状态">
              <div class="status-tabs">
                <button
                  v-for="item in statusOptions"
                  :key="item.value"
                  type="button"
                  class="status-tab"
                  :class="{ 'is-active': statusFilter === item.value }"
                  @click="statusFilter = item.value"
                >
                  {{ item.label }}
                </button>
              </div>
            </el-form-item>
          </el-form>

          <div class="toolbar-panel__footer">
            <div class="toolbar-badges">
              <span class="toolbar-badge">缩放 {{ currentZoom }}</span>
            </div>

            <div class="toolbar-actions">
              <button type="button" class="icon-action" @click="zoomOut">
                <Icon icon="ep:minus" />
              </button>
              <button type="button" class="icon-action" @click="zoomIn">
                <Icon icon="ep:plus" />
              </button>
            </div>
          </div>
        </div>
      </tdt-control>

      <tdt-control position="bottomright" visible>
        <div class="floating-panel p-4!">
          <div class="legend-panel__title">图例说明</div>
          <div class="legend-list">
            <div class="legend-item">
              <span class="legend-chip legend-chip--online">
                <span class="i-mdi:access-point legend-chip__icon"></span>
              </span>
              <span>在线设备</span>
            </div>
            <div class="legend-item">
              <span class="legend-chip legend-chip--offline">
                <span class="i-mdi:access-point-off legend-chip__icon"></span>
              </span>
              <span>离线设备</span>
            </div>
            <div class="legend-item">
              <span class="legend-chip legend-chip--cluster">
                <span class="i-mdi:google-circles-communities legend-chip__icon"></span>
              </span>
              <span>聚合点位</span>
            </div>
          </div>
        </div>
      </tdt-control>
    </tdt-map>

    <DeviceMonitorDrawer
      :model-value="drawerVisible"
      @update:model-value="(val) => (drawerVisible = val)"
      :id="deviceId"
      :deviceName="drawerDeviceName"
      :lastLineTime="lastLineTime"
      :ifLine="ifLine"
      :dept="dept"
      :deviceCode="deviceCode"
      ref="showDrawer"
    />
  </div>
</template>

<style scoped lang="scss">
.device-map-page {
  --page-height: calc(
    100vh - 20px - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height)
  );
  --panel-bg: rgb(255 255 255 / 78%);
  --panel-border: rgb(255 255 255 / 58%);
  --panel-shadow: 0 22px 50px rgb(15 23 42 / 14%);
  --text-main: #0f172a;
  --text-sub: #526072;
  --teal: #0f766e;
  --orange: #f97316;
  --slate: #334155;

  position: relative;
  height: var(--page-height);
  overflow: hidden;
  border: 1px solid rgb(203 213 225 / 70%);
  border-radius: 20px;
  box-shadow: 0 24px 60px rgb(15 23 42 / 12%);
}

.floating-panel {
  padding: 24px;
  background: var(--panel-bg);
  border: 1px solid var(--panel-border);
  border-radius: 16px;
  box-shadow: var(--panel-shadow);
  backdrop-filter: blur(18px);
}

.hero-panel__head {
  display: flex;
  gap: 20px;
  justify-content: space-between;
}

.hero-panel__kicker {
  margin-bottom: 6px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.16em;
  color: var(--teal);
  text-transform: uppercase;
}

.hero-panel__title {
  font-size: 28px;
  font-weight: 700;
  line-height: 1.15;
  color: var(--text-main);
}

.hero-panel__desc {
  max-width: 460px;
  margin-top: 8px;
  font-size: 14px;
  line-height: 1.6;
  color: var(--text-sub);
}

.hero-panel__map-mode {
  position: relative;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  height: fit-content;
  min-width: 152px;
  padding: 4px;
  gap: 0;
  align-items: center;
  background: linear-gradient(180deg, rgb(255 255 255 / 92%), rgb(241 245 249 / 90%));
  border: 1px solid rgb(255 255 255 / 90%);
  border-radius: 999px;
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 92%),
    0 8px 20px rgb(15 23 42 / 8%);
}

.hero-panel__map-mode::before {
  position: absolute;
  top: 4px;
  left: 4px;
  width: calc((100% - 8px) / 2);
  height: calc(100% - 8px);
  background: linear-gradient(135deg, #0f766e 0%, #14b8a6 100%);
  border-radius: 999px;
  content: '';
  transform: translateX(0);
  box-shadow:
    0 6px 14px rgb(15 118 110 / 16%),
    inset 0 1px 0 rgb(255 255 255 / 22%);
  transition: transform 0.22s ease;
}

.hero-panel__map-mode.is-satellite::before {
  transform: translateX(100%);
}

.mode-chip {
  position: relative;
  z-index: 1;
  display: inline-flex;
  height: 36px;
  min-width: 0;
  padding: 0 18px;
  font-size: 13px;
  font-weight: 600;
  line-height: 1;
  text-align: center;
  white-space: nowrap;
  cursor: pointer;
  background: transparent;
  border: none;
  border-radius: 999px;
  transition:
    color 0.18s ease,
    opacity 0.18s ease;
  align-items: center;
  justify-content: center;
}

.mode-chip:not(.is-active) {
  opacity: 0.82;
}

.mode-chip.is-active {
  color: #fff;
  opacity: 1;
}

.hero-stats {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
  margin-top: 18px;
}

.hero-stat {
  padding: 14px 16px;
  background: rgb(255 255 255 / 72%);
  border-radius: 20px;
}

.hero-stat span {
  display: block;
  font-size: 12px;
  color: var(--text-sub);
}

.hero-stat strong {
  display: block;
  margin-top: 8px;
  font-size: 28px;
  line-height: 1;
  color: var(--text-main);
}

.hero-stat--primary {
  background: linear-gradient(135deg, rgb(15 118 110 / 12%), rgb(20 184 166 / 8%));
}

.hero-stat--success {
  background: linear-gradient(135deg, rgb(34 197 94 / 12%), rgb(45 212 191 / 8%));
}

.hero-stat--danger {
  background: linear-gradient(135deg, rgb(249 115 22 / 12%), rgb(239 68 68 / 8%));
}

.hero-stat--neutral {
  background: linear-gradient(135deg, rgb(148 163 184 / 12%), rgb(203 213 225 / 8%));
}

.toolbar-panel {
  width: min(320px, calc(100vw - 64px));
}

.status-tabs {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
  width: 100%;
}

.status-tab {
  padding: 10px 12px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-sub);
  cursor: pointer;
  background: rgb(255 255 255 / 72%);
  border: 1px solid rgb(148 163 184 / 18%);
  border-radius: 14px;
  transition: all 0.22s ease;
}

.status-tab.is-active {
  color: var(--teal);
  background: linear-gradient(135deg, rgb(15 118 110 / 12%), rgb(20 184 166 / 8%));
  border-color: rgb(15 118 110 / 18%);
}

.toolbar-panel__footer {
  display: flex;
  gap: 14px;
  justify-content: space-between;
  align-items: center;
  margin-top: 16px;
}

.toolbar-badges {
  display: flex;
  flex: 1;
  gap: 8px;
  flex-wrap: wrap;
}

.toolbar-badge {
  display: inline-flex;
  padding: 7px 10px;
  font-size: 12px;
  font-weight: 600;
  color: var(--slate);
  background: rgb(255 255 255 / 86%);
  border-radius: 999px;
  align-items: center;
}

.toolbar-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: center;
}

.icon-action {
  display: inline-flex;
  width: 40px;
  height: 40px;
  font-size: 16px;
  color: var(--text-main);
  cursor: pointer;
  background: linear-gradient(135deg, rgb(255 255 255 / 96%), rgb(248 250 252 / 86%));
  border: none;
  border-radius: 14px;
  box-shadow: 0 12px 24px rgb(15 23 42 / 8%);
  transition: transform 0.2s ease;
  justify-content: center;
  align-items: center;
}

.icon-action:hover {
  transform: translateY(-1px);
}

.legend-panel__title {
  margin-bottom: 10px;
  font-size: 14px;
  font-weight: 700;
  color: var(--text-main);
}

.legend-list {
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
}

.legend-item {
  display: inline-flex;
  font-size: 13px;
  color: var(--text-sub);
  gap: 8px;
  align-items: center;
}

.legend-chip {
  display: inline-flex;
  width: 26px;
  height: 26px;
  color: #fff;
  border-radius: 999px;
  box-shadow: 0 10px 22px rgb(15 23 42 / 12%);
  align-items: center;
  justify-content: center;
}

.legend-chip--online {
  background: linear-gradient(135deg, #0f766e, #2dd4bf);
}

.legend-chip--offline {
  background: linear-gradient(135deg, #ea580c, #fb7185);
}

.legend-chip--cluster {
  background: linear-gradient(135deg, #0369a1, #1d4ed8);
}

.legend-chip__icon {
  font-size: 14px;
}

.device-infowindow {
  min-width: 280px;
  color: var(--text-main);
}

.device-infowindow__list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.device-infowindow__row {
  display: grid;
  grid-template-columns: 92px minmax(0, 1fr);
  gap: 12px;
  padding: 10px 12px;
  background: linear-gradient(180deg, rgb(248 250 252 / 96%), rgb(241 245 249 / 90%));
  border: 1px solid rgb(226 232 240 / 90%);
  border-radius: 14px;
  align-items: start;
}

.device-infowindow__row span {
  font-size: 12px;
  font-weight: 600;
  line-height: 1.6;
  color: var(--text-sub);
}

.device-infowindow__row strong {
  font-size: 13px;
  font-weight: 600;
  line-height: 1.5;
  color: var(--text-main);
  word-break: break-word;
}

.device-infowindow__footer {
  margin-top: 14px;
}

.device-infowindow__button {
  display: inline-flex;
  width: 100%;
  padding: 11px 16px;
  font-size: 13px;
  font-weight: 700;
  color: #fff;
  cursor: pointer;
  background: linear-gradient(135deg, #0f766e, #14b8a6);
  border: none;
  border-radius: 14px;
  box-shadow: 0 12px 22px rgb(15 118 110 / 20%);
  transition:
    transform 0.18s ease,
    box-shadow 0.18s ease,
    opacity 0.18s ease;
  justify-content: center;
  align-items: center;
}

.device-infowindow__button:hover {
  transform: translateY(-1px);
  box-shadow: 0 14px 26px rgb(15 118 110 / 24%);
}

.device-infowindow__button:active {
  opacity: 0.92;
}

@media (width <= 640px) {
  .device-infowindow {
    min-width: 236px;
  }

  .device-infowindow__row {
    grid-template-columns: 1fr;
    gap: 6px;
  }
}
</style>
