<template>
  <div class="warehouse-map">
    <div
      v-if="gridData.length === 0"
      class="no-data"
    >
      暂无数据
    </div>
    <div
      v-else
      ref="mapWrapperRef"
      class="map-wrapper"
    >
      <div
        class="map-grid"
        :style="gridStyle"
      >
        <div
          v-for="(cell, index) in gridData"
          :key="index"
          :class="['grid-cell', getCellClass(cell)]"
          :style="getCellStyle(cell)"
          @mousedown.left="handleCellMouseDown(index, $event)"
          @mouseenter="handleCellMouseEnter(cell, index, $event)"
          @mousemove="handleCellMouseMove($event)"
          @mouseleave="handleCellMouseLeave"
          @click="handleCellClick(cell)"
          @contextmenu.prevent="handleCellContextMenu(cell)"
        >
          <div
            v-if="isLocationCell(cell) && isCellVisible(cell)"
            class="cell-content"
          >
            <div
              class="category-badge"
              :style="getCategoryStyle(cell)"
            >
              {{ getHeatLabel(cell) }}
            </div>
            <div class="loc-group">
              {{ cell.locGroup1 }}
            </div>
            <div
              :class="[
                'location-id',
                {
                  'location-id-mismatch':
                    cell.categoryMismatch && !hasAbnormalLocationAttribute(cell),
                  'location-id-abnormal-attribute': hasAbnormalLocationAttribute(cell)
                }
              ]"
            >
              {{ cell.locationId }}
            </div>
            <div
              v-if="cell.categoryMismatch"
              class="location-attribute-tag"
            >
              热度编号异常
            </div>
            <div
              v-if="hasAbnormalLocationAttribute(cell)"
              class="location-attribute-tag"
            >
              {{ getLocationAttributeLabel(cell) }}
            </div>
            <div
              v-if="cell.containerCode"
              class="container-code"
            >
              {{ cell.containerCode }}
            </div>
          </div>
          <div
            v-else-if="isSpecialCell(cell) && cell.label"
            :class="['special-cell-label', `special-cell-label-${cell.type}`]"
          >
            {{ cell.label }}
          </div>
        </div>
      </div>
      <div
        v-if="props.showTooltip && tooltipVisible && tooltipLines.length"
        class="cell-tooltip"
        :style="tooltipStyle"
      >
        <div
          v-for="(line, index) in tooltipLines"
          :key="index"
          class="cell-tooltip-line"
        >
          {{ line }}
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref, watch, type CSSProperties } from 'vue'
import type { LocationResourceDataVO } from '../types'
import { applyWarehouseLayoutEnhancement } from './warehouse-layout-enhancers'
import {
  getWarehouseLayoutSpecialCells,
  type WarehouseLayoutSpecialCell
} from './warehouse-layout-special-cells'

interface Props {
  locations: LocationResourceDataVO[]
  currentLevel: number
  selectedCategory: string
  categoryColorVisibility: Record<string, boolean>
  selectedLocationAttribute: string
  selectedHasContainer: string
  selectedZoneId: string
  locGroupKeyword: string
  locationIdKeyword: string
  showGroupBorder: boolean
  showTooltip: boolean
}

interface ParsedLocation {
  floor: number
  x: number
  y: number
  depth: number
  gridRow: number
  gridCol: number
}

interface GridCell extends LocationResourceDataVO {
  parsed: ParsedLocation
  expectedCategory: string | null
  categoryMismatch: boolean
}

interface AisleCell {
  type: 'aisle'
  gridRow: number
  gridCol: number
}

type MapCell = GridCell | WarehouseLayoutSpecialCell | AisleCell

const props = defineProps<Props>()
const emit = defineEmits<{
  (event: 'select-loc-group', locGroup1: string): void
  (event: 'select-location-id', locationId: string): void
}>()
const mapWrapperRef = ref<HTMLElement | null>(null)
const wrapperSize = ref({
  width: 0,
  height: 0
})
const hoveredCell = ref<GridCell | null>(null)
const tooltipVisible = ref(false)
const tooltipPosition = ref({
  x: 0,
  y: 0
})
const selectionStart = ref<{ row: number; col: number } | null>(null)
const selectionEnd = ref<{ row: number; col: number } | null>(null)
const selectedRange = ref<{
  rowStart: number
  rowEnd: number
  colStart: number
  colEnd: number
} | null>(null)
const didSelectionMove = ref(false)
const suppressNextClick = ref(false)

