import draggable from 'vuedraggable'
import colors from 'vuetify/util/colors'
import { ManagedItems } from '@/modules/ManagedItems'

import { mapActions, mapState } from 'pinia'
import {
	mdiBatteryAlertVariantOutline,
	mdiBattery20,
	mdiBattery50,
	mdiBattery80,
	mdiBattery,
	mdiBatteryUnknown,
	mdiCheckCircle,
	mdiEmoticon,
	mdiEmoticonDead,
	mdiHelpCircle,
	mdiMinusCircle,
	mdiNumeric1Circle,
	mdiNumeric2Circle,
	mdiPlusCircle,
	mdiPowerPlug,
	mdiSleep,
	mdiZWave,
} from '@mdi/js'
import useBaseStore from '../../stores/base.js'
import {
	getBatteryDescription,
	getProtocolIcon,
	getProtocol,
	getProtocolColor,
} from '../../lib/utils.js'
import { instances, manager } from '../../lib/instanceManager.js'
import { defineAsyncComponent } from 'vue'

export default {
	props: {
		socket: Object,
	},
	components: {
		draggable,
		ColumnFilter: defineAsyncComponent(
			() => import('@/components/nodes-table/ColumnFilter.vue'),
		),
		ExpandedNode: defineAsyncComponent(
			() => import('@/components/nodes-table/ExpandedNode.vue'),
		),
		RichValue: defineAsyncComponent(
			() => import('@/components/nodes-table/RichValue.vue'),
		),
		StatisticsArrows: defineAsyncComponent(
			() => import('@/components/custom/StatisticsArrows.vue'),
		),
		ReinterviewBadge: defineAsyncComponent(
			() => import('@/components/custom/ReinterviewBadge.vue'),
		),
		FirmwareUpdateBadge: defineAsyncComponent(
			() => import('@/components/custom/FirmwareUpdateBadge.vue'),
		),
	},
	watch: {
		'managedNodes.selected': function (val) {
			this.$emit('selected', val)
		},
		// Clear selection when switching between physical and virtual views
		// so a lingering selection from the other view doesn't drive the
		// bulk action toolbar.
		nodeView: function () {
			if (this.managedNodes) this.managedNodes.selected = []
		},
	},
	computed: {
		...mapState(useBaseStore, ['nodes']),
		app() {
			return manager.getInstance(instances.APP)
		},
		currentTheme() {
			return this.app.currentTheme
		},
		// Active device view ('physical' | 'virtual'). Backed by the shared
		// store so the selection stays consistent with the compact (smart)
		// view when switching between them.
		nodeView: {
			get() {
				return useBaseStore().uiState.nodeView
			},
			set(value) {
				useBaseStore().setNodeView(value)
			},
		},
		// Nodes shown in the table, split between physical and virtual
		// devices so the two (which have very different characteristics)
		// are never mixed in the same list. A standard Broadcast virtual
		// node always exists, so the split is always relevant.
		displayedNodes() {
			if (!this.managedNodes) return []
			return this.managedNodes.filteredItems.filter((node) =>
				this.nodeView === 'virtual' ? node.virtual : !node.virtual,
			)
		},
	},
	data: function () {
		return {
			search: '',
			managedNodes: null,
			nodesProps: {
				id: { type: 'number', label: 'ID', groupable: false },
				minBatteryLevel: {
					type: 'number',
					label: 'Power',
					customGroupValue: (group) =>
						group
							? `Battery level: ${group}%`
							: 'Mains-powered or battery level unknown',
					customSort: (sortDesc, nodeA, nodeB) =>
						this.powerSort(sortDesc, nodeA, nodeB),
					customValue: (node) => node.minBatteryLevel, // Note: Not required here but kept as demo for use of customValue()
					richValue: (node) => this.powerRichValue(node),
					undefinedPlaceholder: 'Mains', // must match the text of undefined value
				},
				manufacturer: { type: 'string', label: 'Manufacturer' },
				productDescription: { type: 'string', label: 'Product' },
				productLabel: { type: 'string', label: 'Product code' },
				name: { type: 'string', label: 'Name' },
				virtual: {
					type: 'boolean',
					label: 'Virtual',
					richValue: (node) =>
						this.booleanRichValue(node.virtual, {
							true: {
								icon: 'cloud',
								iconStyle: 'color: purple',
								description: 'Virtual Node',
							},
							false: {
								icon: 'device_hub',
								iconStyle: 'color: green',
								description: 'Physical Node',
							},
							default: {
								icon: 'device_hub',
								iconStyle: 'color: green',
								description: 'Physical Node',
							},
						}),
				},
				loc: { type: 'string', label: 'Location' },
				security: {
					type: 'string',
					label: 'Security',
					richValue: (node) => {
						let v = {
							align: 'center',
							icon: mdiHelpCircle,
							iconStyle: `color: ${colors.grey.base}`,
							description: 'Unknown security status',
						}
						if (node.isSecure === true) {
							v.icon = mdiCheckCircle
							v.iconStyle =
								node.security === 'S0_Legacy'
									? `color: ${this.currentTheme.warning}`
									: `color: ${this.currentTheme.success}`
							v.description = node.security
						} else if (node.isSecure === false) {
							v.icon = mdiMinusCircle
							v.iconStyle = `color: ${this.currentTheme.error}`
							v.description = 'No security'
						}
						return v
					},
				},
				supportsBeaming: {
					type: 'boolean',
					label: 'Beaming',
					richValue: (node) =>
						this.booleanRichValue(node.supportsBeaming, {
							default: {
								icon: mdiHelpCircle,
								iconStyle: `color: ${colors.grey.base}`,
								description: 'Unknown beaming support',
							},
							true: {
								icon: mdiCheckCircle,
								iconStyle: `color: ${this.currentTheme.success}`,
								description: 'Beaming is supported',
							},
							false: {
								icon: mdiMinusCircle,
								iconStyle: `color: ${this.currentTheme.error}`,
								description: 'Beaming is unsupported',
							},
						}),
				},
				zwavePlusVersion: {
					type: 'string',
					label: 'Z-Wave+',
					richValue: (node) => {
						let v = {
							align: 'center',
							icon: node.ready ? mdiMinusCircle : mdiHelpCircle,
							iconStyle: node.ready
								? `color: ${this.currentTheme.error}`
								: 'color: grey',
							description: node.ready
								? 'No'
								: 'Unknown ZWave+ version',
						}
						if (node.zwavePlusVersion === undefined) return v
						v.description = `ZWave+ version: ${node.zwavePlusVersion}`
						v.iconStyle = `color: ${this.currentTheme.success}`
						if (node.zwavePlusVersion === 1) {
							v.icon = mdiNumeric1Circle
						} else if (node.zwavePlusVersion === 2) {
							v.icon = mdiNumeric2Circle
						} else {
							v.icon = mdiPlusCircle
							v.displayValue = `${node.zwavePlusVersion}`
						}
						return v
					},
				},
				protocol: {
					type: 'string',
					label: 'Protocol',
					richValue: (node) => {
						let v = {
							align: 'center',
							icon: node.ready ? mdiMinusCircle : mdiHelpCircle,
							iconStyle: node.ready
								? `color: ${this.currentTheme.error}`
								: 'color: grey',
							description: node.ready ? 'No' : 'Unknown Protocol',
						}
						if (node.protocol === undefined) return v

						v.icon = mdiZWave
						v.description = getProtocol(node)
						v.iconStyle = `color: ${getProtocolColor(node, this.currentTheme)}`
						return v
					},
				},
				firmwareVersion: {
					type: 'string',
					label: 'FW',
				},
				status: {
					type: 'string',
					label: 'Status',
					richValue: (node) => {
						let v = {
							align: 'center',
							icon: mdiHelpCircle,
							iconStyle: `color: ${colors.grey.base}`,
							description: node.status,
						}
						switch (node.status) {
							case 'Asleep':
								v.icon = mdiSleep
								v.iconStyle = `color: ${this.currentTheme.warning}`
								break
							case 'Awake':
								v.icon = mdiEmoticon
								v.iconStyle = `color: ${this.currentTheme.success}`
								break
							case 'Dead':
								v.icon = mdiEmoticonDead
								v.iconStyle = `color: ${this.currentTheme.error}`
								break
							case 'Alive':
								v.icon = mdiCheckCircle
								v.iconStyle = `color: ${this.currentTheme.success}`
								break
						}
						return v
					},
				},
				rebuildRoutesProgress: {
					type: 'string',
					label: 'Rebuild routes',
				},
				interviewStage: { type: 'string', label: 'Interview' },
				lastActive: {
					type: 'date',
					label: 'Last Active',
					groupable: false,
				},
				lastAwake: {
					type: 'date',
					label: 'Last Awake',
					groupable: false,
				},
			},
			expanded: [],
			headersMenu: false,
		}
	},
	methods: {
		...mapActions(useBaseStore, ['getDateTimeString']),
		getProgress(node) {
			return node.firmwareUpdate
				? Math.round(
						(node.firmwareUpdate.sentFragments /
							node.firmwareUpdate.totalFragments) *
							100,
					)
				: null
		},
		getRebuildRoutesIcon(status) {
			switch (status) {
				case 'done':
					return { icon: 'done', color: 'success' }
				case 'failed':
					return { icon: 'error', color: 'error' }
				case 'skipped':
					return { icon: 'next_plan', color: 'primary' }
			}

			return undefined
		},
		getProtocolIcon(protocol) {
			return getProtocolIcon(protocol, this.currentTheme)
		},
		groupValue(group) {
			return this.managedNodes.groupValue(group.value)
		},
		richValue(item, propName) {
			return this.managedNodes.richValue(item, propName)
		},
		booleanRichValue(value, valueMap) {
			let map =
				value === undefined
					? valueMap.default
					: value
						? valueMap.true
						: valueMap.false
			return {
				align: 'center',
				icon: map.icon,
				iconStyle: map.iconStyle,
				description: map.description,
			}
		},
		interviewStageColor(status) {
			let map = {
				None: 'grey',
				ProtocolInfo: 'error',
				NodeInfo: 'warning',
				CommandClasses: 'warning',
				OverwriteConfig: 'primary',
				Complete: 'success',
			}
			return map[status] || 'grey'
		},
		// Only show the progress indicator while an interview can actually make
		// progress: not yet complete, and the node is neither dead nor asleep
		// (a stalled interview would otherwise show a misleading spinner).
		showInterviewProgress(node) {
			return (
				node.interviewStage !== 'Complete' &&
				node.status !== 'Dead' &&
				node.status !== 'Asleep'
			)
		},
		powerRichValue(node) {
			let level = node.minBatteryLevel
			let iconStyle = `color: ${this.currentTheme.success}`
			let icon = ''
			let label = ''
			let description = ''
			if (node.isListening) {
				icon = mdiPowerPlug
				description = 'mains-powered'
			} else {
				description = getBatteryDescription(node)
				if (level == null) {
					icon = mdiBatteryUnknown
					iconStyle = `color: ${colors.grey.base}`
					description = 'Battery level: unknown'
				} else {
					label = `${level}%`
					if (level <= 10) {
						icon = mdiBatteryAlertVariantOutline
						iconStyle = `color: ${this.currentTheme.error}`
					} else if (level <= 30) {
						icon = mdiBattery20
						iconStyle = `color: ${this.currentTheme.warning}`
					} else if (level <= 70) {
						icon = mdiBattery50
					} else if (level <= 90) {
						icon = mdiBattery80
					} else {
						icon = mdiBattery
					}
				}
			}
			return {
				align: 'left',
				icon: icon,
				iconStyle: iconStyle,
				displayValue: label,
				displayStyle: '',
				description: description,
				rawValue: level,
			}
		},
		powerSort(sortDesc, nodeA, nodeB) {
			// Special sort for power column
			let levelA = nodeA.isListening ? 101 : nodeA.minBatteryLevel || 0

			let levelB = nodeB.isListening ? 101 : nodeB.minBatteryLevel || 0

			let res = levelA < levelB ? -1 : levelA > levelB ? 1 : 0
			res = sortDesc ? -res : res
			return res
		},
	},
	mounted() {
		this.managedNodes = new ManagedItems(
			this.nodes,
			this.nodesProps,
			localStorage,
			'nodes_',
		)
	},
}
