// frontend/src/pages/bellcloud/devices/tabs/GeneralTab.jsx
// General Information tab — 3-row bento layout

import { useState, useEffect, useMemo, useCallback, useRef } from 'react'
import api from '@/lib/api'
import { useToast } from '@/components/ui/Toast'
import Button from '@/components/ui/Button'
import StatusBadge from '@/components/ui/StatusBadge'
import Icon from '@/components/ui/Icon'
import FormField from '@/components/ui/FormField'
import Spinner from '@/components/ui/Spinner'
import { fmtDateTimeMedium } from '@/lib/formatters'

// ─── Constants ────────────────────────────────────────────────────────────────

const LOG_LEVELS = [0, 1, 2, 3, 4, 5]

const LOG_LEVEL_META = {
  0: { label: 'Disabled', color: 'var(--color-text-muted)'   },
  1: { label: 'Error',    color: 'var(--color-danger)'       },
  2: { label: 'Warning',  color: 'var(--color-warning)'      },
  3: { label: 'Info',     color: 'var(--color-info)'         },
  4: { label: 'Debug',    color: '#9f7aea'                   },
  5: { label: 'Verbose',  color: 'var(--color-success)'      },
}

// Glass bg tokens
const GLASS_BG       = 'rgba(28, 32, 38, 0.30)'
const GLASS_BG_INNER = 'rgba(28, 32, 38, 0.40)'

// ─── Helpers ──────────────────────────────────────────────────────────────────

function parseCoordinates(coordStr) {
  if (!coordStr) return null
  if (typeof coordStr === 'object' && coordStr !== null) {
    const lat = parseFloat(coordStr.lat ?? coordStr.latitude)
    const lng = parseFloat(coordStr.lng ?? coordStr.longitude)
    if (!isNaN(lat) && !isNaN(lng)) return { lat, lng }
    return null
  }
  const numbers = coordStr.match(/-?\d+(?:\.\d+)?/g)
  if (numbers && numbers.length >= 2) {
    let lat = parseFloat(numbers[0])
    let lng = parseFloat(numbers[1])
    if (!isNaN(lat) && !isNaN(lng)) {
      if (/\bS\b/.test(coordStr)) lat = -Math.abs(lat)
      if (/\bW\b/.test(coordStr)) lng = -Math.abs(lng)
      return { lat, lng }
    }
  }
  return null
}

function formatCoordinates(coords) {
  if (!coords) return '—'
  const latDir = coords.lat >= 0 ? 'N' : 'S'
  const lngDir = coords.lng >= 0 ? 'E' : 'W'
  return `${Math.abs(coords.lat).toFixed(6)}° ${latDir}, ${Math.abs(coords.lng).toFixed(6)}° ${lngDir}`
}

function getNearestPlaceLabel(data) {
  const addr = data?.address || {}
  const name = addr.city || addr.town || addr.village || addr.hamlet || addr.municipality || data?.display_name?.split(',')[0] || ''
  const region = addr.state || addr.county || ''
  const country = addr.country || ''
  return [name, region, country].filter(Boolean).join(', ')
}

function getLogLevelMeta(levelRaw) {
  if (!Number.isFinite(levelRaw)) return null
  return LOG_LEVEL_META[Number(levelRaw)] || null
}

// ─── OSM Tile helpers ─────────────────────────────────────────────────────────