let resizeObserver: ResizeObserver | null = null

const DEPTH_CATEGORY_MAP: Record<number, string> = {
  1: 'A',
  2: 'B',
  3: 'C'
}

const CATEGORY_THEME_MAP: Record<string, { solid: string; soft: string; text: string }> = {
  A: {
    solid: '#008000',
    soft: 'rgba(0, 128, 0, 0.16)',
    text: '#f5fff7'
  },
  B: {
    solid: '#0000FF',
    soft: 'rgba(0, 0, 255, 0.16)',
    text: '#f4f8ff'
  },
  C: {
    solid: '#FFFF00',
    soft: 'rgba(255, 255, 0, 0.16)',
    text: '#5a5200'
  }
}

const INACTIVE_CATEGORY_THEME = {
  solid: '#5f6b7a',
  soft: 'rgba(95, 107, 122, 0.16)',
  text: '#eef4f8'
}

const LOCATION_ATTRIBUTE_LABEL_MAP: Record<string, string> = {
  OK: '正常',
  FI: '禁入',
  HD: '封存',
  SC: '管控'
}

const normalizeCategory = (category: string) => category.trim().toUpperCase()

const formatFieldValue = (value: string | number | null | undefined) => {
  if (value === null || value === undefined || value === '') {
    return '-'
  }
  return String(value)
}

// 解析库位编码 H1-0-0-2 => {floor: 1, x: 0, y: 0, depth: 2}
const parseLocationId = (locationId: string) => {
  const match = locationId.match(/H(\d+)-(\d+)-(\d+)-(\d+)/)
  if (!match) return null
  return {
    floor: parseInt(match[1], 10),
    x: parseInt(match[2], 10),
    y: parseInt(match[3], 10),
    depth: parseInt(match[4], 10),
    gridRow: 0,
    gridCol: 0
  }
}

const parsedLocations = computed<GridCell[]>(() => {
  return props.locations
    .map((loc) => ({
      ...loc,
      parsed: parseLocationId(loc.locationId)
    }))
    .filter(
      (loc): loc is LocationResourceDataVO & { parsed: ParsedLocation } => loc.parsed !== null
    )
    .map((loc) => {
      const normalizedCategory = normalizeCategory(loc.category)
      const expectedCategory = DEPTH_CATEGORY_MAP[loc.parsed.depth] || null
      const enhancedPosition = applyWarehouseLayoutEnhancement(props.currentLevel, loc.parsed, {
        // 原始布局规则：第一个数字是列，第二个数字是行。
        gridRow: loc.parsed.y,
        gridCol: loc.parsed.x
      })

      return {
        ...loc,
        parsed: {
          ...loc.parsed,
          ...enhancedPosition
        },
        category: normalizedCategory,
        expectedCategory,
        categoryMismatch: expectedCategory === null || normalizedCategory !== expectedCategory
      }
    })
})

const specialCells = computed(() => getWarehouseLayoutSpecialCells(props.currentLevel))

const specialCellMap = computed(() => {
  return new Map(specialCells.value.map((cell) => [`${cell.gridRow}-${cell.gridCol}`, cell]))
})

const locationMap = computed(() => {
  const map = new Map<string, GridCell>()
  parsedLocations.value.forEach((loc) => {
    map.set(`${loc.parsed.gridRow}-${loc.parsed.gridCol}`, loc)
  })
  return map
})

const gridBounds = computed(() => {
  if (parsedLocations.value.length === 0) return null

  const allRows = [
    ...parsedLocations.value.map((loc) => loc.parsed.gridRow),
    ...specialCells.value.map((cell) => cell.gridRow)
  ]
  const allCols = [
    ...parsedLocations.value.map((loc) => loc.parsed.gridCol),
    ...specialCells.value.map((cell) => cell.gridCol)
  ]

  return {
    minX: Math.min(...allRows),
    maxX: Math.max(...allRows),
    minY: Math.min(...allCols),
    maxY: Math.max(...allCols)
  }
})

