// form/nc/nc-define/nc-layout-editor/nc-layout-keyboard.js

import { parentNode, parentRowNode, parentColNode, isContainer, toggleFocus, deleteNode, toElement } from '/plugin/nc-layout-editor/nc-layout-editor-util.js'
import { _ as nc } from '/assets/nc-plugin.js'
const message = nc.message
const peg = nc.peg

let state, rec, keyJson
export function initKeyboard(state_) {
	state = state_
	rec = state.rec
	keyJson = rec.keyboard_rule //  './keyboard-rule.json'
}

const columnDefaultWidthCount = '12'
let prevNonContainerNode
let prevRow = -1
let prevCol = -1
let currentCol = -1
let currentRow = -1
let rule = null
// let keyEventDisabled = false

/* export function disableKeyEvent(state) {
	keyEventDisabled = state
} */

function addClass(el, className) {
	if (el?.classList?.add) {
		el.classList.add(className)
	} else if (el.classList == null) {
		el.classList = [className]
	} else if (nc.isArray(el.classList)) {
		el.classList.push(className)
	} else {
		console.error('nc-layout-keyboard, addClass: el.classList is not an array')
	}
}

function removeClass(el, className) {
	if (el.classList == null) {
		return
	} else if (el?.classList?.remove) {
		el.classList.remove(className)
	} else if (nc.isArray(el.classList)) {
		const index = el.classList.indexOf(className)
		if (index > -1) {
			el.classList.splice(index, 1) // Remove one item at the specified index
		}
	} else {
		console.error('nc-layout-keyboard, addClass: el.classList is not an array')
	}
}

