// Extend the Window interface to include _discoverySelectedIds for type safety
// Batch enable/disable helper (true module scope for UI access)
import {
  addDevicesInBulk,
  discoverDevices as apiDiscoverDevices,
  fetchBluetoothStatus,
  // fetchDevices, // removed unused import
  syncParentPluginConfigFromDisk,
} from './api.js'
import { loadConfiguredDevices } from './devices.js'
import { uiLog } from './logger.js'
import { hideBusyUi, showBusyUi } from './modal.js'
import { getDiscoveryPreferences, renderDiscoveredDevices, setDiscoveryPreferences } from './render.js'
import { toastError, toastInfo, toastSuccess, toastWarning } from './toast.js'

declare global {
  interface Window {
    _discoverySelectedIds: Set<string>
  }
}

function normalizeId(value: any): string {
  return String(value ?? '').trim().toLowerCase()
}

function dedupeById(devices: any[]): any[] {
  return devices.filter((d, index, arr) => !!d?.id && arr.findIndex(x => x?.id === d.id) === index)
}

function mergeDiscoveredDevices(existingDevices: any[], incomingDevices: any[]): any[] {
  const deviceMap = new Map<string, any>()

  for (const d of dedupeById(existingDevices)) {
    deviceMap.set(d.id, { ...d })
  }

  for (const d of dedupeById(incomingDevices)) {
    const current = deviceMap.get(d.id)
    if (current) {
      // Only set 'Both' if both sources are present in this session
      let nextConnectionType = current.connectionType
      if (current.connectionType && d.connectionType && current.connectionType !== d.connectionType) {
        // Only set 'Both' if both are 'BLE' and 'OpenAPI' (not if one is undefined)
        const types = [current.connectionType, d.connectionType].sort().join(',')
        if (types === 'BLE,OpenAPI' || types === 'OpenAPI,BLE') {
          nextConnectionType = 'Both'
        }
      }
      deviceMap.set(d.id, {
        ...current,
        ...d,
        connectionType: nextConnectionType,
      })
    } else {
      deviceMap.set(d.id, { ...d })
    }
  }

  const merged = [...deviceMap.values()]
  // Log merged device structure for debugging
  if (merged.length > 0) {
    console.warn('[SwitchBot][Discovery][mergeDiscoveredDevices] Merged device sample:', merged[0])

    console.warn('[SwitchBot][Discovery][mergeDiscoveredDevices] Total merged devices:', merged.length)
  }
  return merged
}

type DiscoveryGroupBy = 'connection' | 'hub' | 'type'

interface DiscoveryBleSettings {
  bleEnabled: boolean
  bleScanDurationSeconds: number
  bleTimeoutSeconds: number
}

const DISCOVERY_GROUP_BY_KEY = 'discoveryGroupBy'
const DISCOVERY_GROUP_EXPANDED_KEY = 'discoveryGroupExpanded'
const DISCOVERY_BLE_SETTINGS_KEY = 'discoveryBleSettings'
const DISCOVERY_HIDE_ADDED_KEY = 'discoveryHideAdded'
const DISCOVERY_CACHE_KEY = 'discoveryCache'
const DISCOVERY_AUTO_REFRESH_KEY = 'discoveryAutoRefreshSeconds'
const DISCOVERY_CACHE_TTL_MS = 5 * 60 * 1000

let discoveryAutoRefreshTimer: ReturnType<typeof setInterval> | null = null
let discoveryLastScannedTimer: ReturnType<typeof setInterval> | null = null

interface DiscoveryCachePayload {
  timestamp: number
  devices: any[]
}

function setDiscoveryCache(devices: any[]): void {
  try {
    const payload: DiscoveryCachePayload = { timestamp: Date.now(), devices }
    localStorage.setItem(DISCOVERY_CACHE_KEY, JSON.stringify(payload))
  } catch (_e) {
    // Ignore storage errors
  }
}

function clearDiscoveryCache(): void {
  try {
    localStorage.removeItem(DISCOVERY_CACHE_KEY)
  } catch (_e) {
    // Ignore storage errors
  }
}

function getDiscoveryCache(validOnly = true): DiscoveryCachePayload | null {
  try {
    const stored = localStorage.getItem(DISCOVERY_CACHE_KEY)
    if (!stored) {
      return null
    }
    const payload = JSON.parse(stored) as DiscoveryCachePayload
    if (!payload || !Array.isArray(payload.devices) || typeof payload.timestamp !== 'number') {
      return null
    }
    const age = Date.now() - payload.timestamp
    if (validOnly && age > DISCOVERY_CACHE_TTL_MS) {
      return null
    }
    return payload
  } catch (_e) {
    return null
  }
}

function getDiscoveryAutoRefreshSeconds(): number {
  try {
    const stored = localStorage.getItem(DISCOVERY_AUTO_REFRESH_KEY)
    const value = Number(stored || 0)
    return Number.isFinite(value) && value >= 0 ? value : 0
  } catch (_e) {
    return 0
  }
}

function setDiscoveryAutoRefreshSeconds(value: number): void {
  try {
    localStorage.setItem(DISCOVERY_AUTO_REFRESH_KEY, String(Math.max(0, value)))
  } catch (_e) {
    // Ignore storage errors
  }
}

function getDiscoveryHideAddedPreference(): boolean {
  try {
    return localStorage.getItem(DISCOVERY_HIDE_ADDED_KEY) === 'true'
  } catch (_e) {
    return false
  }
}

function setDiscoveryHideAddedPreference(value: boolean): void {
  try {
    localStorage.setItem(DISCOVERY_HIDE_ADDED_KEY, String(value))
  } catch (_e) {
    // Ignore storage errors
  }
}

function formatElapsedShort(ms: number): string {
  const totalSeconds = Math.max(0, Math.floor(ms / 1000))
  if (totalSeconds < 60) {
    return `${totalSeconds}s ago`
  }
  const minutes = Math.floor(totalSeconds / 60)
  if (minutes < 60) {
    return `${minutes}m ago`
  }
  const hours = Math.floor(minutes / 60)
  if (hours < 24) {
    return `${hours}h ago`
  }
  const days = Math.floor(hours / 24)
  return `${days}d ago`
}