const gridMetrics = computed(() => {
  if (!gridBounds.value) return null

  return {
    rows: gridBounds.value.maxX - gridBounds.value.minX + 1,
    cols: gridBounds.value.maxY - gridBounds.value.minY + 1
  }
})

// 构建网格数据 - 按 X(行) / Y(列) 摆放，缺口显示为过道
const gridData = computed(() => {
  if (!gridBounds.value) return []

  // 构建网格：按行(X)和列(Y)排列，空位根据规则显示为过道或特殊区域。
  const grid: MapCell[] = []
  for (let x = gridBounds.value.minX; x <= gridBounds.value.maxX; x++) {
    for (let y = gridBounds.value.minY; y <= gridBounds.value.maxY; y++) {
      const key = `${x}-${y}`
      const locationCell = locationMap.value.get(key)
      if (locationCell) {
        grid.push(locationCell)
        continue
      }

      const specialCell = specialCellMap.value.get(key)
      if (specialCell) {
        grid.push(specialCell)
        continue
      }
      grid.push({
        type: 'aisle',
        gridRow: x,
        gridCol: y
      })
    }
  }

  return grid
})

const gridStyle = computed(() => {
  if (!gridMetrics.value) return {}

  const gap = 2
  const horizontalPadding = 24
  const verticalPadding = 12
  const availableWidth = Math.max(wrapperSize.value.width - horizontalPadding, 0)
  const availableHeight = Math.max(wrapperSize.value.height - verticalPadding, 0)

  const cellWidth =
    availableWidth > 0
      ? Math.max((availableWidth - (gridMetrics.value.cols - 1) * gap) / gridMetrics.value.cols, 28)
      : 72
  const cellHeight =
    availableHeight > 0
      ? Math.max(
          (availableHeight - (gridMetrics.value.rows - 1) * gap) / gridMetrics.value.rows,
          24
        )
      : 60
  const compactSize = Math.min(cellWidth, cellHeight)

  const showBadge = compactSize > 38 ? 'inline-flex' : 'none'
  const showGroup = compactSize > 58 ? 'block' : 'none'
  const badgeFontSize = compactSize <= 42 ? 8 : compactSize <= 56 ? 9 : 10
  const textFontSize = compactSize <= 56 ? 9 : 10
  const idFontSize = compactSize <= 34 ? 9 : compactSize <= 42 ? 10 : compactSize <= 56 ? 10 : 11
  const contentGap = compactSize <= 42 ? 2 : 4
  const contentPadding = compactSize <= 42 ? 2 : 4
  const groupBorderWidth = compactSize <= 34 ? 1 : 2

  return {
    gridTemplateColumns: `repeat(${gridMetrics.value.cols}, ${cellWidth.toFixed(2)}px)`,
    gridAutoRows: `${cellHeight.toFixed(2)}px`,
    '--cell-badge-font-size': `${badgeFontSize}px`,
    '--cell-group-font-size': `${textFontSize}px`,
    '--cell-id-font-size': `${idFontSize}px`,
    '--cell-badge-display': showBadge,
    '--cell-group-display': showGroup,
    '--cell-content-gap': `${contentGap}px`,
    '--cell-content-padding': `${contentPadding}px`,
    '--group-outline-width': `${groupBorderWidth}px`
  }
})