export function keyEvent(event, node) {
	/* if (keyEventDisabled) {
		return
	} */
	let err
	if (node.component) {
		rule = findRule(event, node, 'draw_area')
		if (rule) {
			event.preventDefault()
			event.stopPropagation()
			console.log('keyPress', node.component, rule, event)
			const el = document.getElementById('nc-layout-area')
			addClass(el, 'nc-hide-mouse-hover')
			// select events
			if (rule.action === 'select-sibling-previous') {
				const parent = parentNode(node)
				if (parent && parent.element.indexOf(node) <= 0) {
					prevCol = -1
					err = selectNode(parent, 'before', 'select') // err = selectNode(node, 'parent', 'select') -- selects same row
				} else {
					err = selectNode(node, 'before', 'select')
				}
			} else if (rule.action === 'select-sibling-next') {
				const parent = parentNode(node)
				if (parent && parent.element.indexOf(node) === parent.element.length - 1) {
					const parentParent = parentNode(parent)
					if (parentParent) {
						prevCol = -1
						err = selectNode(parent, 'after', 'select') // err = selectNode(node, 'parent', 'select') -- selects same row
					} else {
						err = selectNode(node, 'after', 'select') // err = selectNode(node, 'parent', 'select') -- selects same row
					}
				} else {
					err = selectNode(node, 'after', 'select')
				}
			} else if (rule.action === 'select-sibling-first') {
				err = selectNode(node, 'first', 'select')
			} else if (rule.action === 'select-sibling-last') {
				err = selectNode(node, 'last', 'select')
			} else if (rule.action === 'select-previous-or-parent') {
				err = selectNode(node, 'before-or-parent', 'select')
			} else if (rule.action === 'select-next-or-child') {
				err = selectNode(node, 'child-or-after', 'select')
			} else if (rule.action === 'select-parent-row-previous') {
				const rowNode = parentRowNode(node)
				if (rowNode) {
					err = selectNode(rowNode, 'before', 'select')
				} else {
					err = selectNode(node, 'before', 'select') // no more upper level rows
				}
			} else if (rule.action === 'select-parent-row-next') {
				const rowNode = parentRowNode(node)
				if (rowNode) {
					err = selectNode(rowNode, 'after', 'select')
				} else {
					err = selectNode(node, 'after', 'select')
				}
			} else if (rule.action === 'select-parent') {
				prevCol = -1
				if (node.component === 'nc-row') {
					const parent = parentNode(node)
					prevRow = (parent.element && parent.element.indexOf(node)) || -1
				}
				err = selectNode(node, 'parent', 'select')
			} else if (rule.action === 'select-child') {
				err = selectNode(node, 'child', 'select')
			} else if (rule.action === 'select-first-child') {
				prevRow = -1
				if (node.element && node.element.length > 0) {
					const childNode = node.element[0]
					err = selectNode(childNode, 'first', 'select')
				}
			} else if (rule.action === 'select-last-child') {
				prevRow = -1
				if (node.element && node.element.length > 0) {
					const childNode = node.element[0]
					err = selectNode(childNode, 'last', 'select')
				} /* else {
					const parent = parentNode(node)
					const parentParent = parentNode(parent)
					if (parentParent && !parentParent.element) {
						// topmost
						const idx = parent.element.indexOf(node)
						if (idx === 0) {
							err = selectNode(node, 'parent', 'select') // node is first row of area
							node = parentParent // prevent going inside next if
						}
					}
				} */
			} else {
				// swap events
				if (rule.action === 'swap-sibling-first') {
					err = moveNode(node, 'first')
				} else if (rule.action === 'swap-sibling-last') {
					err = moveNode(node, 'last')
				} else if (rule.action === 'swap-sibling-previous') {
					err = moveNode(node, 'before')
				} else if (rule.action === 'swap-sibling-next') {
					err = moveNode(node, 'after')
				} else if (rule.action === 'swap-parent-row-previous') {
					const rowNode = parentRowNode(node)
					if (rowNode) {
						err = moveNode(rowNode, 'before')
					} else {
						err = moveNode(node, 'before')
					}
				} else if (rule.action === 'swap-parent-row-next') {
					const rowNode = parentRowNode(node)
					if (rowNode) {
						err = moveNode(rowNode, 'after')
					} else {
						err = moveNode(node, 'after')
					}
				} else if (rule.action === 'copy') {
					if (rule.style) {
						node.editStyle = rule.style
						node.ncElem.vnode.addClass(el, 'nc-' + rule.style)
					}
				} else {
					// todo: special commands if (rule.action === 'container-width') {
					if (rule.style) {
						node.editStyle = rule.style
						node.ncElem.vnode.addClass(el, 'nc-' + rule.style)
					}
					console.debug(`*** keyboard event action: '${rule.action}'`)
					if (rule.action === 'column-width') {
						const colNode = parentColNode(node)
						if (colNode) {
							if (!colNode.attribute) {
								colNode.attribute = {}
							}
							if (parseInt(rule.parameter) != rule.parameter) {
								// "auto"
								colNode.attribute.width = rule.parameter
							} else {
								const pos = colNode.attribute.width.indexOf('/')
								if (pos > 0) {
									colNode.attribute.width = rule.parameter + colNode.attribute.width.substring(pos)
								} else {
									colNode.attribute.width = rule.parameter + '/' + columnDefaultWidthCount
								}
							}
						}
					} else if (rule.action === 'toggle') {
						toggleFocus(state, 'toggle') // event.stopPropagation()
					} else if (rule.action === 'file') {
						toggleFocus(state, 'close')
						rec.accordion_open.layout = !rec.accordion_open.layout
						/* 	} else if (rule.action === 'preference') {
						rec.accordion_open.preference = !rec.accordion_open.preference
						*/
					} else if (rule.action === 'template') {
						rec.accordion_open.template = !rec.accordion_open.template
					} else if (rule.action === 'reuse_element') {
						rec.accordion_open.reuse_element = !rec.accordion_open.reuse_element
					} else if (rule.action === 'element') {
						toggleFocus(state, 'close')
						rec.accordion_open.element = true
					} else if (rule.action === 'property') {
						toggleFocus(state, 'close')
						rec.accordion_open.property = true
					} else if (rule.action === 'delete') {
						deleteNode(state, node)
					} else if (rule.action === 'cut') {
						console.debug(`*** keyboard event action '${rule.action}' has not been done yet`)
					} else if (rule.action === 'copy') {
						console.debug(`*** keyboard event action '${rule.action}' has not been done yet`)
					} else if (rule.action === 'paste') {
						console.debug(`*** keyboard event action '${rule.action}' has not been done yet`)
					} else if (rule.action === 'duplicate') {
						console.debug(`*** keyboard event action '${rule.action}' has not been done yet`)
					} else if (rule.action === 'add-element') {
						console.debug(`*** keyboard event action '${rule.action}' has not been done yet`)
					} else {
						console.warn(`*** invalid keyboard event action '${rule.action}'`)
					}
				}
			}
			if (err) {
				if (peg.startsWith(err, 'error:')) {
					console.debug(`keyboard event error: '${err}'`)
					message.setWarning(null, err)
				} else if (err !== 'ok') {
					console.debug(`keyboard event message: '${err}'`)
				}
			}
			rule = null
		}
	}
	return err
}