function updateLastScannedStatus(): void {
  const lastScannedStatus = document.getElementById('lastScannedStatus')
  if (!lastScannedStatus) {
    return
  }

  const cache = getDiscoveryCache(false)
  if (!cache) {
    lastScannedStatus.textContent = 'Last scanned: never'
    return
  }

  const ageMs = Date.now() - cache.timestamp
  const timestampText = new Date(cache.timestamp).toLocaleString()
  const stale = ageMs > DISCOVERY_CACHE_TTL_MS
  lastScannedStatus.textContent = stale
    ? `Last scanned: ${formatElapsedShort(ageMs)} (${timestampText}, cache expired)`
    : `Last scanned: ${formatElapsedShort(ageMs)} (${timestampText})`
}

async function renderCachedDiscoveryResults(): Promise<void> {
  const cache = getDiscoveryCache(true)
  const list = document.getElementById('discoveredList')
  if (!cache || !list || !cache.devices.length) {
    return
  }

  list.style.display = 'block'
  // Use type-safe property for window._discoverySelectedIds
  if (!(window as any)._discoverySelectedIds) {
    (window as any)._discoverySelectedIds = new Set()
  }
  await updateDiscoveryView(
    cache.devices,
    getDiscoveryPreferences(),
    getDiscoveryGroupByPreference(),
    getDiscoveryHideAddedPreference(),
    (window as any)._discoverySelectedIds,
  )
}

function getDiscoveryBleSettings(): DiscoveryBleSettings {
  try {
    const stored = localStorage.getItem(DISCOVERY_BLE_SETTINGS_KEY)
    if (!stored) {
      return { bleEnabled: true, bleScanDurationSeconds: 5, bleTimeoutSeconds: 8 }
    }
    const parsed = JSON.parse(stored)
    return {
      bleEnabled: parsed?.bleEnabled !== false,
      bleScanDurationSeconds: Math.max(3, Math.min(15, Number(parsed?.bleScanDurationSeconds || 5))),
      bleTimeoutSeconds: Math.max(3, Math.min(30, Number(parsed?.bleTimeoutSeconds || 8))),
    }
  } catch (_e) {
    return { bleEnabled: true, bleScanDurationSeconds: 5, bleTimeoutSeconds: 8 }
  }
}

function setDiscoveryBleSettings(settings: DiscoveryBleSettings): void {
  try {
    localStorage.setItem(DISCOVERY_BLE_SETTINGS_KEY, JSON.stringify(settings))
  } catch (_e) {
    // Ignore storage errors
  }
}

export async function initializeDiscoverySettings(): Promise<void> {
  const scanSelect = document.getElementById('bleScanDurationSelect') as HTMLSelectElement | null
  const timeoutInput = document.getElementById('bleTimeoutInput') as HTMLInputElement | null
  const disableBleCheckbox = document.getElementById('disableBleScanCheckbox') as HTMLInputElement | null
  const scanSetting = document.getElementById('bleScanSetting')
  const timeoutSetting = document.getElementById('bleTimeoutSetting')
  const bluetoothStatus = document.getElementById('bluetoothStatus')
  const autoRefreshSelect = document.getElementById('autoRefreshIntervalSelect') as HTMLSelectElement | null
  const refreshBtn = document.getElementById('refreshDiscoverBtn') as HTMLButtonElement | null

  const current = getDiscoveryBleSettings()
  if (scanSelect) {
    scanSelect.value = String(current.bleScanDurationSeconds)
  }
  if (timeoutInput) {
    timeoutInput.value = String(current.bleTimeoutSeconds)
  }
  if (disableBleCheckbox) {
    disableBleCheckbox.checked = !current.bleEnabled
  }

  if (autoRefreshSelect) {
    autoRefreshSelect.value = String(getDiscoveryAutoRefreshSeconds())
  }

  const updateBleSettingVisibility = (): void => {
    const disabled = !!disableBleCheckbox?.checked
    if (scanSetting) {
      scanSetting.style.display = disabled ? 'none' : 'inline-flex'
    }
    if (timeoutSetting) {
      timeoutSetting.style.display = disabled ? 'none' : 'inline-flex'
    }
  }

  const persistFromControls = (): void => {
    const next: DiscoveryBleSettings = {
      bleEnabled: !(disableBleCheckbox?.checked ?? false),
      bleScanDurationSeconds: Math.max(3, Math.min(15, Number(scanSelect?.value || 5))),
      bleTimeoutSeconds: Math.max(3, Math.min(30, Number(timeoutInput?.value || 8))),
    }
    setDiscoveryBleSettings(next)
  }

  scanSelect?.addEventListener('change', persistFromControls)
  timeoutInput?.addEventListener('change', persistFromControls)
  disableBleCheckbox?.addEventListener('change', () => {
    persistFromControls()
    updateBleSettingVisibility()
  })

  updateBleSettingVisibility()

  if (bluetoothStatus) {
    const status = await fetchBluetoothStatus()
    bluetoothStatus.textContent = status.available
      ? `Bluetooth: available (${status.message})`
      : `Bluetooth: unavailable (${status.message})`
  }

  updateLastScannedStatus()
  if (discoveryLastScannedTimer) {
    clearInterval(discoveryLastScannedTimer)
  }
  discoveryLastScannedTimer = setInterval(updateLastScannedStatus, 15000)

  refreshBtn?.addEventListener('click', () => {
    void discoverDevices()
  })

  const applyAutoRefresh = (): void => {
    const seconds = Math.max(0, Number(autoRefreshSelect?.value || 0))
    setDiscoveryAutoRefreshSeconds(seconds)

    if (discoveryAutoRefreshTimer) {
      clearInterval(discoveryAutoRefreshTimer)
      discoveryAutoRefreshTimer = null
    }

    if (seconds > 0) {
      discoveryAutoRefreshTimer = setInterval(() => {
        const discoverBtn = document.getElementById('discoverBtn') as HTMLButtonElement | null
        if (!discoverBtn || discoverBtn.disabled || document.hidden) {
          return
        }
        void discoverDevices()
      }, seconds * 1000)
    }
  }

  autoRefreshSelect?.addEventListener('change', applyAutoRefresh)
  applyAutoRefresh()

  await renderCachedDiscoveryResults()
}