const isCellMatched = (cell: GridCell) => {
  const matchedCategory = !props.selectedCategory || cell.category === props.selectedCategory
  const matchedLocationAttribute =
    !props.selectedLocationAttribute || cell.locationAttribute === props.selectedLocationAttribute
  const hasContainer = Boolean(cell.containerCode && cell.containerCode.trim())
  const matchedHasContainer =
    !props.selectedHasContainer ||
    (props.selectedHasContainer === 'Y' && hasContainer) ||
    (props.selectedHasContainer === 'N' && !hasContainer)
  const matchedZoneId = !props.selectedZoneId || String(cell.zoneId || '') === props.selectedZoneId
  const normalizedKeyword = props.locGroupKeyword.trim().toUpperCase()
  const matchedLocGroup =
    !normalizedKeyword || cell.locGroup1.toUpperCase().includes(normalizedKeyword)
  const normalizedLocationIdKeyword = props.locationIdKeyword.trim().toUpperCase()
  const matchedLocationId =
    !normalizedLocationIdKeyword ||
    cell.locationId.toUpperCase().includes(normalizedLocationIdKeyword)

  return (
    matchedCategory &&
    matchedLocationAttribute &&
    matchedHasContainer &&
    matchedZoneId &&
    matchedLocGroup &&
    matchedLocationId
  )
}

const getGridPointByIndex = (index: number) => {
  if (!gridBounds.value || !gridMetrics.value) {
    return null
  }

  const rowOffset = Math.floor(index / gridMetrics.value.cols)
  const colOffset = index % gridMetrics.value.cols

  return {
    row: gridBounds.value.minX + rowOffset,
    col: gridBounds.value.minY + colOffset
  }
}

const buildSelectionRange = (
  start: { row: number; col: number },
  end: { row: number; col: number }
) => ({
  rowStart: Math.min(start.row, end.row),
  rowEnd: Math.max(start.row, end.row),
  colStart: Math.min(start.col, end.col),
  colEnd: Math.max(start.col, end.col)
})

const isCellInSelectedRange = (cell: GridCell) => {
  if (!selectedRange.value) {
    return true
  }

  return (
    cell.parsed.gridRow >= selectedRange.value.rowStart &&
    cell.parsed.gridRow <= selectedRange.value.rowEnd &&
    cell.parsed.gridCol >= selectedRange.value.colStart &&
    cell.parsed.gridCol <= selectedRange.value.colEnd
  )
}

const isCellVisible = (cell: GridCell) => {
  return isCellMatched(cell) && isCellInSelectedRange(cell)
}

const isSpecialCell = (cell: MapCell): cell is WarehouseLayoutSpecialCell => {
  return Boolean(cell && 'type' in cell && cell.type !== 'aisle')
}

const isLocationCell = (cell: MapCell): cell is GridCell => {
  return Boolean(cell && !('type' in cell))
}

const getCellClass = (cell: MapCell) => {
  if (isSpecialCell(cell)) {
    return [cell.type]
  }

  if (!isLocationCell(cell)) {
    return ['aisle']
  }

  if (!isCellVisible(cell)) {
    return ['aisle']
  }

  const classNames = ['location-cell']
  if (props.categoryColorVisibility[cell.category] !== false) {
    classNames.push(`category-${cell.category.toLowerCase()}`)
  } else {
    classNames.push('category-muted')
  }
  if (props.showGroupBorder && hasSameBorderNeighbor(cell)) {
    classNames.push('grouped')
  }
  return classNames
}

const getHeatLabel = (cell: GridCell) => {
  return cell.category
}

const hasAbnormalLocationAttribute = (cell: GridCell) => {
  return Boolean(cell.locationAttribute && cell.locationAttribute !== 'OK')
}

const getLocationAttributeLabel = (cell: GridCell) => {
  if (!cell.locationAttribute) {
    return '-'
  }
  return LOCATION_ATTRIBUTE_LABEL_MAP[cell.locationAttribute] || cell.locationAttribute
}

