// form/nc/nc-define/nc-layout-editor/nc-layout-editor-util.js
// set json attribute.id, convert json to html or pug

import { ref } from '/vue.js'
import { selectEvent } from '/plugin/nc-layout-editor/nc-layout-keyboard.js'

export function propertyComponentName(name) {
	if (name.substring(0, 3) === 'nc-') {
		return 'nc-property-' + name.substring(3)
	}
	return 'nc-property-' + name
}

export function loadPropertyComponent(nc, rec, name) {
	if (!rec.component_map[name]) {
		console.error(
			`nc-layout-editor, property component '${name}' path does not exist, please check "form/nc/nc-define/nc-layout-editor/nc-element-property/nc-property-component.json"`
		)
		return
	}
	if (!rec.component_loaded[name]) {
		rec.component_loaded[name] = true
		console.debug(`nc-layout-editor, add property component:`, name, rec.component_map[name])
		nc.addComponent({ name: name, path: rec.component_map[name] })
	}
}

export function parentElement(el) {
	let node = el.ncNode
	while (!node && el.id !== 'nc-layout-area') {
		el = el.parentNode
		node = el.ncNode
	}
	return node
}

export function parentNode(ncNode) {
	if (!ncNode) return
	const elemProps = ncNode.ncElem && ncNode.ncElem.props
	if (elemProps && elemProps.ncParent) {
		return elemProps.ncParent
	}
	// comes here when scrolling down the area
	return
}

export function parentElementNode(node, component) {
	let rowNode
	if (node.ncElem.vnode.el.closest) {
		rowNode = node.ncElem.vnode.el.closest('.' + component)
	} else {
		rowNode = parentNode(node) // nc-tab-pane does not have closest() defined, but nc-tab-pane does not exist any more? // fix/test
		if (rowNode) {
			if (rowNode.ncElem.vnode.el && rowNode.ncElem.vnode.el.closest) {
				rowNode = rowNode.ncElem.vnode.el.closest('.' + component)
			} else {
				rowNode = rowNode.ncElem.vnode.el.parentNode.closest('.' + component)
			}
		}
	}
	return (rowNode && rowNode.ncNode && rowNode.ncNode.component === component && rowNode.ncNode) || null
}

export const hoverBoxStyle = ref()
let nextIdNum = 0

function nextId() {
	nextIdNum++
	return nextIdNum
}

export function toElement(node) {
	return node && node.ncElem && node.ncElem.vnode.el
	/* let el = node && node.ncElem && node.ncElem.vnode.el
	if (el && !el.classList) {
		debugger
		el = el.nextElementSibling
	}
	return el */
}

export function restCallback(state) {
	return function (ret) {
		if (ret && ret.status !== 'ok') {
			nc.setRec(state, 'layout_name', '') // set to empty layout to prevent infinite call loop on error
		}
	}
}

export function toggleFocus(state, action) {
	if (!rec.accordion_open) {
		return
	}
	if (action === 'close') {
		for (const key of Object.keys(rec.accordion_open)) {
			rec.accordion_open[key] = false
		}
	} else {
		const element = rec.accordion_open.element
		const property = rec.accordion_open.property
		toggleFocus(state, 'close')
		if ((element && property) || (!element && !property)) {
			rec.accordion_open.property = true
			rec.accordion_open.element = false
		} else {
			rec.accordion_open.property = !property
			rec.accordion_open.element = !element
		}
	}
}
export function getVueElementDepth(vueElement, rootVue, depth) {
	// note: how many levels up should we test for depth? give maxDepth as a param?
	if (vueElement == null || vueElement.parent === rootVue) {
		return depth
	} else {
		return getVueElementDepth(vueElement.parent, rootVue, depth + 1)
	}
}

export function isContainer(comp) {
	const name = comp && comp.component
	return (
		name &&
		(name === 'nc-row' ||
			name === 'nc-col' ||
			name === 'nc-form' ||
			name === 'nc-form-fields' ||
			name === 'NcRow' ||
			name === 'NcCol' ||
			name === 'NcForm' ||
			name === 'NcFormFields')
	)
}

export function isNcElement(ncNode) {
	if (ncNode.text != null || (ncNode.component && ncNode.component.substring(0, 3) !== 'nc-')) {
		return false
	}
	return true
}

export function parentRowNode(node) {
	return parentElementNode(node, 'nc-row')
}

export function parentColNode(node) {
	return parentElementNode(node, 'nc-col')
}