function latLngToTile(lat, lng, zoom) {
  const n = Math.pow(2, zoom)
  const xFrac = ((lng + 180) / 360) * n
  const latRad = (lat * Math.PI) / 180
  const yFrac = ((1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2) * n
  const x = Math.floor(xFrac)
  const y = Math.floor(yFrac)
  return { x, y, px: (xFrac - x) * 256, py: (yFrac - y) * 256 }
}

function buildTileGrid(lat, lng, zoom = 13, options = {}) {
  const { x, y, px, py } = latLngToTile(lat, lng, zoom)
  const COL_RADIUS = options.colRadius ?? 3
  const ROW_RADIUS = options.rowRadius ?? 1
  const tiles = []
  for (let dy = -ROW_RADIUS; dy <= ROW_RADIUS; dy++) {
    for (let dx = -COL_RADIUS; dx <= COL_RADIUS; dx++) {
      tiles.push({
        url: `https://tiles.stadiamaps.com/tiles/stamen_toner/${zoom}/${x + dx}/${y + dy}.png`,
        col: dx + COL_RADIUS,
        row: dy + ROW_RADIUS,
      })
    }
  }
  return {
    tiles,
    markerOffset: {
      x: COL_RADIUS * 256 + px,
      y: ROW_RADIUS * 256 + py,
    },
  }
}

function getResponsiveTileCounts(width, height) {
  const safeWidth = Number.isFinite(width) && width > 0 ? width : 0
  const safeHeight = Number.isFinite(height) && height > 0 ? height : 0

  let totalCols = Math.max(7, Math.ceil(safeWidth / 256) + 2)
  let totalRows = Math.max(3, Math.ceil(safeHeight / 256) + 2)

  if (totalCols % 2 === 0) totalCols += 1
  if (totalRows % 2 === 0) totalRows += 1

  return {
    totalCols,
    totalRows,
    colRadius: Math.floor(totalCols / 2),
    rowRadius: Math.floor(totalRows / 2),
  }
}

// ─── Keyframes ────────────────────────────────────────────────────────────────

let keyframesInjected = false
function ensureKeyframes() {
  if (keyframesInjected) return
  keyframesInjected = true
  const style = document.createElement('style')
  style.textContent = `
    @keyframes gen-tab-pulse {
      0%   { opacity: 0.9; transform: scale(1); }
      70%  { opacity: 0;   transform: scale(2.4); }
      100% { opacity: 0;   transform: scale(2.4); }
    }
    .gen-log-slider {
      -webkit-appearance: none;
      appearance: none;
      width: 100%;
      height: 4px;
      border-radius: 2px;
      outline: none;
      cursor: pointer;
      background: rgba(28,32,38,0.70);
    }
    .gen-log-slider::-webkit-slider-thumb {
      -webkit-appearance: none;
      appearance: none;
      width: 14px;
      height: 14px;
      border-radius: 50%;
      border: 2px solid rgba(192,193,255,0.25);
      cursor: pointer;
      transition: transform 0.15s, box-shadow 0.15s;
    }
    .gen-log-slider::-moz-range-thumb {
      width: 14px;
      height: 14px;
      border-radius: 50%;
      border: 2px solid rgba(192,193,255,0.25);
      cursor: pointer;
    }
    .gen-log-slider:hover::-webkit-slider-thumb {
      transform: scale(1.2);
    }
  `
  document.head.appendChild(style)
}

// ─── Primitive design atoms ───────────────────────────────────────────────────

function SectionLabel({ children }) {
  return (
    <span style={{
      fontSize: 'var(--font-size-xs)',
      fontWeight: 'var(--font-weight-semibold)',
      textTransform: 'uppercase',
      letterSpacing: 'var(--tracking-wide)',
      color: 'var(--color-text-muted)',
    }}>
      {children}
    </span>
  )
}

// Glass card — the universal panel wrapper
function GlassCard({ title, titleAction, children, noPadding, style, fullHeight }) {
  return (
    <div style={{
      background: GLASS_BG,
      backdropFilter: 'var(--blur-modal)',
      WebkitBackdropFilter: 'var(--blur-modal)',
      border: '1px solid var(--color-border)',
      borderRadius: 'var(--radius-xl)',
      boxShadow: 'var(--shadow-card), var(--shadow-md)',
      overflow: 'hidden',
      display: 'flex',
      flexDirection: 'column',
      height: fullHeight ? '100%' : undefined,
      ...style,
    }}>
      {title && (
        <div style={{
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'space-between',
          gap: 'var(--space-3)',
          padding: 'var(--space-4) var(--space-5)',
          borderBottom: '1px solid var(--color-border)',
          background: 'linear-gradient(180deg, rgba(192,193,255,0.04) 0%, transparent 100%)',
          flexShrink: 0,
        }}>
          <span style={{
            fontSize: 'var(--font-size-base)',
            fontWeight: 'var(--font-weight-semibold)',
            color: 'var(--color-text-primary)',
            minWidth: 0,
          }}>
            {title}
          </span>
          {titleAction && <div style={{ flexShrink: 0, display: 'flex', alignItems: 'center' }}>{titleAction}</div>}
        </div>
      )}
      <div style={noPadding ? { flex: 1, display: 'flex', flexDirection: 'column' } : { padding: 'var(--space-5)', flex: 1, display: 'flex', flexDirection: 'column' }}>
        {children}
      </div>
    </div>
  )
}

// Inner surface — glass nested surface
function InnerSurface({ children, style }) {
  return (
    <div style={{
      background: GLASS_BG_INNER,
      backdropFilter: 'var(--blur-modal)',
      WebkitBackdropFilter: 'var(--blur-modal)',
      border: '1px solid var(--color-border)',
      borderRadius: 'var(--radius-lg)',
      ...style,
    }}>
      {children}
    </div>
  )
}

function EditActionButton({ onClick, label = 'Edit' }) {
  return (
    <Button
      variant="ghost"
      size="sm"
      onClick={onClick}
      icon={<Icon name="edit" size={13} />}
      style={{ whiteSpace: 'nowrap', flexShrink: 0 }}
    >
      {label}
    </Button>
  )
}

function GoogleMapsIcon({ size = 14 }) {
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" aria-hidden="true">
      <path fill="#34A853" d="M6 3.5 11 5.2v15.3L6 18.8z" />
      <path fill="#FBBC04" d="m11 5.2 7-2.2v15.3l-7 2.2z" />
      <path fill="#4285F4" d="m18 3 4 1.4v15.3L18 18.3z" />
      <path fill="#EA4335" d="M15.2 10.2c0 2.4-3.2 6.1-3.2 6.1s-3.2-3.7-3.2-6.1a3.2 3.2 0 1 1 6.4 0Z" />
      <circle cx="12" cy="10.2" r="1.4" fill="#fff" />
    </svg>
  )
}