const getCellTitle = (cell: GridCell | null) => {
  if (!cell) return '过道'
  if (!isCellMatched(cell)) return '未命中筛选条件'
  if (!isCellInSelectedRange(cell)) return '未命中当前选区'
  const mismatchText =
    cell.categoryMismatch && cell.expectedCategory
      ? `\n异常: 深度${cell.parsed.depth} 期望热度${cell.expectedCategory}`
      : cell.categoryMismatch
        ? `\n异常: 深度${cell.parsed.depth} 未配置对应热度`
        : ''
  const locationAttributeLabel = cell.locationAttribute
    ? LOCATION_ATTRIBUTE_LABEL_MAP[cell.locationAttribute] || cell.locationAttribute
    : '-'
  return [
    `库位号: ${formatFieldValue(cell.locationId)}`,
    `库位组: ${formatFieldValue(cell.locGroup1)}`,
    `WCS库位: ${formatFieldValue(cell.wcsLocationId)}`,
    `资源类别: ${formatFieldValue(cell.category)}`,
    `容器编码: ${formatFieldValue(cell.containerCode)}`,
    `库位楼层: ${formatFieldValue(cell.locLevel)}`,
    `库区: ${formatFieldValue(cell.zoneId)}`,
    `库位属性: ${locationAttributeLabel}${cell.locationAttribute ? `(${cell.locationAttribute})` : ''}`,
    `上架排序: ${formatFieldValue(cell.putawayLogicalSequence)}`,
    `X坐标: ${cell.parsed.x}`,
    `Y坐标: ${cell.parsed.y}`,
    `深度: ${cell.parsed.depth}${mismatchText}`
  ].join('\n')
}

const getCategoryStyle = (cell: GridCell) => {
  const theme =
    props.categoryColorVisibility[cell.category] === false
      ? INACTIVE_CATEGORY_THEME
      : CATEGORY_THEME_MAP[cell.category]
  return {
    background: theme?.solid || '#5f6b7a',
    color: theme?.text || '#fff'
  }
}

const tooltipLines = computed(() => {
  return getCellTitle(hoveredCell.value)
    .split('\n')
    .map((line) => line.trim())
    .filter(Boolean)
})

const tooltipStyle = computed<CSSProperties>(() => ({
  left: `${tooltipPosition.value.x}px`,
  top: `${tooltipPosition.value.y}px`
}))

const getBorderGroupValue = (cell: GridCell) => {
  if (props.showGroupBorder) {
    return cell.locGroup1
  }
  return ''
}

const isSameBorderNeighbor = (cell: GridCell, xOffset: number, yOffset: number) => {
  const currentGroupValue = getBorderGroupValue(cell)
  if (!currentGroupValue) {
    return false
  }

  const neighbor = locationMap.value.get(
    `${cell.parsed.gridRow + xOffset}-${cell.parsed.gridCol + yOffset}`
  )
  return Boolean(neighbor && getBorderGroupValue(neighbor) === currentGroupValue)
}

const hasSameBorderNeighbor = (cell: GridCell) => {
  return (
    isSameBorderNeighbor(cell, -1, 0) ||
    isSameBorderNeighbor(cell, 1, 0) ||
    isSameBorderNeighbor(cell, 0, -1) ||
    isSameBorderNeighbor(cell, 0, 1)
  )
}

const getCellStyle = (cell: MapCell): CSSProperties => {
  if (
    !isLocationCell(cell) ||
    !isCellVisible(cell) ||
    !props.showGroupBorder ||
    !hasSameBorderNeighbor(cell)
  ) {
    return {}
  }

  const borderWidth = 'var(--group-outline-width, 2px)'
  return {
    '--group-border-color': '#ffffff',
    '--group-border-top': isSameBorderNeighbor(cell, -1, 0) ? '0px' : borderWidth,
    '--group-border-right': isSameBorderNeighbor(cell, 0, 1) ? '0px' : borderWidth,
    '--group-border-bottom': isSameBorderNeighbor(cell, 1, 0) ? '0px' : borderWidth,
    '--group-border-left': isSameBorderNeighbor(cell, 0, -1) ? '0px' : borderWidth
  }
}

const clearSelectedRange = () => {
  selectedRange.value = null
}

const handleCellClick = (cell: MapCell) => {
  if (suppressNextClick.value) {
    suppressNextClick.value = false
    return
  }
  if (!isLocationCell(cell) || !isCellVisible(cell)) {
    if (selectedRange.value) {
      clearSelectedRange()
    }
    return
  }
  emit('select-loc-group', cell.locGroup1)
}