export function deleteNodeContent(ncNode) {
	if (ncNode.dnd) {
		// ncNode.dnd.draggable(false).dropzone(false).resizable(false).gesturable(false).unset() // interact
		ncNode.dnd.stop(true)
		ncNode.dnd.destroy()
		delete ncNode.dnd
	}
	if (!ncNode.ncElem) {
		return
		/* message.setError(null, `ncNode.ncElem does not exist, component '${ncNode.component}'`) */
	}
	delete ncNode.ncElem // ncNode.ncElem is lost in some cases, does Vue reuse this instance?
}

export function setIdAndAttribute(rec, extraAttribute, contentRecursion) {
	if (!contentRecursion && rec.component == 'nc-layout-content') {
		rec.component = 'nc-layout-content--DISABLED-TO-PREVENT-RECURSION'
	}
	rec.id = nextId()
	if (extraAttribute) {
		extraAttribute.forEach(attr => {
			if (attr.component.includes(rec.component)) {
				rec.attribute = Object.assign({}, rec.attribute, attr.attribute)
			}
		})
	}
	/*
	if (!rec.attribute) {
		rec.attribute = {}
	}
	if (!rec.attribute.class) {
		rec.attribute.class = []
	}
	if (isContainer(rec)) {
		rec.attribute.class.push('nc-container')
	} else {
		rec.attribute.class.push('nc-element')
	} */
	/* if (!rec.attribute.id) {
		rec.attribute.id = rec.id
	} */
	if (rec.element && rec.element.length > 0) {
		rec.element.forEach(rec2 => {
			setIdAndAttribute(rec2, extraAttribute)
		})
	}
	return rec
}

export function addNode(newNode, selectedNode, location) {
	let parent = parentNode(selectedNode) // parent must have selectedNode in element -array
	if (isContainer(selectedNode)) {
		if (!isContainer(newNode)) {
			parent = selectedNode
			if (location === 'before') {
				location = 'first'
			} else if (location !== 'first') {
				location = 'last'
			}
		}
		if (!parent.element) {
			parent.element = []
			parent.id = nextId() // trigger redraw by changing v-for key
		}
	}
	if (!parent) {
		message.setWarning(null, `addNode() parent does not exist`)
		return
	}
	if (newNode === parent) {
		message.setError(null, `addNode() newNode is same as parent`)
		return
	}
	let newIndex
	if (location === 'first') {
		newIndex = 0
	} else if (location === 'last') {
		newIndex = parent.element.length
	} else {
		newIndex = parent.element.indexOf(selectedNode)
		if (newIndex < 0) {
			message.setError(null, `addNode() selectedNode was not found from parent ncNode`)
			newIndex = parent.element.length
		} else if (location === 'after') {
			newIndex++
		} else if (location !== 'before') {
			message.setError(null, `addNode() location is not first, last, before of after`)
			newIndex = parent.element.length
		}
	}
	setIdAndAttribute(newNode)
	selectedNode.ncElem.vnode.el.classList.remove('nc-selected-element')
	parent.element.splice(newIndex, 0, newNode) // array.splice(start[, deleteCount[, item1[, ...]]])
	setTimeout(() => {
		selectEvent(newNode) // wait for newNode to be drawn on screen, selectEvent needs ncNode and $el
	}, 0)
}

export function deleteNode(state, node) {
	const parent = parentNode(node)
	if (parent) {
		const deleteIndex = parent.element.indexOf(node)
		if (deleteIndex < 0) {
			message.setError(null, `deleteNode(), node to delete was not found from parent`)
		} else {
			// const popWasOpen = toggleFocus(state, 'close')
			// setTimeout(() => {
			parent.element.splice(deleteIndex, 1) // array.splice(start, deleteCount)
			parent.ncElem.vnode.el.classList.remove('nc-selected-parent')
			const rowNode = parentRowNode(parent)
			rowNode && rowNode.ncElem && rowNode.ncElem.vnode.el.classList.remove('nc-selected-parent')
			// }, popWasOpen * 600) // wait for property pop to hide before deleting
		}
		/* } else {
				message.setWarning(null, `deleteNode() parent does not exist`) */
	}
}

export function cleanRecJson(rec, stripId) {
	const ret = {
		component: rec.component,
		id: (!stripId && rec.id) || undefined // layout editor id, not attribute.id
	}
	if (rec.attribute != null) ret.attribute = rec.attribute
	if (rec.text != null) ret.text = rec.text
	if (rec.comment != null) ret.comment = rec.comment
	if (rec.component === 'nc-layout-content--DISABLED-TO-PREVENT-RECURSION') {
		ret.component = 'nc-layout-content'
	}
	return ret
}