// ─── Safety Row ───────────────────────────────────────────────────────────────

function SafetyRow({ label, description, enabled }) {
  return (
    <InnerSurface style={{
      display: 'flex',
      alignItems: 'center',
      justifyContent: 'space-between',
      padding: 'var(--space-3) var(--space-4)',
    }}>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
        <span style={{
          fontSize: 'var(--font-size-base)',
          fontWeight: 'var(--font-weight-medium)',
          color: 'var(--color-text-primary)',
        }}>
          {label}
        </span>
        <span style={{
          fontSize: 'var(--font-size-xs)',
          textTransform: 'uppercase',
          letterSpacing: 'var(--tracking-wide)',
          color: 'var(--color-text-muted)',
        }}>
          {description}
        </span>
      </div>
      <StatusBadge variant={enabled ? 'success' : 'neutral'}>
        {enabled ? 'Armed' : 'Disarmed'}
      </StatusBadge>
    </InnerSurface>
  )
}

// ─── Log Slider ───────────────────────────────────────────────────────────────

function LogSlider({ label, level, onChange, canEdit, color }) {
  const meta = getLogLevelMeta(level)
  const safeLevel = Number.isFinite(level) ? Number(level) : 0

  // Build CSS gradient for the track — filled up to thumb position
  const pct = (safeLevel / (LOG_LEVELS.length - 1)) * 100
  const trackBg = `linear-gradient(90deg, ${color} 0%, ${color} ${pct}%, rgba(28,32,38,0.70) ${pct}%, rgba(28,32,38,0.70) 100%)`

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-2)' }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
        <SectionLabel>{label}</SectionLabel>
        <span style={{
          fontSize: 'var(--font-size-xs)',
          fontWeight: 'var(--font-weight-semibold)',
          fontFamily: 'var(--font-family-mono)',
          color: meta ? meta.color : 'var(--color-text-muted)',
        }}>
          {meta ? meta.label : '—'}
        </span>
      </div>
      <input
        type="range"
        className="gen-log-slider"
        min={0}
        max={5}
        step={1}
        value={safeLevel}
        disabled={!canEdit}
        onChange={canEdit ? e => onChange(Number(e.target.value)) : undefined}
        style={{
          background: trackBg,
          opacity: canEdit ? 1 : 0.6,
          // thumb color via CSS custom property on inline style — works in Safari
          '--thumb-color': color,
        }}
      />
      {/* tick marks — padding-inline of 7px (half thumb width) aligns ticks with thumb centre */}
      <div style={{ display: 'flex', justifyContent: 'space-between', marginTop: -2, paddingInline: '7px' }}>
        {LOG_LEVELS.map(l => (
          <div key={l} style={{
            width: 1,
            height: 4,
            background: l === safeLevel ? color : 'rgba(192,193,255,0.12)',
            borderRadius: 1,
            transition: 'background 0.2s',
          }} />
        ))}
      </div>
    </div>
  )
}

// ─── Location Map ─────────────────────────────────────────────────────────────