const handleCellContextMenu = (cell: MapCell) => {
  if (!isLocationCell(cell) || !isCellVisible(cell)) return
  emit('select-location-id', cell.locationId)
}

const copyText = async (text: string) => {
  if (!text) {
    return
  }

  try {
    await navigator.clipboard.writeText(text)
    return
  } catch (error) {
    console.warn('Clipboard API copy failed, fallback to execCommand.', error)
  }

  const textarea = document.createElement('textarea')
  textarea.value = text
  textarea.setAttribute('readonly', 'true')
  textarea.style.position = 'fixed'
  textarea.style.top = '-9999px'
  document.body.appendChild(textarea)
  textarea.select()
  document.execCommand('copy')
  document.body.removeChild(textarea)
}

const handleCellMouseDown = (index: number, event: MouseEvent) => {
  if (event.button !== 0) {
    return
  }

  const point = getGridPointByIndex(index)
  if (!point) {
    return
  }

  selectionStart.value = point
  selectionEnd.value = point
  didSelectionMove.value = false
  tooltipVisible.value = false
  hoveredCell.value = null
}

const updateTooltipPosition = (event: MouseEvent) => {
  const tooltipWidth = 300
  const tooltipHeight = 260
  const offset = 14
  const maxX = window.innerWidth - tooltipWidth - 12
  const maxY = window.innerHeight - tooltipHeight - 12

  tooltipPosition.value = {
    x: Math.max(12, Math.min(event.clientX + offset, maxX)),
    y: Math.max(12, Math.min(event.clientY + offset, maxY))
  }
}

const handleCellMouseEnter = (cell: MapCell, index: number, event: MouseEvent) => {
  if (selectionStart.value && (event.buttons & 1) === 1) {
    const point = getGridPointByIndex(index)
    if (point) {
      selectionEnd.value = point
      didSelectionMove.value =
        point.row !== selectionStart.value.row || point.col !== selectionStart.value.col
    }
    tooltipVisible.value = false
    hoveredCell.value = null
    return
  }

  if (!props.showTooltip || !isLocationCell(cell)) {
    tooltipVisible.value = false
    hoveredCell.value = null
    return
  }
  hoveredCell.value = cell
  tooltipVisible.value = true
  updateTooltipPosition(event)
}

const handleCellMouseMove = (event: MouseEvent) => {
  if (!props.showTooltip || !tooltipVisible.value) return
  updateTooltipPosition(event)
}

const handleCellMouseLeave = () => {
  if (selectionStart.value) {
    return
  }
  tooltipVisible.value = false
  hoveredCell.value = null
}

const handleWindowMouseUp = () => {
  if (!selectionStart.value || !selectionEnd.value) {
    return
  }

  if (didSelectionMove.value) {
    selectedRange.value = buildSelectionRange(selectionStart.value, selectionEnd.value)
    const selectedLocationIds = gridData.value
      .filter(isLocationCell)
      .filter((cell) => isCellMatched(cell) && isCellInSelectedRange(cell))
      .map((cell) => cell.locationId)

    void copyText(selectedLocationIds.join('\n'))
    suppressNextClick.value = true
  }

  selectionStart.value = null
  selectionEnd.value = null
  didSelectionMove.value = false
}

const updateWrapperSize = () => {
  if (!mapWrapperRef.value) return
  wrapperSize.value = {
    width: mapWrapperRef.value.clientWidth,
    height: mapWrapperRef.value.clientHeight
  }
}

onMounted(() => {
  updateWrapperSize()
  window.addEventListener('mouseup', handleWindowMouseUp)
  if (!mapWrapperRef.value) return

  resizeObserver = new ResizeObserver(() => {
    updateWrapperSize()
  })
  resizeObserver.observe(mapWrapperRef.value)
})

watch(
  () => props.showTooltip,
  (enabled) => {
    if (!enabled) {
      tooltipVisible.value = false
      hoveredCell.value = null
    }
  }
)

onBeforeUnmount(() => {
  window.removeEventListener('mouseup', handleWindowMouseUp)
  resizeObserver?.disconnect()
})
</script>