export function cleanLayoutJson(rec, stripId) {
	const rec2 = cleanRecJson(rec, stripId) // delete rec.ncElem and rec.dnd
	if (rec.element && rec.element.length > 0) {
		rec2.element = []
		for (let i = 0; i < rec.element.length; i++) {
			rec2.element[i] = cleanLayoutJson(rec.element[i], stripId)
		}
	}
	return rec2
}

// not in use:

/* let usePropertyPop = false
let prevPropertyPopElem = undefined
let propertyPopOpen = 0
let propertyPopOrigPosition = null
const popOpenTimeoutMs = 300 */

/*
function setPropertyPop(node, action) {
	if (!propertyPopOrigPosition) {
		propertyPopOrigPosition = document.getElementById('nc-element-property-popup')
		if (propertyPopOrigPosition) {
			propertyPopOrigPosition = propertyPopOrigPosition.parentElement
			const mainArea = document.getElementById('nc-layout-area')
			mainArea.addEventListener('mouseleave', popMouseLeave)
		} else {
			propertyPopOrigPosition = null
		}
	}
	const elem = node.ncElem.vnode.el
	function popMouseLeave() {
		if (prevPropertyPopElem) {
			prevPropertyPopElem.removeEventListener('click', popMouseLeave)
		}
		const pop = document.getElementById('nc-element-property-popup')
		if (pop) {
			console.debug('popMouseLeave', node)
			pop.dispatchEvent(new Event('mouseleave'))
			propertyPopOrigPosition.appendChild(pop) // move back to original location or dom element will be lost when element is destroyed - is this needed?
			propertyPopOpen = 0
		}
	}
	function popMouseEnter() {
		let pop = document.getElementById('nc-element-property-popup')
		if (pop) {
			console.debug('popMouseEnter', node)
			pop = elem.appendChild(pop) // move to selected element as child
			setTimeout(() => {
				pop.dispatchEvent(new Event('mouseenter'))
				prevPropertyPopElem = elem
				propertyPopOpen = 1
				// elem.addEventListener('click', popMouseLeave) // elem.addEventListener('mouseleave', popMouseLeave)
			}, popOpenTimeoutMs)
		}
	}
	const popWasOpen = propertyPopOpen
	if (prevPropertyPopElem) {
		// prevPropertyPopElem
		// do not show on first draw area select
		// console.debug('prevPropertyPopElem', prevPropertyPopElem, elem)
		popMouseLeave()
		if (action === 'hide') {
			prevPropertyPopElem = null
		} else if (popWasOpen && action === 'toggle') {
			prevPropertyPopElem = null
		} else if (!popWasOpen && action === 'toggle') {
			popMouseEnter()
		} else if (prevPropertyPopElem !== elem) {
			popMouseEnter() // second click on same element closes popup
		} else {
			prevPropertyPopElem = null
		}
	} else if (prevPropertyPopElem !== undefined) {
		popMouseEnter() // skip first call
	} else {
		prevPropertyPopElem = null
	}
	return popWasOpen
}
*/

/*

function fixElementArr(arr) {
	if (arr && arr.element) {
		arr.element.forEach(rec => {
			if (rec.attribute && rec.attribute.callback) {
				-- Some components need a callback that must be a function. Json can't contain real functions, so create one.
				rec.attribute.callback = function() { }
			}
		})
	}
}


import dirtyJson from '/web_modules/dirty-json'

function changeJsonValue(value) {
	let jsonVal
	try {
		jsonVal = dirtyJson.parse(value) // prevent error messages
		if (jsonVal.length === 1) {
			message.setWarning(state, 'layout json edit - Error: check that start and end quotes match')
			return
		}
	} catch (error) {
		message.setWarning(state, 'layout json edit - ' + error)
		return // wait for valid json input before setting new json value
	}
	message.clearMessage(state)

	try {
		layout.value = jsonVal
		updateHtml()
	} catch (error) {
		message.setWarning(state, 'layout json edit - ' + error)
	}
} */

/* function getNcNode(event) {
	return event.target.ncNode || event.target.parentNode.ncNode || event.target.parentNode.parentNode.ncNode // nc.dropdown needs parentNode.parentNode
} */

/*
import gsap from 'gsap'

export function onBeforeEnter(el) {
	debugger
	el.style.opacity = 0
	// el.style.height = 0
}

export function onEnter(el, done) {
	debugger
	gsap.to(el, {
		opacity: 1,
		delay: 1.1,
		onComplete: done
	})
}

export function onLeave(el, done) {
	debugger
	gsap.to(el, {
		opacity: 0,
		delay: 0.75,
		onComplete: done
	})
}
*/