function LocationMap({ coords, locationName, locationLabel, canEdit, onEdit }) {
  const ZOOM = 13
  const PIN_X = 72 // % across
  const PIN_Y = 50 // % down
  const containerRef = useRef(null)
  const [mapSize, setMapSize] = useState({ width: 0, height: 0 })

  useEffect(() => {
    const node = containerRef.current
    if (!node) return

    const updateSize = () => {
      const { width, height } = node.getBoundingClientRect()
      setMapSize(prev => (
        prev.width === width && prev.height === height
          ? prev
          : { width, height }
      ))
    }

    updateSize()

    if (typeof ResizeObserver === 'undefined') return

    const observer = new ResizeObserver(updateSize)
    observer.observe(node)
    return () => observer.disconnect()
  }, [])

  const { totalCols, totalRows, colRadius, rowRadius } = useMemo(
    () => getResponsiveTileCounts(mapSize.width, mapSize.height),
    [mapSize.width, mapSize.height]
  )

  const { tiles, markerOffset } = useMemo(() => {
    if (!coords) return { tiles: null, markerOffset: null }
    return buildTileGrid(coords.lat, coords.lng, ZOOM, { colRadius, rowRadius })
  }, [coords, colRadius, rowRadius])

  const googleMapsUrl = coords
    ? `https://www.google.com/maps?q=${coords.lat},${coords.lng}`
    : null

  const handleOpenGoogleMaps = useCallback(() => {
    if (!googleMapsUrl || typeof window === 'undefined') return
    window.open(googleMapsUrl, '_blank', 'noopener,noreferrer')
  }, [googleMapsUrl])

  return (
    <div style={{
      position: 'relative',
      width: '100%',
      height: '100%',
      minHeight: 0,
      background: 'rgba(10,14,20,0.90)',
      overflow: 'hidden',
    }} ref={containerRef}>
      {/* Tiles */}
      {tiles ? (
        <div
          aria-hidden="true"
          style={{
            position: 'absolute',
            display: 'grid',
            gridTemplateColumns: `repeat(${totalCols}, 256px)`,
            gridTemplateRows: `repeat(${totalRows}, 256px)`,
            top: `calc(${PIN_Y}% - ${markerOffset.y}px)`,
            left: `calc(${PIN_X}% - ${markerOffset.x}px)`,
            pointerEvents: 'none',
            userSelect: 'none',
          }}
        >
          {tiles.map((t, i) => (
            <img
              key={i}
              src={t.url}
              alt=""
              draggable={false}
              style={{
                width: 256,
                height: 256,
                display: 'block',
                gridColumn: t.col + 1,
                gridRow: t.row + 1,
                filter: 'invert(1) brightness(0.32) contrast(1.1)',
              }}
            />
          ))}
        </div>
      ) : (
        <div
          aria-hidden="true"
          style={{
            position: 'absolute',
            inset: 0,
            backgroundImage: 'radial-gradient(circle, rgba(192,193,255,0.04) 1px, transparent 1px)',
            backgroundSize: '28px 28px',
          }}
        />
      )}

      {/* Marker */}
      {tiles && markerOffset && (
        <div
          aria-hidden="true"
          style={{
            position: 'absolute',
            top: `${PIN_Y}%`,
            left: `${PIN_X}%`,
            zIndex: 10,
            pointerEvents: 'none',
          }}
        >
          <div style={{
            position: 'absolute',
            width: 10, height: 10, borderRadius: '50%',
            background: 'rgba(250,204,21,1)',
            boxShadow: '0 0 0 2.5px rgba(8,12,18,0.95), 0 0 14px rgba(250,204,21,0.85)',
            top: -5, left: -5,
          }} />
          <div style={{
            position: 'absolute',
            width: 26, height: 26, borderRadius: '50%',
            border: '1.5px solid rgba(250,204,21,0.55)',
            top: -13, left: -13,
            animation: 'gen-tab-pulse 2.2s ease-out infinite',
          }} />
        </div>
      )}

      {/* Left-fade overlay */}
      <div
        aria-hidden="true"
        style={{
          position: 'absolute', inset: 0,
          background: 'linear-gradient(90deg, rgba(10,14,20,0.72) 0%, rgba(10,14,20,0.48) 22%, rgba(10,14,20,0.18) 45%, rgba(10,14,20,0.04) 70%, rgba(10,14,20,0.0) 100%)',
          pointerEvents: 'none',
        }}
      />

      {/* Top-left info stack */}
      <div style={{
        position: 'absolute',
        top: 'var(--space-5)',
        left: 'var(--space-5)',
        display: 'flex',
        flexDirection: 'column',
        gap: 'var(--space-3)',
        zIndex: 20,
        maxWidth: '42%',
      }}>
        <div style={{
          background: 'rgba(28,32,38,0.60)',
          backdropFilter: 'var(--blur-modal)',
          WebkitBackdropFilter: 'var(--blur-modal)',
          border: '1px solid var(--color-border)',
          borderRadius: 'var(--radius-lg)',
          padding: 'var(--space-3) var(--space-4)',
        }}>
          <div style={{ fontSize: 'var(--font-size-xs)', textTransform: 'uppercase', letterSpacing: 'var(--tracking-wide)', color: 'var(--color-text-muted)', marginBottom: 4 }}>
            Location
          </div>
          <div style={{ fontSize: 'var(--font-size-base)', fontWeight: 'var(--font-weight-semibold)', color: 'var(--color-text-primary)' }}>
            {locationLabel || '—'}
          </div>
        </div>

        {locationName && (
          <div style={{
            background: 'rgba(28,32,38,0.60)',
            backdropFilter: 'var(--blur-modal)',
            WebkitBackdropFilter: 'var(--blur-modal)',
            border: '1px solid var(--color-border)',
            borderRadius: 'var(--radius-lg)',
            padding: 'var(--space-3) var(--space-4)',
          }}>
            <div style={{ fontSize: 'var(--font-size-xs)', textTransform: 'uppercase', letterSpacing: 'var(--tracking-wide)', color: 'var(--color-text-muted)', marginBottom: 4 }}>
              Nearest Place
            </div>
            <div style={{ fontSize: 'var(--font-size-sm)', fontWeight: 'var(--font-weight-medium)', color: 'var(--color-text-secondary)' }}>
              {locationName}
            </div>
          </div>
        )}
      </div>

      {/* Coordinates — bottom-left */}
      {coords && (
        <div style={{ position: 'absolute', bottom: 'var(--space-5)', left: 'var(--space-5)', zIndex: 20 }}>
          <button
            type="button"
            onClick={handleOpenGoogleMaps}
            title="Open in Google Maps"
            aria-label="Open coordinates in Google Maps"
            style={{
              display: 'inline-flex',
              alignItems: 'center',
              gap: 'var(--space-2)',
              background: 'rgba(28,32,38,0.80)',
              backdropFilter: 'var(--blur-modal)',
              WebkitBackdropFilter: 'var(--blur-modal)',
              border: '1px solid var(--color-border)',
              borderRadius: 'var(--radius-md)',
              padding: '5px var(--space-3)',
              fontSize: 'var(--font-size-xs)',
              fontFamily: 'var(--font-family-mono)',
              color: 'var(--color-text-muted)',
              letterSpacing: '0.03em',
              cursor: 'pointer',
            }}
          >
            <GoogleMapsIcon size={14} />
            {formatCoordinates(coords)}
          </button>
        </div>
      )}

      {/* Edit button — top-right */}
      {canEdit && (
        <div style={{ position: 'absolute', top: 'var(--space-4)', right: 'var(--space-4)', zIndex: 20 }}>
          <EditActionButton onClick={onEdit} />
        </div>
      )}
    </div>
  )
}