function getDiscoveryGroupByPreference(): DiscoveryGroupBy {
  try {
    const stored = localStorage.getItem(DISCOVERY_GROUP_BY_KEY)
    if (stored === 'hub' || stored === 'type') {
      return stored
    }
    return 'type' // Default to Device Type grouping
  } catch (_e) {
    return 'type'
  }
}

function setDiscoveryGroupByPreference(groupBy: DiscoveryGroupBy): void {
  try {
    localStorage.setItem(DISCOVERY_GROUP_BY_KEY, groupBy)
  } catch (_e) {
    // Ignore storage errors
  }
}

function getDiscoveryGroupExpandedState(): Record<string, boolean> {
  try {
    const stored = localStorage.getItem(DISCOVERY_GROUP_EXPANDED_KEY)
    if (!stored) {
      return {}
    }
    const parsed = JSON.parse(stored)
    return typeof parsed === 'object' && parsed ? parsed : {}
  } catch (_e) {
    return {}
  }
}

function setDiscoveryGroupExpandedState(state: Record<string, boolean>): void {
  try {
    localStorage.setItem(DISCOVERY_GROUP_EXPANDED_KEY, JSON.stringify(state))
  } catch (_e) {
    // Ignore storage errors
  }
}

function isDiscoveryGroupExpanded(groupKey: string): boolean {
  const state = getDiscoveryGroupExpandedState()
  return state[groupKey] !== false
}

function setDiscoveryGroupExpanded(groupKey: string, expanded: boolean): void {
  const state = getDiscoveryGroupExpandedState()
  state[groupKey] = expanded
  setDiscoveryGroupExpandedState(state)
}