function selectNode(node, position, action) {
	const { targetNode, error } = findTarget(node, position, action)
	if (error) {
		return error
	}
	if (targetNode && targetNode.ncElem) {
		if (node === parentNode(targetNode)) {
			const nodeIsContainer = isContainer(node)
			const targetIsContainer = isContainer(targetNode)
			if (!prevNonContainerNode && !nodeIsContainer && !targetIsContainer) {
				prevNonContainerNode = node
			}
		}
		if (targetNode.component === 'nc-col') {
			const parentRow = parentRowNode(targetNode)
			if (parentRow) {
				prevCol = parentRow.element.indexOf(targetNode)
			}
		} else if (targetNode.component !== 'nc-row' && parentRowNode(targetNode)) {
			prevCol = -1
		}
		selectEvent(targetNode)
	} else {
		prevNonContainerNode = null
	}
	return 'ok'
}

export function moveNode(node, position, targetNode_, targetIndex_) {
	let { parent, targetNode, nodeIndex, targetIndex, error } = findTarget(node, position, 'move')
	if (targetNode_ && targetIndex_ != null) {
		targetNode = targetNode_
		targetIndex = targetIndex_
	}
	if (error) {
		return error
	}
	if (!parent || !parent.element) {
		return {
			error: `error: findTarget parent node does not exist`
		}
	}
	// debugger
	// array.splice(start[, deleteCount[, item1[, ...]]])
	if (targetIndex === 0 || targetIndex === parent.element.length - 1) {
		if (parent.element.length > 1) {
			parent.element.splice(nodeIndex, 1) // delete moved node from previous start/end
			parent.element.splice(targetIndex, 0, node) // add moved node to another start/end
		}
	} else {
		// do swap
		parent.element.splice(nodeIndex, 1, targetNode) // delete moved node and add targetNode to moved node index
		parent.element.splice(targetIndex, 1, node) // delete targetNode and add moved node to it's place
	}
	return 'ok'
}