// ─── Device Notes ─────────────────────────────────────────────────────────────

function DeviceNotesSection({ deviceId, canEdit }) {
  const { toast } = useToast()
  const [notes, setNotes] = useState([])
  const [loaded, setLoaded] = useState(false)
  const [adding, setAdding] = useState(false)
  const [newText, setNewText] = useState('')
  const [savingNew, setSavingNew] = useState(false)
  const [editingId, setEditingId] = useState(null)
  const [editingText, setEditingText] = useState('')
  const [savingEdit, setSavingEdit] = useState(false)
  const [deletingId, setDeletingId] = useState(null)

  useEffect(() => {
    if (!deviceId) return
    api.get(`/devices/${deviceId}/notes`)
      .then(data => { setNotes(data.notes || []); setLoaded(true) })
      .catch(() => setLoaded(true))
  }, [deviceId])

  const handleAdd = async () => {
    const content = newText.trim()
    if (!content) return
    setSavingNew(true)
    try {
      const data = await api.post(`/devices/${deviceId}/notes`, { content })
      setNotes(prev => [data.note || { id: Date.now(), content, created_at: new Date().toISOString() }, ...prev])
      setNewText('')
      setAdding(false)
    } catch (err) {
      toast.danger('Error', err.message || 'Failed to add note.')
    } finally {
      setSavingNew(false)
    }
  }

  const handleUpdate = async (noteId) => {
    const content = editingText.trim()
    if (!content) return
    setSavingEdit(true)
    try {
      const data = await api.put(`/devices/${deviceId}/notes/${noteId}`, { content })
      setNotes(prev => prev.map(n => n.id === noteId ? (data.note || { ...n, content }) : n))
      setEditingId(null)
    } catch (err) {
      toast.danger('Error', err.message || 'Failed to update note.')
    } finally {
      setSavingEdit(false)
    }
  }

  const handleDelete = async (noteId) => {
    setDeletingId(noteId)
    try {
      await api.delete(`/devices/${deviceId}/notes/${noteId}`)
      setNotes(prev => prev.filter(n => n.id !== noteId))
    } catch (err) {
      toast.danger('Error', err.message || 'Failed to delete note.')
    } finally {
      setDeletingId(null)
    }
  }

  if (!loaded) {
    return (
      <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-2)', color: 'var(--color-text-muted)', fontSize: 'var(--font-size-sm)' }}>
        <Spinner size="sm" />
        Loading notes…
      </div>
    )
  }

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-3)' }}>
      {notes.length === 0 && !adding && (
        <p style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-muted)', fontStyle: 'italic', margin: 0 }}>
          No notes for this device.
        </p>
      )}

      {notes.map(note => (
        <InnerSurface key={note.id} style={{ padding: 'var(--space-3) var(--space-4)' }}>
          {editingId === note.id ? (
            <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-2)' }}>
              <FormField
                name={`note-edit-${note.id}`}
                type="textarea"
                value={editingText}
                onChange={e => setEditingText(e.target.value)}
                rows={3}
              />
              <div style={{ display: 'flex', gap: 'var(--space-2)' }}>
                <Button variant="primary" size="sm" onClick={() => handleUpdate(note.id)} loading={savingEdit}>
                  Save
                </Button>
                <Button variant="ghost" size="sm" onClick={() => setEditingId(null)}>
                  Cancel
                </Button>
              </div>
            </div>
          ) : (
            <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 'var(--space-3)' }}>
              <div style={{ flex: 1, minWidth: 0 }}>
                <p style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-secondary)', whiteSpace: 'pre-wrap', margin: '0 0 var(--space-1)' }}>
                  {note.content}
                </p>
                <span style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)' }}>
                  {note.created_by && `${note.created_by} · `}{note.created_at ? fmtDateTimeMedium(note.created_at) : ''}
                </span>
              </div>
              {canEdit && (
                <div style={{ display: 'flex', gap: 'var(--space-1)', flexShrink: 0 }}>
                  <Button
                    variant="ghost"
                    size="sm"
                    onClick={() => { setEditingId(note.id); setEditingText(note.content) }}
                    aria-label="Edit note"
                  >
                    <Icon name="edit" size={12} />
                  </Button>
                  <Button
                    variant="danger"
                    size="sm"
                    loading={deletingId === note.id}
                    onClick={() => handleDelete(note.id)}
                    aria-label="Delete note"
                  >
                    <Icon name="delete" size={12} />
                  </Button>
                </div>
              )}
            </div>
          )}
        </InnerSurface>
      ))}

      {/* Add note */}
      {canEdit && (
        adding ? (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-2)' }}>
            <FormField
              name="new-note"
              type="textarea"
              value={newText}
              onChange={e => setNewText(e.target.value)}
              placeholder="Write a note…"
              rows={3}
            />
            <div style={{ display: 'flex', gap: 'var(--space-2)' }}>
              <Button variant="primary" size="sm" onClick={handleAdd} loading={savingNew} disabled={!newText.trim()}>
                Add Note
              </Button>
              <Button variant="ghost" size="sm" onClick={() => { setAdding(false); setNewText('') }}>
                Cancel
              </Button>
            </div>
          </div>
        ) : (
          <Button variant="secondary" size="sm" icon={<Icon name="plus" size={13} />} onClick={() => setAdding(true)} style={{ alignSelf: 'flex-start' }}>
            Add Note
          </Button>
        )
      )}
    </div>
  )
}