<style scoped>
.warehouse-map {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  position: relative;
}

.no-data {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
  color: #8b8b8b;
  font-size: 16px;
}

.map-wrapper {
  flex: 1;
  position: relative;
  overflow: auto;
  background: #000000;
}

.map-grid {
  display: grid;
  gap: 2px;
  padding: 0 12px 12px;
  box-sizing: border-box;
  width: 100%;
  height: 100%;
  align-content: start;
}

.grid-cell {
  border: none;
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.2s;
  cursor: pointer;
  position: relative;
}

.grid-cell.grouped::after {
  content: '';
  position: absolute;
  inset: 0;
  border-style: solid;
  border-color: var(--group-border-color, #ffffff);
  border-top-width: var(--group-border-top, 0px);
  border-right-width: var(--group-border-right, 0px);
  border-bottom-width: var(--group-border-bottom, 0px);
  border-left-width: var(--group-border-left, 0px);
  border-radius: 4px;
  pointer-events: none;
  opacity: 0.95;
}

.grid-cell.aisle {
  background: #050505;
}

.grid-cell.wall {
  background: #151515;
  cursor: default;
}

.grid-cell.elevator {
  background: #101010;
  cursor: default;
}

.grid-cell.category-a {
  background: rgba(0, 128, 0, 0.16);
}

.grid-cell.category-b {
  background: rgba(0, 0, 255, 0.16);
}

.grid-cell.category-c {
  background: rgba(255, 255, 0, 0.16);
}

.grid-cell.category-muted {
  background: rgba(95, 107, 122, 0.16);
}

.grid-cell:not(.aisle):not(.wall):not(.elevator):hover {
  transform: scale(1.03);
  box-shadow: 0 0 12px rgba(255, 255, 255, 0.08);
  z-index: 10;
}

.cell-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--cell-content-gap, 4px);
  padding: var(--cell-content-padding, 4px);
  width: 100%;
  height: 100%;
  overflow: hidden;
}

.category-badge {
  display: var(--cell-badge-display, inline-flex);
  align-items: center;
  justify-content: center;
  padding: 2px 8px;
  border-radius: 10px;
  font-size: var(--cell-badge-font-size, 10px);
  font-weight: bold;
  line-height: 1;
  white-space: nowrap;
}

.loc-group {
  display: var(--cell-group-display, -webkit-box);
  font-size: var(--cell-group-font-size, 10px);
  color: #a0a0a0;
  text-align: center;
  line-height: 1.2;
  word-break: break-all;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}

.location-id {
  display: block;
  font-size: var(--cell-id-font-size, 11px);
  font-weight: bold;
  color: #f2f2f2;
  text-align: center;
  line-height: 1.1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.location-id.location-id-mismatch {
  color: #ff4d4f;
}

.location-id.location-id-abnormal-attribute {
  color: #ff4d4f;
}

.location-attribute-tag {
  max-width: 100%;
  font-size: calc(var(--cell-id-font-size, 11px) - 2px);
  color: #ff4d4f;
  text-align: center;
  line-height: 1.1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.container-code {
  max-width: 100%;
  font-size: calc(var(--cell-id-font-size, 11px) - 2px);
  color: #9a9a9a;
  text-align: center;
  line-height: 1.1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.special-cell-label {
  font-size: calc(var(--cell-id-font-size, 11px) - 1px);
  font-weight: 700;
  color: rgba(255, 255, 255, 0.76);
  letter-spacing: 1px;
  user-select: none;
}

.special-cell-label-elevator {
  color: rgba(255, 255, 255, 0.86);
}

.cell-tooltip {
  position: fixed;
  z-index: 1000;
  max-width: 300px;
  padding: 10px 12px;
  border: 1px solid #1f1f1f;
  border-radius: 8px;
  background: rgba(0, 0, 0, 0.96);
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.32);
  color: #f2f2f2;
  font-size: 12px;
  line-height: 1.45;
  pointer-events: none;
  backdrop-filter: blur(6px);
}

.cell-tooltip-line + .cell-tooltip-line {
  margin-top: 3px;
}
</style>