export function selectEvent(node, state_) {
	let rec = state.rec
	if (state_) {
		rec = state_.rec
	}
	let rowNode, parent
	if (node.component === 'nc-row') {
		rowNode = node
		parent = parentNode(rowNode)
	} else {
		rowNode = parentRowNode(node)
		parent = parentNode(rowNode)
		if (parent && parent.element) {
			currentRow = parent.element.indexOf(rowNode)
		} else {
			currentRow = -1
		}
		parent = parentNode(node)
		if (prevRow > -1) {
			if (parent && parent.element && parent.element.length > 0 && prevRow < parent.element.length) {
				node = parent.element[prevRow] // select same row as in previous row
				prevRow = -1
				parent = parentNode(node)
			}
		}
		if (parent && node.component === 'nc-col' && parent.component === 'nc-row') {
			if (!(rule && rule.action)) {
				prevCol = parent.element.indexOf(node) // click event
			}
		}
		currentCol = prevCol
		if (!isContainer(node)) {
			const parentParent = parentNode(parent)
			if (!parentParent || !parentParent.element) {
				currentCol = -1
			} else if (parent.component === 'nc-col') {
				const rowNode = parentRowNode(parent)
				if (rowNode) {
					prevCol = rowNode.element.indexOf(parent)
					currentCol = prevCol
				}
			}
		}
		if (prevCol > -1 && node.component === 'nc-row') {
			if (node.element.length > 0) {
				if (prevCol > node.element.length - 1) {
					currentCol = node.element.length - 1
					node = node.element[currentCol] // select same col as in previous row
				} else {
					node = node.element[prevCol]
				}
				parent = parentNode(node)
			}
		}
	}
	if (rec.selected_node.ncElem) {
		let el = toElement(rec.selected_node)
		el && removeClass(el, 'nc-selected-element')
		// remove nc-selected-parent class from previous parent and parent row
		const selectedNodeParent = parentNode(rec.selected_node)
		if (selectedNodeParent && selectedNodeParent.ncElem) {
			el = toElement(selectedNodeParent)
			el && removeClass(el, 'nc-selected-parent')
			// const rowNode = toElement(parentRowNode(selectedNodeParent))
			el && removeClass(el, 'nc-selected-parent')
		}
	}
	if (node.ncElem) {
		// debugger
		let el = toElement(node)
		el && addClass(el, 'nc-selected-element') // todo: fix disappearing class with fa svg icons
		if (parent && parent.ncElem) {
			// add nc-selected-parent class to parent and parent row
			el = toElement(parent)
			el && addClass(el, 'nc-selected-parent')
			el = toElement(parentRowNode(parent))
			el && addClass(el, 'nc-selected-parent')
		}
		rec.selected_node = node
		rec.selected_node.column = prevCol
		rec.selected_node.currentColumn = currentCol
		rec.selected_node.currentRow = currentRow
		/* if (!rule) {
			toggleFocus(state, '?', node) // not a keyboard event
		} */
	} else {
		message.setError(null, `node.ncElem is missing, component ${node.component}' `)
		rec.selected_node = {}
	}
	rec.attribute = rec.selected_node.attribute || {}
}