// ─── Tags Row ─────────────────────────────────────────────────────────────────

function TagsRow({ tags, canEdit, onAdd, onRemove, saving }) {
  const [input, setInput] = useState('')

  const handleAdd = () => {
    const trimmed = input.trim()
    if (!trimmed) return
    onAdd(trimmed)
    setInput('')
  }

  return (
    <div style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 'var(--space-2)' }}>
      {tags.length === 0 && !canEdit && (
        <span style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-muted)', fontStyle: 'italic' }}>
          No tags.
        </span>
      )}
      {tags.map(tag => (
        <span key={tag} style={{
          display: 'inline-flex',
          alignItems: 'center',
          gap: 4,
          padding: '3px 10px',
          borderRadius: 'var(--radius-full)',
          background: 'var(--color-primary-subtle)',
          border: '1px solid rgba(192,193,255,0.20)',
          fontSize: 'var(--font-size-xs)',
          fontWeight: 'var(--font-weight-semibold)',
          color: 'var(--color-primary)',
          letterSpacing: 'var(--tracking-wide)',
          textTransform: 'uppercase',
        }}>
          {tag}
          {canEdit && (
            <button
              type="button"
              onClick={() => onRemove(tag)}
              disabled={saving}
              aria-label={`Remove tag ${tag}`}
              style={{
                background: 'none', border: 'none', padding: 0, lineHeight: 1,
                cursor: 'pointer', color: 'var(--color-text-muted)', fontSize: 13,
                opacity: saving ? 0.4 : 1,
              }}
            >
              ×
            </button>
          )}
        </span>
      ))}
      {canEdit && (
        <div style={{ display: 'flex', gap: 'var(--space-2)', alignItems: 'center' }}>
          <input
            type="text"
            value={input}
            onChange={e => setInput(e.target.value)}
            onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); handleAdd() } }}
            placeholder="Add tag…"
            style={{
              background: GLASS_BG_INNER,
              border: '1px solid var(--color-border)',
              borderRadius: 'var(--radius-md)',
              padding: '4px 10px',
              fontSize: 'var(--font-size-xs)',
              color: 'var(--color-text-primary)',
              outline: 'none',
              width: 110,
              fontFamily: 'var(--font-family-base)',
            }}
          />
          <Button variant="secondary" size="sm" onClick={handleAdd} disabled={!input.trim() || saving}>
            Add
          </Button>
        </div>
      )}
    </div>
  )
}

// ─── GeneralTab ───────────────────────────────────────────────────────────────