export async function discoverDevices(): Promise<void> {
  const btn = document.getElementById('discoverBtn') as HTMLButtonElement
  const status = document.getElementById('discoverStatus')
  const phaseProgress = document.getElementById('discoverPhaseProgress') as HTMLElement | null
  const phaseFill = document.getElementById('discoverPhaseFill') as HTMLElement | null
  const phaseLabel = document.getElementById('discoverPhaseLabel') as HTMLElement | null
  const list = document.getElementById('discoveredList')
  const autoAddAll = (document.getElementById('autoAddAllCheckbox') as HTMLInputElement)?.checked
  const scanSelect = document.getElementById('bleScanDurationSelect') as HTMLSelectElement | null
  const timeoutInput = document.getElementById('bleTimeoutInput') as HTMLInputElement | null
  const disableBleCheckbox = document.getElementById('disableBleScanCheckbox') as HTMLInputElement | null

  if (!btn) {
    console.error('[SwitchBot][Discovery] discoverDevices: discoverBtn not found in DOM')
    return
  }
  if (!status) {
    console.error('[SwitchBot][Discovery] discoverDevices: discoverStatus not found in DOM')
    return
  }
  if (!list) {
    console.error('[SwitchBot][Discovery] discoverDevices: discoveredList container not found in DOM')
    toastError('Discovery UI error: device list container missing. Please reload the page.')
    return
  }

  const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
  let spinnerIndex = 0
  const startedAt = Date.now()
  let phaseStartedAt = startedAt
  let phase = 'Preparing discovery...'

  // --- Real-time RSSI polling additions ---
  // (bleScanDurationSeconds is now only used in bleSettings below)

  const setPhase = (nextPhase: string): void => {
    phase = nextPhase
    phaseStartedAt = Date.now()
  }

  const getPhasePercent = (phaseName: string): number => {
    if (phaseName.includes('Scanning BLE')) {
      return 35
    }
    if (phaseName.includes('Fetching OpenAPI')) {
      return 75
    }
    if (phaseName.includes('Complete')) {
      return 100
    }
    return 10
  }

  const renderProgress = (): void => {
    const totalSeconds = Math.max(0, Math.floor((Date.now() - startedAt) / 1000))
    const phaseSeconds = Math.max(0, Math.floor((Date.now() - phaseStartedAt) / 1000))
    const frame = spinnerFrames[spinnerIndex % spinnerFrames.length]
    spinnerIndex += 1
    status.textContent = `${frame} ${phase} (${phaseSeconds}s, ${totalSeconds}s total)`

    if (phaseProgress) {
      phaseProgress.style.display = 'block'
    }
    if (phaseFill) {
      phaseFill.style.width = `${getPhasePercent(phase)}%`
    }
    if (phaseLabel) {
      phaseLabel.textContent = phase
    }
  }

  const progressTimer = setInterval(renderProgress, 250)
  let discoveredDevices: any[] = []
  const preferences = getDiscoveryPreferences()
  let groupBy: DiscoveryGroupBy = getDiscoveryGroupByPreference()
  let hideAdded = getDiscoveryHideAddedPreference()
  // Use persistent selection state across renders
  if (!window._discoverySelectedIds) {
    window._discoverySelectedIds = new Set<string>()
  }
  const selectedIds: Set<string> = window._discoverySelectedIds
  let controlsInitialized = false

  // --- Real-time RSSI polling loop ---
  // (Moved inside main try block after bleSettings is defined)

  // Batch enable/disable helper (moved to module scope for UI access)
  async function batchSetDeviceEnabled(selectedIds: Set<string>, enabled: boolean): Promise<void> {
  // Fetch current config
    // Fetch current config using Homebridge UI API
    if (typeof homebridge.getPluginConfig !== 'function') {
      throw new TypeError('homebridge.getPluginConfig is not available')
    }
    const configArr = await homebridge.getPluginConfig()
    const platformIdx = Array.isArray(configArr) ? configArr.findIndex(c => (c.platform || c.name || '').toLowerCase().includes('switchbot')) : -1
    if (platformIdx === -1) {
      throw new Error('SwitchBot platform config not found')
    }
    const platformConfig = configArr[platformIdx]
    if (!Array.isArray(platformConfig.devices)) {
      throw new TypeError('No devices array in config')
    }
    let changed = false
    for (const dev of platformConfig.devices) {
      const id = String(dev.deviceId || dev.id || '').trim().toLowerCase()
      if (selectedIds.has(id)) {
        if (dev.enabled !== enabled) {
          dev.enabled = enabled
          changed = true
        }
      }
    }
    if (changed) {
      if (typeof homebridge.updatePluginConfig === 'function') {
        await homebridge.updatePluginConfig(configArr)
      } else {
        throw new TypeError('homebridge.updatePluginConfig is not available')
      }
      if (typeof homebridge.savePluginConfig === 'function') {
        await homebridge.savePluginConfig()
      }
    }
  }

  const ensureDiscoveryControls = async (): Promise<void> => {
    // --- Select All / Deselect All controls ---
    const selectAllBtn = document.createElement('button')
    selectAllBtn.textContent = 'Select All'
    selectAllBtn.style.fontSize = '13px'
    selectAllBtn.style.padding = '6px 18px'
    selectAllBtn.style.borderRadius = '6px'
    selectAllBtn.style.background = '#f3f4f6'
    selectAllBtn.style.color = '#1d4ed8'
    selectAllBtn.style.border = '1px solid #d1d5db'
    selectAllBtn.style.cursor = 'pointer'
    selectAllBtn.style.marginRight = '8px'
    selectAllBtn.onclick = () => {
      // Add all visible device IDs to selectedIds
      for (const d of discoveredDevices) {
        selectedIds.add(normalizeId(d.id))
      }
      window.dispatchEvent(new Event('discovery-selection-changed'))
      void updateDiscoveryView(discoveredDevices, preferences, groupBy, hideAdded, selectedIds)
    }

    const deselectAllBtn = document.createElement('button')
    deselectAllBtn.textContent = 'Deselect All'
    deselectAllBtn.style.fontSize = '13px'
    deselectAllBtn.style.padding = '6px 18px'
    deselectAllBtn.style.borderRadius = '6px'
    deselectAllBtn.style.background = '#f3f4f6'
    deselectAllBtn.style.color = '#ef4444'
    deselectAllBtn.style.border = '1px solid #d1d5db'
    deselectAllBtn.style.cursor = 'pointer'
    deselectAllBtn.onclick = () => {
      // Remove all visible device IDs from selectedIds
      for (const d of discoveredDevices) {
        selectedIds.delete(normalizeId(d.id))
      }
      window.dispatchEvent(new Event('discovery-selection-changed'))
      void updateDiscoveryView(discoveredDevices, preferences, groupBy, hideAdded, selectedIds)
    }

    // Insert select/deselect all controls above the action buttons
    const selectControlsRow = document.createElement('div')
    selectControlsRow.style.display = 'flex'
    selectControlsRow.style.gap = '10px'
    selectControlsRow.style.margin = '0 0 10px 0'
    selectControlsRow.appendChild(selectAllBtn)
    selectControlsRow.appendChild(deselectAllBtn)
    if (controlsInitialized) {
      return
    }
    // Always use persistent selectedIds (already defined in outer scope)

    const controlsDiv = document.createElement('div')
    controlsDiv.style.cssText = 'margin-bottom: 12px; display: flex; gap: 12px; flex-wrap: wrap; align-items: center;'

    const filterLabel = document.createElement('label')
    filterLabel.style.fontSize = '12px'
    filterLabel.style.fontWeight = '500'
    filterLabel.textContent = 'Filter:'

    const filterGroup = document.createElement('div')
    filterGroup.style.display = 'flex'
    filterGroup.style.gap = '4px'

    const filterOptions: Array<{ label: string, value: 'all' | 'ble' | 'api' | 'both' | 'ir' }> = [
      { label: 'All', value: 'all' },
      { label: 'BLE', value: 'ble' },
      { label: 'API', value: 'api' },
      { label: 'Both', value: 'both' },
      { label: 'IR', value: 'ir' },
    ]

    for (const option of filterOptions) {
      const filterBtn = document.createElement('button')
      filterBtn.textContent = option.label
      filterBtn.style.padding = '4px 8px'
      filterBtn.style.fontSize = '11px'
      filterBtn.style.borderRadius = '3px'
      filterBtn.style.cursor = 'pointer'
      filterBtn.style.border = preferences.connectionType === option.value ? '2px solid #007AFF' : '1px solid #ccc'
      filterBtn.style.backgroundColor = preferences.connectionType === option.value ? '#f0f7ff' : '#fff'
      filterBtn.style.color = preferences.connectionType === option.value ? '#1d4ed8' : '#374151'

      filterBtn.onclick = () => {
        preferences.connectionType = option.value
        setDiscoveryPreferences(preferences)
        void updateDiscoveryView(discoveredDevices, preferences, groupBy, hideAdded, selectedIds)

        Array.prototype.forEach.call(filterGroup.querySelectorAll('button'), (b) => {
          (b as HTMLButtonElement).style.border = '1px solid #ccc';
          (b as HTMLButtonElement).style.backgroundColor = '#fff';
          (b as HTMLButtonElement).style.color = '#374151'
        })
        filterBtn.style.border = '2px solid #007AFF'
        filterBtn.style.backgroundColor = '#f0f7ff'
        filterBtn.style.color = '#1d4ed8'
      }

      filterGroup.appendChild(filterBtn)
    }

    const sortLabel = document.createElement('label')
    sortLabel.style.fontSize = '12px'
    sortLabel.style.fontWeight = '500'
    sortLabel.style.marginLeft = '8px'
    sortLabel.textContent = 'Sort:'

    const sortSelect = document.createElement('select')
    sortSelect.style.fontSize = '11px'
    sortSelect.style.padding = '4px 8px'
    sortSelect.style.borderRadius = '3px'
    sortSelect.value = preferences.sortBy

    const sortOptions = [
      { label: 'Name', value: 'name' },
      { label: 'Signal Strength', value: 'signal' },
      { label: 'Type', value: 'type' },
      { label: 'Connection', value: 'connection' },
    ]

    for (const opt of sortOptions) {
      const sortOption = document.createElement('option')
      sortOption.value = opt.value
      sortOption.textContent = opt.label
      sortSelect.appendChild(sortOption)
    }

    sortSelect.onchange = () => {
      preferences.sortBy = sortSelect.value as any
      setDiscoveryPreferences(preferences)
      void updateDiscoveryView(discoveredDevices, preferences, groupBy, hideAdded, selectedIds)
    }

    const groupSelect = document.createElement('select')
    groupSelect.style.fontSize = '11px'
    groupSelect.style.padding = '4px 8px'
    groupSelect.style.borderRadius = '3px'
    // Set default value to 'type' if no stored preference
    if (!localStorage.getItem(DISCOVERY_GROUP_BY_KEY)) {
      groupSelect.value = 'type'
    } else {
      groupSelect.value = groupBy
    }

    const groupLabel = document.createElement('label')
    groupLabel.style.fontSize = '12px'
    groupLabel.style.fontWeight = '500'
    groupLabel.style.marginLeft = '8px'
    // Set label text to match selected group
    const groupLabelTextMap = {
      connection: 'Connection',
      hub: 'Hub',
      type: 'Device Type',
    }
    groupLabel.textContent = `Group: ${groupLabelTextMap[groupSelect.value] || 'Connection'}`

    const groupOptions: Array<{ label: string, value: DiscoveryGroupBy }> = [
      { label: 'Connection', value: 'connection' },
      { label: 'Hub', value: 'hub' },
      { label: 'Device Type', value: 'type' },
    ]

    for (const opt of groupOptions) {
      const groupOption = document.createElement('option')
      groupOption.value = opt.value
      groupOption.textContent = opt.label
      groupSelect.appendChild(groupOption)
    }

    groupSelect.onchange = () => {
      groupBy = groupSelect.value as DiscoveryGroupBy
      setDiscoveryGroupByPreference(groupBy)
      groupLabel.textContent = `Group: ${groupLabelTextMap[groupSelect.value] || 'Connection'}`
      void updateDiscoveryView(discoveredDevices, preferences, groupBy, hideAdded, selectedIds)
    }

    const hideAddedLabel = document.createElement('label')
    hideAddedLabel.style.display = 'inline-flex'
    hideAddedLabel.style.alignItems = 'center'
    hideAddedLabel.style.gap = '4px'
    hideAddedLabel.style.fontSize = '11px'
    hideAddedLabel.style.marginLeft = '8px'

    const hideAddedCheckbox = document.createElement('input')
    hideAddedCheckbox.type = 'checkbox'
    hideAddedCheckbox.checked = hideAdded
    hideAddedCheckbox.style.margin = '0'
    hideAddedCheckbox.style.width = 'auto'

    const hideAddedText = document.createElement('span')
    hideAddedText.textContent = 'Hide Added'

    hideAddedLabel.appendChild(hideAddedCheckbox)
    hideAddedLabel.appendChild(hideAddedText)

    hideAddedCheckbox.onchange = () => {
      hideAdded = hideAddedCheckbox.checked
      setDiscoveryHideAddedPreference(hideAdded)
      void updateDiscoveryView(discoveredDevices, preferences, groupBy, hideAdded, selectedIds)
    }

    const searchInput = document.createElement('input')
    searchInput.type = 'text'
    searchInput.placeholder = 'Search by name, ID, or type...'
    searchInput.style.fontSize = '13px'
    searchInput.style.padding = '8px 16px'
    searchInput.style.borderRadius = '6px'
    searchInput.style.border = '1px solid #ccc'
    searchInput.style.flex = '1 1 0%'
    searchInput.style.minWidth = '120px'
    searchInput.style.maxWidth = '100%'
    searchInput.style.width = '100%'
    searchInput.value = preferences.searchQuery

    let searchTimeout: NodeJS.Timeout
    searchInput.oninput = () => {
      clearTimeout(searchTimeout)
      searchTimeout = setTimeout(() => {
        preferences.searchQuery = searchInput.value
        setDiscoveryPreferences(preferences)
        void updateDiscoveryView(discoveredDevices, preferences, groupBy, hideAdded, selectedIds)
      }, 300)
    }

    // Shared style for all top action buttons (smaller, more compact)
    const actionBtnStyle = {
      fontSize: '16px',
      padding: '10px 0',
      borderRadius: '10px',
      margin: '0 12px 0 0',
      width: '100%',
      maxWidth: '220px',
      fontWeight: 'bold',
      background: '#ef4444',
      color: '#fff',
      border: 'none',
      cursor: 'pointer',
      boxShadow: '0 2px 8px #0001',
      transition: 'background 0.2s',
      outline: 'none',
      display: 'block',
    }

    // Add Selected button
    const addSelectedBtn = document.createElement('button')
    addSelectedBtn.textContent = 'Add Selected to Config'
    Object.assign(addSelectedBtn.style, actionBtnStyle)
    addSelectedBtn.disabled = true
    addSelectedBtn.onclick = async () => {
      if (!selectedIds.size) {
        return
      }
      addSelectedBtn.disabled = true
      addSelectedBtn.textContent = 'Adding...'
      try {
        showBusyUi()
        const selectedDevices = discoveredDevices.filter(d => selectedIds.has(normalizeId(d.id)))
        const bulkResult = await addDevicesInBulk(selectedDevices.map(d => ({
          deviceId: d.id,
          name: d.name,
          type: d.type,
          rssi: d.rssi,
          address: d.address,
          model: d.model,
        })))
        uiLog.info('Batch add response:', bulkResult)
        if (!bulkResult || bulkResult.success === false) {
          throw new Error(bulkResult?.data?.message || 'Batch add failed')
        }
        const addedCount = bulkResult?.addedCount ?? bulkResult?.data?.addedCount ?? 0
        const skippedCount = bulkResult?.skippedCount ?? bulkResult?.data?.skippedCount ?? 0
        toastSuccess(`Added ${addedCount} device(s)${skippedCount > 0 ? ` (${skippedCount} skipped)` : ''}`)
        await loadConfiguredDevices()
        selectedIds.clear()
        addSelectedBtn.disabled = true
        addSelectedBtn.textContent = 'Add Selected'
        await updateDiscoveryView(discoveredDevices, preferences, groupBy, hideAdded, selectedIds)
      } catch (e) {
        uiLog.error('Batch add error:', e)
        toastError(e instanceof Error ? e.message : 'Failed to add devices')
        addSelectedBtn.disabled = false
        addSelectedBtn.textContent = 'Add Selected'
      } finally {
        hideBusyUi()
      }
    }

    // Enable Selected button
    const enableSelectedBtn = document.createElement('button')
    enableSelectedBtn.textContent = 'Enable Selected'
    Object.assign(enableSelectedBtn.style, actionBtnStyle)
    enableSelectedBtn.disabled = true
    enableSelectedBtn.onclick = async () => {
      if (!selectedIds.size) {
        return
      }
      enableSelectedBtn.disabled = true
      enableSelectedBtn.textContent = 'Enabling...'
      try {
        showBusyUi()
        await batchSetDeviceEnabled(selectedIds, true)
        toastSuccess('Selected devices enabled')
        await loadConfiguredDevices()
        enableSelectedBtn.disabled = true
        enableSelectedBtn.textContent = 'Enable Selected'
        await updateDiscoveryView(discoveredDevices, preferences, groupBy, hideAdded, selectedIds)
      } catch (e) {
        uiLog.error('Batch enable error:', e)
        toastError(e instanceof Error ? e.message : 'Failed to enable devices')
        enableSelectedBtn.disabled = false
        enableSelectedBtn.textContent = 'Enable Selected'
      } finally {
        hideBusyUi()
      }
    }

    // Disable Selected button
    const disableSelectedBtn = document.createElement('button')
    disableSelectedBtn.textContent = 'Disable Selected'
    Object.assign(disableSelectedBtn.style, actionBtnStyle)
    disableSelectedBtn.disabled = true
    disableSelectedBtn.onclick = async () => {
      if (!selectedIds.size) {
        return
      }
      disableSelectedBtn.disabled = true
      disableSelectedBtn.textContent = 'Disabling...'
      try {
        showBusyUi()
        await batchSetDeviceEnabled(selectedIds, false)
        toastSuccess('Selected devices disabled')
        await loadConfiguredDevices()
        disableSelectedBtn.disabled = true
        disableSelectedBtn.textContent = 'Disable Selected'
        await updateDiscoveryView(discoveredDevices, preferences, groupBy, hideAdded, selectedIds)
      } catch (e) {
        uiLog.error('Batch disable error:', e)
        toastError(e instanceof Error ? e.message : 'Failed to disable devices')
        disableSelectedBtn.disabled = false
        disableSelectedBtn.textContent = 'Disable Selected'
      } finally {
        hideBusyUi()
      }
    }

    // Only add the filter/search controls
    controlsDiv.appendChild(filterLabel)
    controlsDiv.appendChild(filterGroup)
    controlsDiv.appendChild(sortLabel)
    controlsDiv.appendChild(sortSelect)
    controlsDiv.appendChild(groupLabel)
    controlsDiv.appendChild(groupSelect)
    controlsDiv.appendChild(hideAddedLabel)
    controlsDiv.appendChild(searchInput)

    // Top row action buttons container
    const topActionRow = document.createElement('div')

    topActionRow.style.display = 'flex'
    topActionRow.style.gap = '20px'
    topActionRow.style.margin = '18px 0 10px 0'
    topActionRow.style.justifyContent = 'flex-start'
    topActionRow.appendChild(addSelectedBtn)
    topActionRow.appendChild(enableSelectedBtn)
    topActionRow.appendChild(disableSelectedBtn)

    // Clear list and append controls in correct order
    list.innerHTML = ''
    list.appendChild(selectControlsRow)
    list.appendChild(topActionRow)
    list.appendChild(controlsDiv)

    let deviceListContainer = document.getElementById('discoveredDevices')
    if (!deviceListContainer) {
      deviceListContainer = document.createElement('ul')
      deviceListContainer.id = 'discoveredDevices'
      deviceListContainer.style.maxHeight = '400px'
      deviceListContainer.style.overflowY = 'auto'
      deviceListContainer.style.marginTop = '12px'
      deviceListContainer.style.padding = '0'
      deviceListContainer.style.listStyle = 'none'
      list.appendChild(deviceListContainer)
    }

    list.style.display = 'block'
    controlsInitialized = true

    // Update action button enabled state based on selection
    const updateActionButtons = () => {
      const hasSelection = selectedIds.size > 0
      addSelectedBtn.disabled = !hasSelection
      enableSelectedBtn.disabled = !hasSelection
      disableSelectedBtn.disabled = !hasSelection
    }
    // Listen for selection changes (selection is managed elsewhere, so poll)
    setInterval(updateActionButtons, 300)
  }

  try {
    const bleSettings: DiscoveryBleSettings = {
      bleEnabled: !(disableBleCheckbox?.checked ?? false),
      bleScanDurationSeconds: Math.max(3, Math.min(15, Number(scanSelect?.value || 5))),
      bleTimeoutSeconds: Math.max(3, Math.min(30, Number(timeoutInput?.value || 8))),
    }
    setDiscoveryBleSettings(bleSettings)

    showBusyUi()
    btn.disabled = true
    btn.textContent = '🔍 Discovering...'
    setPhase(bleSettings.bleEnabled ? 'Scanning BLE...' : 'Skipping BLE scan...')
    renderProgress()
    status.classList.remove('error')

    const devicesFoundDisplay = document.getElementById('discoverDevicesFound')
    if (devicesFoundDisplay) {
      devicesFoundDisplay.style.display = 'none'
      devicesFoundDisplay.classList.remove('discovery-scanning-pulse')
    }

    if (bleSettings.bleEnabled) {
      const bleDevicesRaw = await apiDiscoverDevices('ble', bleSettings)

      discoveredDevices = dedupeById(bleDevicesRaw)
      uiLog.info('BLE discover response:', bleDevicesRaw)

      // Update real-time device counter
      if (devicesFoundDisplay && bleDevicesRaw.length > 0) {
        devicesFoundDisplay.style.display = 'inline'
        devicesFoundDisplay.classList.add('discovery-scanning-pulse')
        devicesFoundDisplay.textContent = `📊 ${bleDevicesRaw.length} device(s) found (scanning...)`
      }
    } else {
      discoveredDevices = []
      uiLog.info('BLE discovery skipped by user setting')
    }

    if (!autoAddAll && discoveredDevices.length > 0) {
      await ensureDiscoveryControls()
      await updateDiscoveryView(discoveredDevices, preferences, groupBy, hideAdded, selectedIds)
      status.textContent = `Showing ${discoveredDevices.length} device(s) from BLE, fetching OpenAPI...`
    }

    setPhase('Fetching OpenAPI...')
    renderProgress()

    try {
      const openApiDevicesRaw = await apiDiscoverDevices('openapi')

      uiLog.info('OpenAPI discover response:', openApiDevicesRaw)
      discoveredDevices = mergeDiscoveredDevices(discoveredDevices, openApiDevicesRaw)

      // Update device counter with merged count
      if (devicesFoundDisplay && discoveredDevices.length > 0) {
        devicesFoundDisplay.textContent = `📊 ${discoveredDevices.length} device(s) found (complete)`
      }
    } catch (openApiError) {
      uiLog.warn('OpenAPI phase failed during discovery:', openApiError)
      // Keep BLE results if available
      if (!discoveredDevices.length) {
        throw openApiError
      }
      if (devicesFoundDisplay) {
        devicesFoundDisplay.classList.remove('discovery-scanning-pulse')
      }
    }

    setPhase('Complete')
    renderProgress()
    uiLog.info('Final merged discover response:', discoveredDevices)

    if (!discoveredDevices.length) {
      status.textContent = 'No devices found in your SwitchBot account'
      toastInfo('No devices found in your SwitchBot account')
      list.style.display = 'none'
      if (devicesFoundDisplay) {
        devicesFoundDisplay.style.display = 'none'
        devicesFoundDisplay.classList.remove('discovery-scanning-pulse')
      }
      clearDiscoveryCache()
      updateLastScannedStatus()
      return
    }

    // If auto-add is enabled, add all devices immediately using bulk endpoint
    if (autoAddAll) {
      status.textContent = `Auto-adding ${discoveredDevices.length} device(s)...`

      try {
        const bulkResult = await addDevicesInBulk(
          discoveredDevices.map(d => ({
            deviceId: d.id,
            name: d.name,
            type: d.type,
            rssi: d.rssi,
            address: d.address,
            model: d.model,
          })),
        )
        uiLog.info('Bulk add response:', bulkResult)

        if (!bulkResult || bulkResult.success === false) {
          throw new Error(bulkResult?.data?.message || 'Bulk add failed')
        }

        const addedCount
          = bulkResult?.addedCount
            ?? bulkResult?.data?.addedCount
            ?? 0
        const skippedCount
          = bulkResult?.skippedCount
            ?? bulkResult?.data?.skippedCount
            ?? 0
        status.textContent = `✓ Added ${addedCount} device(s)${skippedCount > 0 ? ` (${skippedCount} skipped)` : ''}`
        if (addedCount > 0) {
          toastSuccess(`Added ${addedCount} device(s)${skippedCount > 0 ? ` (${skippedCount} skipped)` : ''}`)
        } else if (skippedCount > 0) {
          toastWarning(`No new devices were added (${skippedCount} skipped)`)
        }
        status.classList.remove('error')
        list.style.display = 'none'

        // Sync parent Homebridge form cache and auto-save to prevent cache overwrite
        // Run sync when discovery returned devices (even if backend response shape changes)
        if (discoveredDevices.length > 0) {
          const synced = await syncParentPluginConfigFromDisk(true)
          status.textContent += synced
            ? ' - Config saved automatically.'
            : ' - Warning: config may not persist until you close/reopen settings.'

          if (synced) {
            toastSuccess('Configuration synced and saved automatically')
          } else {
            toastWarning('Configuration sync failed; close and reopen settings before Save')
          }
        }
      } catch (e) {
        uiLog.error('Bulk add error:', e)
        status.textContent = `✗ Error: ${e instanceof Error ? e.message : 'Failed to add devices'}`
        status.classList.add('error')
        toastError(e instanceof Error ? e.message : 'Failed to add devices')
      }

      // Refresh the configured devices list
      await loadConfiguredDevices()
      return
    }

    await ensureDiscoveryControls()
    await updateDiscoveryView(discoveredDevices, preferences, groupBy, hideAdded, selectedIds)
    setDiscoveryCache(discoveredDevices)
    updateLastScannedStatus()
  } catch (e) {
    uiLog.error('Discovery error:', e)
    status.textContent = `Error: ${e instanceof Error ? e.message : 'Discovery failed'}`
    status.classList.add('error')
    toastError(e instanceof Error ? e.message : 'Discovery failed')
    list.style.display = 'none'
  } finally {
    clearInterval(progressTimer)
    hideBusyUi()
    if (phaseProgress) {
      phaseProgress.style.display = 'none'
    }
    if (phaseFill) {
      phaseFill.style.width = '0%'
    }
    if (phaseLabel) {
      phaseLabel.textContent = ''
    }
    const devicesFoundDisplay = document.getElementById('discoverDevicesFound')
    if (devicesFoundDisplay) {
      devicesFoundDisplay.style.display = 'none'
      devicesFoundDisplay.classList.remove('discovery-scanning-pulse')
    }
    btn.disabled = false
    btn.textContent = '🔍 Discover Devices'
  }
}