function findTarget(node, position, action) {
	let parent = parentNode(node)
	if (position === 'parent') {
		return { targetNode: parent }
	} else if (position === 'child') {
		if (node.element && node.element.length > 0) {
			return { targetNode: node.element[0] }
		} else {
			return { targetNode: null }
		}
	}
	if (!parent) {
		return {} // parent does not exist
	}
	if (node === parent) {
		return {
			error: `error: node is same as parent`
		}
	}
	if (!parent.element) {
		if (position === 'child-or-after') {
			return { targetNode: node.element[0] }
		} else if (position === 'before-or-parent') {
			return { targetNode: node.element[node.element.length - 1] }
		}
		return { targetNode: node } // at top level
	}
	const nodeIndex = parent.element.indexOf(node)
	let targetIndex
	if (position === 'before') {
		targetIndex = nodeIndex - 1
		if (action === 'select' && targetIndex < 0) {
			if (!isContainer(node)) {
				return findTarget(parent, 'before', action) // get out of non-container element to upper level, usually column
			}
			return { targetNode: parent }
		}
	} else if (position === 'after') {
		targetIndex = nodeIndex + 1
		if (action === 'select' && targetIndex > parent.element.length - 1) {
			// && !isContainer(node)
			const parentParent = parentNode(parent)
			if (parentParent && parentParent.element) {
				return findTarget(parent, 'after', action)
			} else {
				return { targetNode: parent } // last row in a topmost element (form), select topmost element
			}
		}
	} else if (position === 'before-or-parent') {
		if (node.element && node.element.length > 0) {
			return { targetNode: node.element[node.element.length - 1] }
		}
		if (nodeIndex === 0) {
			const parentParent = parentNode(parent)
			if (isContainer(parentParent)) {
				if (prevNonContainerNode === parentParent.element[0] || parent === parentParent.element[0]) {
					prevNonContainerNode = null
					prevCol = -1
					return findTarget(parentParent, 'before', action)
				}
			}
		}
		if (nodeIndex === 0) {
			prevCol = -1
			return findTarget(parent, 'before', action)
		}
		targetIndex = nodeIndex - 1
	} else if (position === 'child-or-after') {
		if (nodeIndex === parent.element.length - 1) {
			const parentParent = parentNode(parent)
			if (isContainer(parentParent)) {
				if (prevNonContainerNode == parentParent.element[parentParent.element.length - 1] || parent === parentParent.element[parentParent.element.length - 1]) {
					prevNonContainerNode = null
					prevCol = -1
					return findTarget(parentParent, 'after', action)
				}
			}
		}
		if (node.element && node.element.length > 0) {
			return { targetNode: node.element[0] }
		}
		if (nodeIndex === parent.element.length - 1 && !isContainer(node)) {
			prevCol = -1
			return findTarget(parent, 'after', action)
		}
		targetIndex = nodeIndex + 1
	} else if (position === 'first') {
		targetIndex = 0
	} else if (position === 'last') {
		targetIndex = parent.element.length - 1
	} else {
		return {
			error: `error: unknown position: ${position}`
		}
	}
	if (targetIndex < 0) {
		targetIndex = parent.element.length - 1 // first to last
	} else if (targetIndex > parent.element.length - 1) {
		targetIndex = 0 // last to first
	}
	const targetNode = parent.element[targetIndex]
	return { parent, targetNode, nodeIndex, targetIndex }
}

function findRuleFromArr(event, ruleArr) {
	return ruleArr.find(rec => {
		if (rec.modifier == null) {
			if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) return false // is event.shiftKey ok here?
			return true
		}
		for (let key in rec.modifier) {
			if (rec.modifier[key] === true && rec.modifier[key] !== event[key]) {
				return false
			}
		}
		return true
	})
}

function findRule(event, node, ruleTarget) {
	event.cmdOrCtrlKey = nc.isMac() ? event.metaKey : event.ctrlKey
	let rule
	let ruleKey = keyJson[ruleTarget][node.component] || keyJson[ruleTarget].default
	let ruleArr = ruleKey[event.code]
	// console.log(event.code, event)
	const aliasKey = keyJson[ruleTarget].alias && keyJson[ruleTarget].alias[event.code]
	// how about keyJson.element, when to use it, how about properties?
	if (!ruleArr && aliasKey) {
		ruleArr = ruleKey[aliasKey]
	}
	if (!ruleArr && ruleKey !== keyJson[ruleTarget].default) {
		ruleKey = keyJson[ruleTarget].default
		ruleArr = ruleKey[event.code]
		if (!ruleArr && aliasKey) {
			ruleArr = ruleKey[aliasKey]
		}
	}
	if (ruleArr) {
		rule = findRuleFromArr(event, ruleArr)
		if (!rule && aliasKey) {
			ruleArr = ruleKey[aliasKey]
			if (ruleArr) {
				rule = findRuleFromArr(event, ruleArr)
			}
		}
	}
	if (!rule) {
		ruleKey = keyJson.global[node.component] || keyJson.global.default
		ruleArr = ruleKey[event.code]
		if (!ruleArr && ruleKey !== keyJson.global.default) {
			ruleKey = keyJson.global.default
			ruleArr = ruleKey[event.code]
		}
		if (!ruleArr && aliasKey) {
			ruleArr = ruleKey[aliasKey]
		}
		if (ruleArr) {
			rule = findRuleFromArr(event, ruleArr)
			if (!rule && aliasKey) {
				ruleArr = ruleKey[aliasKey]
				if (ruleArr) {
					rule = findRuleFromArr(event, ruleArr)
				}
			}
		}
	}
	return rule
}