export default function GeneralTab({
  device,
  canEdit,
  attr,
  tags,
  setTags,
  onEditLocation,
  onEditAttributes,
  onEditLogging,
}) {
  const { toast } = useToast()
  const [locationName, setLocationName] = useState(null)
  const [savingTags, setSavingTags] = useState(false)

  // Logging level state — local editable copies for sliders
  const [serialLevel, setSerialLevel] = useState(attr?.serialLogLevel ?? 0)
  const [sdLevel,     setSdLevel]     = useState(attr?.sdLogLevel     ?? 0)
  const [mqttLevel,   setMqttLevel]   = useState(attr?.mqttLogLevel   ?? 0)

  const id = device?.id || device?.device_id

  // Inject CSS keyframes + slider styles
  useEffect(() => { ensureKeyframes() }, [])

  // Keep slider state in sync if attr changes
  useEffect(() => {
    if (Number.isFinite(attr?.serialLogLevel)) setSerialLevel(attr.serialLogLevel)
    if (Number.isFinite(attr?.sdLogLevel))     setSdLevel(attr.sdLogLevel)
    if (Number.isFinite(attr?.mqttLogLevel))   setMqttLevel(attr.mqttLogLevel)
  }, [attr?.serialLogLevel, attr?.sdLogLevel, attr?.mqttLogLevel])

  // Nearest place
  const coords = useMemo(() => parseCoordinates(device?.device_location_coordinates), [device?.device_location_coordinates])

  useEffect(() => {
    if (!coords) return
    setLocationName(null)
    fetch(`https://nominatim.openstreetmap.org/reverse?lat=${coords.lat}&lon=${coords.lng}&format=json`)
      .then(r => r.json())
      .then(data => setLocationName(getNearestPlaceLabel(data) || null))
      .catch(() => setLocationName(null))
  }, [coords])

  const net = attr?.networkSettings || {}
  const staticIpAddress = Array.isArray(net.ipAddress)
    ? net.ipAddress.filter(Boolean).join('.')
    : (net.ipAddress || '')

  // ── Tag handlers ────────────────────────────────────────────────────────────

  const handleAddTag = useCallback(async (tag) => {
    const trimmed = tag.trim()
    if (!trimmed || tags.includes(trimmed)) return
    const next = [...tags, trimmed]
    setSavingTags(true)
    try {
      await api.put(`/devices/${id}/tags`, { tags: next })
      setTags(next)
    } catch (err) {
      toast.danger('Error', err.message || 'Failed to add tag.')
    } finally {
      setSavingTags(false)
    }
  }, [id, tags, setTags, toast])

  const handleRemoveTag = useCallback(async (tag) => {
    const next = tags.filter(t => t !== tag)
    setSavingTags(true)
    try {
      await api.put(`/devices/${id}/tags`, { tags: next })
      setTags(next)
    } catch (err) {
      toast.danger('Error', err.message || 'Failed to remove tag.')
    } finally {
      setSavingTags(false)
    }
  }, [id, tags, setTags, toast])

  // ── Log level save (on blur / edit) ─────────────────────────────────────────

  const handleSaveLogging = useCallback(async (field, value) => {
    if (!canEdit) return
    try {
      await api.put(`/devices/${id}`, {
        device_attributes: {
          ...(attr || {}),
          serialLogLevel: field === 'serial' ? value : serialLevel,
          sdLogLevel:     field === 'sd'     ? value : sdLevel,
          mqttLogLevel:   field === 'mqtt'   ? value : mqttLevel,
        }
      })
      toast.success('Saved', 'Log levels updated.')
    } catch (err) {
      toast.danger('Error', err.message || 'Failed to save log levels.')
    }
  }, [id, attr, serialLevel, sdLevel, mqttLevel, canEdit, toast])

  // ── Network fields ───────────────────────────────────────────────────────────

  const netFields = [
    { label: 'Hostname',    value: net.hostname    || '—' },
    { label: 'IP Address',  value: staticIpAddress || '—' },
    { label: 'Gateway',     value: net.gateway     || '—' },
    { label: 'DNS',         value: net.dns         || '—' },
    { label: 'Subnet',      value: net.subnet      || '—' },
    { label: 'MAC Address', value: net.mac || net.macAddress || '—' },
  ]

  // ── Render ──────────────────────────────────────────────────────────────────

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-6)', width: '100%', maxWidth: 2000, margin: '0 auto' }}>

      {/* ════════════════════════════════════════════════════════════════════
          ROW 1 — Map (2/3) + Safety Settings (1/3)   equal height
          ════════════════════════════════════════════════════════════════════ */}
      <div style={{
        display: 'grid',
        gridTemplateColumns: '2fr 1fr',
        gap: 'var(--space-6)',
        alignItems: 'stretch',
      }}>

        {/* Map */}
        <div style={{
          borderRadius: 'var(--radius-xl)',
          overflow: 'hidden',
          border: '1px solid var(--color-border)',
          boxShadow: 'var(--shadow-card), var(--shadow-md)',
          minHeight: 320,
        }}>
          <LocationMap
            coords={coords}
            locationName={locationName}
            locationLabel={device?.device_location}
            canEdit={canEdit}
            onEdit={onEditLocation}
          />
        </div>

        {/* Safety Settings */}
        <GlassCard
          title="Safety Settings"
          fullHeight
          titleAction={canEdit && (
            <EditActionButton onClick={onEditAttributes} />
          )}
        >
          <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-2)', flex: 1, justifyContent: 'center' }}>
            <SafetyRow label="Bell Guard"  description="Automated striking lock" enabled={attr?.bellGuardOn}       />
            <SafetyRow label="Warnings"    description="Audible system alerts"   enabled={attr?.warningsOn}        />
            <SafetyRow label="Safety"      description="Manual override lock"    enabled={attr?.bellGuardSafetyOn} />
          </div>
        </GlassCard>
      </div>

      {/* ════════════════════════════════════════════════════════════════════
          ROW 2 — Mechanisms | Log Settings | Network Info   equal height
          ════════════════════════════════════════════════════════════════════ */}
      <div style={{
        display: 'grid',
        gridTemplateColumns: '1fr 1fr 1fr',
        gap: 'var(--space-6)',
        alignItems: 'stretch',
      }}>

        {/* Mechanisms */}
        <GlassCard
          title="Mechanisms"
          fullHeight
          titleAction={canEdit && (
            <EditActionButton onClick={onEditAttributes} />
          )}
        >
          <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-4)', flex: 1 }}>

            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 'var(--space-3)' }}>
              {[
                { label: 'Has Bells', value: attr?.hasBells, yes: 'Yes', no: 'No', variant: 'success' },
                { label: 'Has Clock', value: attr?.hasClock, yes: 'Yes', no: 'No', variant: 'info'    },
              ].map(({ label, value, yes, no, variant }) => (
                <InnerSurface key={label} style={{
                  display: 'flex', flexDirection: 'column', alignItems: 'center',
                  gap: 'var(--space-2)', padding: 'var(--space-4)',
                }}>
                  <SectionLabel>{label}</SectionLabel>
                  <StatusBadge variant={value ? variant : 'neutral'}>
                    {value ? yes : no}
                  </StatusBadge>
                </InnerSurface>
              ))}
            </div>

            <InnerSurface style={{
              display: 'flex', alignItems: 'center', justifyContent: 'space-between',
              padding: 'var(--space-3) var(--space-4)',
            }}>
              <SectionLabel>Connected Bells</SectionLabel>
              <span style={{
                fontSize: 'var(--font-size-xl)',
                fontWeight: 'var(--font-weight-bold)',
                fontFamily: 'var(--font-family-mono)',
                color: 'var(--color-text-primary)',
                letterSpacing: 'var(--tracking-display)',
              }}>
                {Number.isFinite(attr?.totalBells) ? attr.totalBells : '—'}
              </span>
            </InnerSurface>
          </div>
        </GlassCard>

        {/* Log Settings */}
        <GlassCard
          title="Log Settings"
          fullHeight
          titleAction={canEdit && (
            <EditActionButton onClick={onEditLogging} />
          )}
        >
          <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-6)', flex: 1, justifyContent: 'center' }}>
            <LogSlider
              label="Serial Log"
              level={serialLevel}
              color={getLogLevelMeta(serialLevel)?.color || 'var(--color-text-muted)'}
              canEdit={canEdit}
              onChange={v => { setSerialLevel(v); handleSaveLogging('serial', v) }}
            />
            <LogSlider
              label="SD Storage"
              level={sdLevel}
              color={getLogLevelMeta(sdLevel)?.color || 'var(--color-text-muted)'}
              canEdit={canEdit}
              onChange={v => { setSdLevel(v); handleSaveLogging('sd', v) }}
            />
            <LogSlider
              label="MQTT Sync"
              level={mqttLevel}
              color={getLogLevelMeta(mqttLevel)?.color || 'var(--color-text-muted)'}
              canEdit={canEdit}
              onChange={v => { setMqttLevel(v); handleSaveLogging('mqtt', v) }}
            />
          </div>
        </GlassCard>

        {/* Network Info */}
        <GlassCard
          title="Network Info"
          fullHeight
          titleAction={canEdit && (
            <EditActionButton onClick={onEditAttributes} />
          )}
        >
          <div style={{
            display: 'grid',
            gridTemplateColumns: '1fr 1fr',
            gap: 'var(--space-4)',
            flex: 1,
            alignContent: 'start',
          }}>
            {netFields.map(({ label, value }) => (
              <div key={label} style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-1)' }}>
                <SectionLabel>{label}</SectionLabel>
                <span style={{
                  fontSize: 'var(--font-size-sm)',
                  fontFamily: 'var(--font-family-mono)',
                  color: value === '—' ? 'var(--color-text-muted)' : 'var(--color-text-primary)',
                  fontWeight: 'var(--font-weight-medium)',
                }}>
                  {value}
                </span>
              </div>
            ))}
          </div>
        </GlassCard>
      </div>

      {/* ════════════════════════════════════════════════════════════════════
          ROW 3 — Tags & Notes (full width)
          ════════════════════════════════════════════════════════════════════ */}
      <GlassCard title="Tags & Notes">
        <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-5)' }}>

          {/* Tags row */}
          <div>
            <div style={{
              fontSize: 'var(--font-size-xs)',
              fontWeight: 'var(--font-weight-semibold)',
              textTransform: 'uppercase',
              letterSpacing: 'var(--tracking-wide)',
              color: 'var(--color-text-muted)',
              marginBottom: 'var(--space-3)',
            }}>
              Tags
            </div>
            <TagsRow
              tags={tags || []}
              canEdit={canEdit}
              onAdd={handleAddTag}
              onRemove={handleRemoveTag}
              saving={savingTags}
            />
          </div>

          {/* Divider */}
          <div style={{ height: 1, background: 'var(--color-border)' }} />

          {/* Device Notes */}
          <div>
            <div style={{
              fontSize: 'var(--font-size-xs)',
              fontWeight: 'var(--font-weight-semibold)',
              textTransform: 'uppercase',
              letterSpacing: 'var(--tracking-wide)',
              color: 'var(--color-text-muted)',
              marginBottom: 'var(--space-3)',
            }}>
              Device Notes
            </div>
            <DeviceNotesSection deviceId={id} canEdit={canEdit} />
          </div>

        </div>
      </GlassCard>

    </div>
  )
}