/**
 * Update discovery list with filtered and sorted devices
 */
async function updateDiscoveryView(
  allDevices: any[],
  preferences: any,
  groupBy: any,
  hideAdded: any,
  selectedIds: Set<string>,
) {
  // Compute visibleDevices based on filters and preferences
  console.warn('[SwitchBot][Discovery] updateDiscoveryView: allDevices', allDevices)
  const visibleDevices = allDevices
    .filter((d) => {
      // Hide already added devices if hideAdded is true
      if (hideAdded && d.added) {
        return false
      }
      // Additional filtering logic can be added here based on preferences
      return true
    })
  console.warn('[SwitchBot][Discovery] visibleDevices after filter:', visibleDevices)
  // Optionally sort devices if needed (sortDevices is imported but not used)
  // .sort((a, b) => a.name.localeCompare(b.name))

  // Set of already added device IDs
  const configuredIds = new Set(
    allDevices.filter(d => d.added).map(d => normalizeId(d.id)),
  )
  // Batch import controls and selection logic are handled later in the file. Removed broken/duplicated code.

  const getConnectionGroup = (device: any): string => {
    if (device?.isIR) {
      return 'IR'
    }
    const connectionType = String(device?.connectionType || '').toLowerCase()
    if (connectionType.includes('both')) {
      return 'Both'
    }
    if (connectionType.includes('ble')) {
      return 'BLE'
    }
    if (connectionType.includes('api')) {
      return 'OpenAPI'
    }
    return 'Unknown'
  }

  const getHubGroup = (device: any): string => {
    const hub = String(device?.hubDeviceId || '').trim()
    return hub ? `Hub ${hub}` : 'No Hub'
  }

  const getTypeGroup = (device: any): string => {
    const type = String(device?.type || '').trim()
    return type || 'Unknown Type'
  }

  const groupedDevices = new Map<string, any[]>()
  for (const d of visibleDevices) {
    let group = getConnectionGroup(d)
    if (groupBy === 'hub') {
      group = getHubGroup(d)
    } else if (groupBy === 'type') {
      group = getTypeGroup(d)
    }
    const groupDevices = groupedDevices.get(group) || []
    groupDevices.push(d)
    groupedDevices.set(group, groupDevices)
  }
  console.warn('[SwitchBot][Discovery] groupedDevices:', groupedDevices)

  let orderedGroups: string[] = []
  if (groupBy === 'hub') {
    const hubGroups = [...groupedDevices.keys()].filter(group => group !== 'No Hub').sort((a, b) => a.localeCompare(b))
    orderedGroups = groupedDevices.has('No Hub') ? [...hubGroups, 'No Hub'] : hubGroups
  } else if (groupBy === 'type') {
    const typeGroups = [...groupedDevices.keys()].filter(group => group !== 'Unknown Type').sort((a, b) => a.localeCompare(b))
    orderedGroups = groupedDevices.has('Unknown Type') ? [...typeGroups, 'Unknown Type'] : typeGroups
  } else {
    const groupOrder = ['Both', 'BLE', 'OpenAPI', 'IR', 'Unknown']
    orderedGroups = groupOrder.filter(group => groupedDevices.has(group))
  }

  const container = document.createElement('div')
  container.id = 'discoveredDevices'
  container.className = 'discovery-groups'
  console.warn('[SwitchBot][Discovery] Rendering device groups:', orderedGroups)

  if (!visibleDevices.length) {
    const empty = document.createElement('div')
    empty.className = 'discovery-group-empty'
    empty.textContent = hideAdded
      ? 'No devices match current filters (or all are already added).'
      : 'No devices match current filters.'
    container.appendChild(empty)
    console.warn('[SwitchBot][Discovery] No visible devices after filtering.')
  } else {
    for (const groupName of orderedGroups) {
      const groupItems = groupedDevices.get(groupName)
      if (!groupItems?.length) {
        continue
      }
      console.warn(`[SwitchBot][Discovery] Rendering group: ${groupName}`, groupItems)
      // ...existing code...
      const groupSection = document.createElement('section')
      groupSection.className = 'discovery-group'
      const groupStorageKey = `${groupBy}:${groupName}`
      let expanded = isDiscoveryGroupExpanded(groupStorageKey)
      const groupHeader = document.createElement('button')
      groupHeader.className = 'discovery-group-header-btn'
      groupHeader.type = 'button'
      const setGroupHeaderText = () => {
        const marker = expanded ? '▾' : '▸'
        groupHeader.textContent = `${marker} ${groupName} (${groupItems.length})`
      }
      setGroupHeaderText()
      groupSection.appendChild(groupHeader)
      const groupList = await renderDiscoveredDevices(groupItems, {
        configuredIds,
        selectedIds,
        onToggleSelect: (device, selected) => {
          const id = normalizeId(device.id)
          if (selected) {
            selectedIds.add(id)
          } else {
            selectedIds.delete(id)
          }
          // Update Add Selected button state
          const btn = document.querySelector('button')?.parentElement?.querySelector('button')
          if (btn && btn.textContent?.includes('Add Selected')) {
            (btn as HTMLButtonElement).disabled = selectedIds.size === 0
          }
        },
      })
      if (!expanded) {
        groupList.style.display = 'none'
      }
      groupHeader.onclick = () => {
        expanded = !expanded
        setDiscoveryGroupExpanded(groupStorageKey, expanded)
        setGroupHeaderText()
        groupList.style.display = expanded ? 'grid' : 'none'
      }
      groupSection.appendChild(groupList)
      container.appendChild(groupSection)
    }
  }

  // Replace or append the rendered list
  const existingList = document.getElementById('discoveredDevices')
  container.id = 'discoveredDevices'
  if (existingList && existingList.parentNode) {
    existingList.replaceWith(container)
  } else {
    // Fallback: append to discovery list container
    const listContainer = document.getElementById('discoveredList')
    if (listContainer) {
      listContainer.appendChild(container)
    } else {
      console.error('[SwitchBot][Discovery] render: discoveredList container not found in DOM (fallback)')
      toastError('Discovery UI error: device list container missing. Please reload the page.')
    }
  }

  // Only update the enabled/disabled state of batch action buttons (created in ensureDiscoveryControls)
  function updateBatchButtonStates() {
    // These buttons are created in ensureDiscoveryControls and should have unique IDs
    const addSelectedBtn = document.getElementById('addSelectedBtn') as HTMLButtonElement | null
    const enableSelectedBtn = document.getElementById('enableSelectedBtn') as HTMLButtonElement | null
    const disableSelectedBtn = document.getElementById('disableSelectedBtn') as HTMLButtonElement | null
    const hasSelection = selectedIds.size > 0
    if (addSelectedBtn) {
      addSelectedBtn.disabled = !hasSelection
    }
    if (enableSelectedBtn) {
      enableSelectedBtn.disabled = !hasSelection
    }
    if (disableSelectedBtn) {
      disableSelectedBtn.disabled = !hasSelection
    }
  }
  window.removeEventListener('discovery-selection-changed', updateBatchButtonStates)
  window.addEventListener('discovery-selection-changed', updateBatchButtonStates)
  // Initial state update
  updateBatchButtonStates()

  // Update status with count
  const status = document.getElementById('discoverStatus')
  if (status) {
    const totalCount = allDevices.length
    const filteredCount = visibleDevices.length
    status.textContent = filteredCount === totalCount
      ? `Found ${totalCount} device(s)`
      : `Showing ${filteredCount} of ${totalCount} device(s)`
  }
}

export async function addDeviceToConfig(device: any): Promise<void> {
  const { addDeviceToConfig: addDevice } = await import('./devices.js')
  await addDevice(device)
}
