import { ROOT_NODE, ORPHANED_NODE, DELETED_NODE, Node } from 'node_store'
import { CustomHTMLElement } from './lib/custom_html_element.mjs'
import { Color, Solver } from './lib/css_colorize.mjs'

// TreeExpandedHandler is responsible for collapsing or expanding
// the node tree, wide view or narrow "mobile" view.
class TreeExpansionHandler {// {{{
	constructor() {
		this.isNarrow = false
		this.initializeMediaHandler()
		this.initializeBusEvents()
	}

	initializeBusEvents() {
		_mbus.subscribe('TREE_EXPANSION', ({ detail }) => {
			// When a node is selected on the screen and the screen
			// is narrow the tree is automatically hidden.
			//
			// Can't always hide the tree automatically when a node
			// is selected since the wide mode shows the tree as standard.
			if (detail.data?.when == 'narrow' && !this.isNarrow)
				return

			this.treeExpansion(detail.data?.expand)
		})
	}

	initializeMediaHandler() {
		const query = window.matchMedia('(max-width: 800px)')
		query.addEventListener('change', event => this.screenNarrowHandler(event))

		// Run once to set initial state, instead of needing to toggle state.
		this.screenNarrowHandler(query)
	}

	// When screen becomes narrow, the tree is automatically hidden.
	// Primary purpose is to read content, not browse, which is why
	// the tree is hidden as standard.
	screenNarrowHandler(event) {
		this.isNarrow = event.matches

		if (this.isNarrow)
			this.treeExpansion(false)
		else
			this.treeExpansion(true)
	}

	treeExpansion(expanded) {
		const notes2 = document.getElementById('notes2')

		if (expanded) {
			notes2.classList.remove('hide-tree')
			notes2.classList.add('show-tree')
		} else {
			notes2.classList.add('hide-tree')
			notes2.classList.remove('show-tree')
		}
	}
}// }}}

export class N2Sidebar extends CustomHTMLElement {
	static {// {{{
		this.tmpl = document.createElement('template')
		this.tmpl.innerHTML = `
			<style>
				n2-sidebar {
					#logo {
						display: grid;
						grid-template-columns: 1fr min-content;
						align-items: center;
						justify-items: start;
						cursor: pointer;
						padding: 16px;
						border-bottom: 1px solid var(--line-color);

						img:first-child {
							height: 24px;
							margin-right: 8px;
						}
					}

					.icons {
						display: flex;
						justify-content: start;
						margin-top: 16px;
						gap: 8px 16px;
						padding-left: 16px;
						padding-bottom: 16px;
						border-bottom: 1px solid var(--line-color);

						img {
							cursor: pointer;
						}
					}


					.el-hide-tree {
						font-size: 1.25em;
						font-weight: bold;
						cursor: pointer;
						margin-left: 16px;
					}
				}
			</style>
			<div id="logo">
				<img data-el="logo" src="/images/${_VERSION}/logo.svg" />
				<div data-el="hide-tree">&lt;</div>
			</div>
			<div class="icons">
				<img data-el="sync"     class='sync colorize'     src="/images/${_VERSION}/icon_refresh.svg"  />
				<img data-el="storage"  class='storage colorize'  src="/images/${_VERSION}/icon_storage.svg"  />
				<img data-el="search"   class='search colorize'   src="/images/${_VERSION}/icon_search.svg" style="height: 22px" />
				<img data-el="settings" class='settings colorize' src="/images/${_VERSION}/icon_settings.svg" />
			</div>
			<div data-el="sync-status">
				<img data-el="sync-icon" class="colorize">
				<div data-el="sync-label"></div>
				<progress data-el="sync-progress" min="0" max="100" value="0"></progress>
			</div>
			<div data-el="treenodes"></div>
		`
	}// }}}

	constructor() {// {{{
		super()

		this.id = 'tree-nodes'
		this.tabIndex = 0

		this.treeNodeComponents = {}
		this.expandedNodes = {} // keyed on UUID
		this.selectedNode = null
		this.rendered = false
		this.nodesDownloadCount = 0
		this.nodesUploadCount = 0

		new TreeExpansionHandler()

		this.addEventListener('keydown', event => this.keyHandler(event))
		this.elSearch.addEventListener('click', () => _mbus.dispatch('op-search'))
		this.elSync.addEventListener('click', () => _sync.run())
		this.elStorage.addEventListener('click', () => _mbus.dispatch('SHOW_PAGE', { page: 'storage' }))
		this.elLogo.addEventListener('click', () => _app.goToNode(ROOT_NODE, false, false))
		this.elSettings.addEventListener('click', () => _mbus.dispatch('SHOW_PAGE', { page: 'preferences' }))
		this.elHideTree.addEventListener('click', event => {
			event.stopPropagation()
			_mbus.dispatch('TREE_EXPANSION', { expand: false })
		})

		_mbus.subscribe('NODE_MODIFIED', ({ detail }) => {
			const node = detail.data.node
			const treenode = this.treeNodeComponents[node.get('UUID')]

			if (!treenode)
				return

			treenode.node = node
			treenode.render(true)
		})

		// SYNC_DOWNLOAD_COUNT and SYNC_UPLOAD_COUNT will be sent first before progress messages comes for either.
		_mbus.subscribe('SYNC_DOWNLOAD_COUNT', event => this.nodesDownloadCount = event.detail.data.count)
		_mbus.subscribe('SYNC_UPLOAD_COUNT', event => this.nodesUploadCount = event.detail.data.count)
		_mbus.subscribe('SYNC_DOWNLOADED', event => {
			this.elSyncIcon.classList.add('colorize')
			this.elSyncIcon.setAttribute('src', `/images/${_VERSION}/icon_node_download.svg`)
			this.elSyncProgress.setAttribute('max', this.nodesDownloadCount)
			this.elSyncProgress.value = event.detail.data.handled
		})
		_mbus.subscribe('SYNC_UPLOADED', event => {
			this.elSyncIcon.classList.add('colorize')
			this.elSyncIcon.setAttribute('src', `/images/${_VERSION}/icon_node_upload.svg`)
			this.elSyncProgress.setAttribute('max', this.nodesUploadCount)
			this.elSyncProgress.value = event.detail.data.count
		})
		_mbus.subscribe('SYNC_DONE', () => {
			this.elSyncIcon.classList.remove('colorize')
			this.elSyncIcon.setAttribute('src', `/images/${_VERSION}/icon_sync_done.svg`)
		})

		_mbus.subscribe('FILE_SYNC', event => {
			const { direction, name } = event.detail.data
			this.elSyncIcon.setAttribute('src', `/images/${_VERSION}/icon_file_${direction}.svg`)
			this.elSyncLabel.innerText = name
		})

		_mbus.subscribe('FILE_DOWNLOAD_PROGRESS', event => {
			const { uuid, size, downloaded, percent, done } = event.detail.data
			console.log(percent)

			if (percent) {
				this.elSyncProgress.value = percent
			}

			if (done) {
				this.elSyncLabel.innerText = ''
				this.elSyncProgress.value = 0
			}
		})

		/* XXX - set color */
		let color = new Color(0x80, 0x00, 0x33)
		let solver = new Solver(color)
		let result = solver.solve()
		console.log(result.filter)
	}// }}}
	async render() {// {{{
		if (this.rendered)
			alert('Tree should only be rendered once.')

		this.expandedNodes[ROOT_NODE] = true
		const startnode = await nodeStore.get(ROOT_NODE)
		const starttreenode = new N2TreeNode(this, startnode, null)

		const deletednode = await nodeStore.get(DELETED_NODE)
		const deletedtreenode = new SpecialNodeDeleted(this, deletednode, null)

		const orphanednode = await nodeStore.get(ORPHANED_NODE)
		const orphanedtreenode = new SpecialNodeOrphaned(this, orphanednode, null)

		startnode._sibling_after = deletednode
		deletednode._sibling_before = startnode

		deletednode._sibling_after = orphanednode
		orphanednode._sibling_before = deletednode

		this.treeNodeComponents[startnode.UUID] = starttreenode
		this.treeNodeComponents[deletednode.UUID] = deletedtreenode
		this.treeNodeComponents[orphanednode.UUID] = orphanedtreenode

		this.elTreenodes.appendChild(await starttreenode.render())
		this.elTreenodes.appendChild(await deletedtreenode.render())
		this.elTreenodes.appendChild(await orphanedtreenode.render())

		// Notify the application that the initial tree is rendered (with children)
		// and that initial node selection can take place. App will check URL to
		// select the correct one.
		_mbus.dispatch('TREE_RENDERED')

		this.rendered = true
		return this
	}// }}}
	reset() {// {{{
		this.treeNodeComponents = {}
		this.rendered = false
		this.elTreenodes.replaceChildren()
		this.render()
	}// }}}
	getNodeExpanded(UUID) {//{{{
		if (this.expandedNodes[UUID] === undefined)
			this.expandedNodes[UUID] = false
		return this.expandedNodes[UUID]
	}//}}}
	async setNodeExpanded(node, value) {//{{{
		let expanded = this.expandedNodes[node.UUID]
		if (expanded === undefined) {
			this.expandedNodes[node.UUID] = false
			expanded = false
		}

		if (expanded === value)
			return

		this.expandedNodes[node.UUID] = value
		_mbus.dispatch(`NODE_EXPAND_${node.UUID}`, value)
	}//}}}
	setSelected(node, dontExpand) {//{{{
		if (node === undefined)
			return

		// The previously selected node, if any, needs to be rerendered
		// to not retain its 'selected' class.
		const prevUUID = this.selectedNode?.UUID
		this.selectedNode = node
		if (prevUUID)
			this.treeNodeComponents[prevUUID]?.render(true)

		// And now the newly selected node is rerendered.
		this.treeNodeComponents[node.UUID]?.render(true)

		if (!dontExpand)
			this.setNodeExpanded(node, true)
	}//}}}
	isSelected(node) {//{{{
		return this.selectedNode?.UUID === node.UUID
	}//}}}
	getTreeNode(uuid) {// {{{
		return this.treeNodeComponents[uuid]
	}// }}}

	async keyHandler(event) {//{{{
		let handled = true
		const n = this.selectedNode
		const Space = ' '

		// This handler would otherwise react to stuff like Ctrl+L.
		if (event.ctrlKey || event.altKey)
			return

		switch (event.key) {
			// Space and enter is toggling expansion.
			// Holding shift down does it recursively.
			case Space:
			case 'Enter':
				const expanded = this.getNodeExpanded(n.UUID)
				if (event.shiftKey) {
					this.recursiveExpand(n, !expanded)
				} else {
					this.setNodeExpanded(n, !expanded)
				}
				break

			case 'Home':
				this.navigateTop()
				break

			case 'End':
				this.navigateBottom()
				break

			case 'ArrowDown':
				await this.navigateDown(this.selectedNode)
				break

			case 'ArrowUp':
				await this.navigateUp(this.selectedNode)
				break

			case 'ArrowLeft':
				await this.navigateLeft(this.selectedNode)
				break

			case 'ArrowRight':
				await this.navigateRight(this.selectedNode)
				break

			default:
				handled = false
		}

		if (handled) {
			event.preventDefault()
			event.stopPropagation()
		}
	}//}}}
	async navigateLeft(n) {//{{{
		if (n === null || n === undefined || n.UUID == ROOT_NODE)
			return

		const expanded = this.getNodeExpanded(n.UUID)
		if (expanded && n.hasChildren() && n.UUID !== ROOT_NODE) {
			this.setNodeExpanded(n, false)
			return
		}

		if (n.isFirstSibling()) {
			_mbus.dispatch("GO_TO_NODE", { nodeUUID: n.getParent()?.UUID, dontPush: false, dontExpand: true })
			return
		}

		const siblingBefore = n.getSiblingBefore()
		const siblingExpanded = this.getNodeExpanded(siblingBefore?.UUID)
		if (siblingBefore !== null && siblingExpanded && siblingBefore.hasChildren()) {
			const siblingAbove = this.getLastExpandedNode(siblingBefore)
			_mbus.dispatch("GO_TO_NODE", { nodeUUID: siblingAbove?.UUID, dontPush: false, dontExpand: true })
			return
		}

		_mbus.dispatch("GO_TO_NODE", { nodeUUID: n.getSiblingBefore()?.UUID, dontPush: false, dontExpand: true })
	}//}}}
	async navigateRight(n) {//{{{
		if (n === null || n === undefined)
			return

		const siblingAfter = n.getSiblingAfter()
		const expanded = this.getNodeExpanded(n.UUID)

		if (!expanded && n.hasChildren()) {
			this.setNodeExpanded(n, true)
			return
		}

		if (expanded && n.hasChildren()) {
			_mbus.dispatch("GO_TO_NODE", { nodeUUID: n.Children[0]?.UUID, dontPush: false, dontExpand: true })
			return
		}

		if (n.isLastSibling()) {
			const nextNode = this.getParentWithNextSibling(n)
			_mbus.dispatch("GO_TO_NODE", { nodeUUID: nextNode?.UUID, dontPush: false, dontExpand: true })
			return
		}

		_mbus.dispatch("GO_TO_NODE", { nodeUUID: n.getSiblingAfter()?.UUID, dontPush: false, dontExpand: true })
	}//}}}
	async navigateUp(n) {//{{{
		if (n === null || n === undefined || n.UUID == ROOT_NODE)
			return

		let parent = null
		const siblingBefore = n.getSiblingBefore()
		let siblingExpanded = false
		if (siblingBefore !== null)
			siblingExpanded = this.getNodeExpanded(siblingBefore.UUID)

		if (n.isFirstSibling()) {
			parent = n.getParent()
			_mbus.dispatch("GO_TO_NODE", { nodeUUID: parent?.UUID, dontPush: false, dontExpand: true })
			return
		}

		if (siblingBefore !== null && siblingExpanded && siblingBefore.hasChildren()) {
			const nodeVisuallyAbove = this.getLastExpandedNode(siblingBefore)
			_mbus.dispatch("GO_TO_NODE", { nodeUUID: nodeVisuallyAbove.UUID, dontPush: false, dontExpand: true })
			return
		}

		if (siblingBefore) {
			_mbus.dispatch("GO_TO_NODE", { nodeUUID: siblingBefore.UUID, dontPush: false, dontExpand: true })
			return
		}
	}//}}}
	async navigateDown(n) {//{{{
		if (n === null || n === undefined)
			return

		const nodeExpanded = this.getNodeExpanded(n.UUID)

		// Last node, not expanded, so it matters not whether it has children or not.
		// Traverse upward to nearest parent with next sibling.
		if (!nodeExpanded && n.isLastSibling()) {
			const wantedNode = this.getParentWithNextSibling(n)
			_mbus.dispatch("GO_TO_NODE", { nodeUUID: wantedNode?.UUID, dontPush: false, dontExpand: true })
			return
		}

		if (nodeExpanded && n.isLastSibling() && !n.hasChildren()) {
			const wantedNode = this.getParentWithNextSibling(n)
			_mbus.dispatch("GO_TO_NODE", { nodeUUID: wantedNode?.UUID, dontPush: false, dontExpand: true })
			return
		}

		// Node not expanded. Go to this node's next sibling.
		// GoToNode will abort if given null.
		if (!nodeExpanded || !n.hasChildren()) {
			_mbus.dispatch("GO_TO_NODE", { nodeUUID: n.getSiblingAfter()?.UUID, dontPush: false, dontExpand: true })
			return
		}

		// Node is expanded.
		// Children will be visually beneath this node, if any.
		if (nodeExpanded && n.hasChildren()) {
			_mbus.dispatch("GO_TO_NODE", { nodeUUID: n.Children[0].UUID, dontPush: false, dontExpand: true })
			return
		}
	}//}}}
	async navigateTop() {//{{{
		const root = await nodeStore.get(ROOT_NODE)
		_mbus.dispatch("GO_TO_NODE", { nodeUUID: root.UUID, dontPush: false, dontExpand: true })
	}//}}}
	async navigateBottom() {//{{{
		const orphaned = await nodeStore.get(ORPHANED_NODE)

		if (!orphaned.hasChildren() || this.getNodeExpanded(orphaned.UUID)) {
			_mbus.dispatch("GO_TO_NODE", { nodeUUID: orphaned.UUID, dontPush: false, dontExpand: true })
			return
		}

		/* TODO - fix this when orphaned nodes are implemented.
		const toplevel = orphaned.Children[orphaned.Children.length - 1]
		const toplevelExpanded = this.getNodeExpanded(toplevel?.UUID)

		if (toplevelExpanded) {
			const lastnode = this.getLastExpandedNode(toplevel)
			_mbus.dispatch("GO_TO_NODE", { nodeUUID: lastnode?.UUID, dontPush: false, dontExpand: true })
		} else
			_mbus.dispatch("GO_TO_NODE", { nodeUUID: orphaned.Children[orphaned.Children.length - 1]?.UUID, dontPush: false, dontExpand: true })
		*/
	}//}}}

	getParentWithNextSibling(node) {//{{{
		let currNode = node
		while (currNode !== null && currNode.UUID !== ROOT_NODE && currNode.getSiblingAfter() === null) {
			currNode = currNode.getParent()
		}
		return currNode?.getSiblingAfter()
	}//}}}
	getLastExpandedNode(node) {//{{{
		let currNode = node
		while (this.getNodeExpanded(currNode.UUID) && currNode.hasChildren()) {
			currNode = currNode.Children[currNode.Children.length - 1]
		}
		return currNode
	}//}}}
	async recursiveExpand(node, state) {//{{{
		if (state)
			await this.setNodeExpanded(node, true)

		// An expanded node needs to have its children fetched.
		if (!node.hasFetchedChildren())
			await node.fetchChildren()

		for (const child of node.Children)
			await this.recursiveExpand(child, state)

		if (!state)
			await this.setNodeExpanded(node, false)
	}//}}}
	async makeVisible(node, providedAncestors, dontExpand) {// {{{
		const treenode = this.treeNodeComponents[node.UUID]

		if (!dontExpand) {
			const ancestors = providedAncestors || await nodeStore.getNodeAncestry(node)
			for (const ancestor of ancestors.reverse()) {
				this.setNodeExpanded(ancestor, true)
			}
		}

		treenode?.scrollIntoView({ block: 'nearest' })
	}// }}}
}

export class N2TreeNode extends CustomHTMLElement {
	static DRAG_ICON = new Image()
	static DRAG_ICON_OK = new Image()

	static {// {{{
		N2TreeNode.DRAG_ICON.src = `/images/${_VERSION}/leaf.svg`
		N2TreeNode.DRAG_ICON_OK.src = `/images/${_VERSION}/expanded.svg`

		this.tmpl = document.createElement('template')
		this.tmpl.innerHTML = `
			<style>
				n2-sidebar:focus-within {
					n2-specialnodedeleted > .el-name,
					n2-specialnodeorphaned > .el-name,
					n2-treenode > .el-name {
						&.selected {
							span {
								position:relative;
							}
							span:before {
								position: absolute;
								content: '';
								top: -8px;
								left: -36px;
								right: -8px;
								bottom: -4px;
								z-index: -1;

								background-color: #f8f8f8;
								border: 1px solid #ddd;
								border-radius: 4px;
							}
						}
					}
				}

				n2-treenode {
					& > .el-name {
						white-space: nowrap;
						width: min-content;
					}

					&.drag-source {
						& > .el-name {
							position: relative;
						}

						& > .el-name:after {
							position: absolute;
							content: url('/images/${_VERSION}/icon_drag_source.svg');
							filter: var(--colorize);
							top: -1px;
							right: -24px;
						}
					}

					&.drag-target {
						position: relative;

						& > .el-name {
							anchor-name: --name;
						}

						& > .el-name:after {
							content: '';
							position: absolute;

							border: 2px dashed #888;

							top: calc(anchor(--name top) - 12px);
							right: calc(anchor(--name right) - 8px);
							bottom: calc(anchor(--name bottom) - 8px);
							left: calc(anchor(--name left) - 40px);

							pointer-events: none;
						}

						& > .el-drag-icon {
							display: block;
							top: 0px;
							left: 0px;
							z-index: 16384;
						}
					}
				}
			</style>

			<div data-el="expand-toggle" class="expand-toggle">
				<img data-el="expand" draggable="false">
			</div>
			<div data-el="name" class="name"><span></span></div>
			<div data-el="children" class="children"></div>
		`
	}// }}}

	constructor(sidebar, node, parent) {//{{{
		super()
		this.setAttribute('draggable', 'true')
		this.classList.add('node')

		this.sidebar = sidebar
		this.node = node
		this.parent = parent

		this.children_populated = false
		this.rendered = false
		this.dragNode = null

		this.elExpandToggle.addEventListener('click', event => {
			if (this.node.hasChildren())
				this.expandNode(event)
			else
				_mbus.dispatch('TREE_NODE_SELECTED', this.node)
		})
		this.elName.addEventListener('click', () => _mbus.dispatch('TREE_NODE_SELECTED', this.node))

		_mbus.subscribe(`NODE_EXPAND_${node.UUID}`, _state => {
			this.render(true)
		})

		// Drag-and-dropping of nodes
		this.addEventListener('dragstart', event => this.dragStart(event))
		this.addEventListener('dragend', event => this.dragEnd(event))
		this.addEventListener('dragover', event => this.dragOver(event))
		this.addEventListener('drop', event => this.dragDrop(event))
		this.elName.addEventListener('dragenter', event => this.dragEnter(event))
		this.elName.addEventListener('dragleave', event => this.dragLeave(event))
	}// }}}

	dragStart(e) {// {{{
		if (this.node.isModified()) {
			alert('Save note before moving it.')
			e.stopPropagation()
			e.preventDefault()
			return
		}

		this.classList.add('drag-source')
		const blankPixel = new Image()
		blankPixel.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
		e.dataTransfer.setDragImage(blankPixel, 0, 0)
		e.dataTransfer.allowedEffects = 'none'
		e.stopPropagation()
		_app.dragIcon.setSource(this)
		_app.dragIcon.start()
	}// }}}
	dragEnd(e) {// {{{
		this.classList.remove('drag-source')
		_app.dragIcon.end()
		e.stopPropagation()
	}// }}}
	dragOver(e) {// {{{
		e.dataTransfer.dropEffect = 'move'
		e.preventDefault()
	}// }}}
	async dragDrop(e) {// {{{
		try {
			e.stopPropagation()
			const sourceNode = _app.dragIcon.getSource()

			// Abort if user drops the node back on itself.
			if (sourceNode.node.UUID === this.node.UUID)
				return

			await _app.moveNode(sourceNode.node, this.node.UUID)

			_app.sidebar.setNodeExpanded(this, true)
			await this.render(true, true)
			await sourceNode.render(true, true)
		} catch (e) {
			console.error(e)
			alert(e)
		} finally {
			this.dragLeave(e)
		}
	}// }}}
	dragEnter(e) {// {{{
		const targetNode = e.target.closest('n2-treenode')
		if (targetNode.classList.contains('drag-source'))
			return
		e.stopPropagation()
		_app.dragIcon.icon('ok')
		this.classList.add('drag-target')
	}// }}}
	dragLeave(e) {// {{{
		e.stopPropagation()
		e.dataTransfer.dropEffect = 'none'
		e.dataTransfer.setDragImage(N2TreeNode.DRAG_ICON, -16, 8)
		_app.dragIcon.icon('')
		this.classList.remove('drag-target')
	}// }}}

	async expandNode(event) {// {{{
		const expanded = _app.sidebar.getNodeExpanded(this.node.UUID)

		if (event.shiftKey) {
			_app.sidebar.recursiveExpand(this.node, !expanded)
		} else {
			_app.sidebar.setNodeExpanded(this.node, !expanded)
		}
	}// }}}
	async fetchChildren(force_fetch) {//{{{
		if (this.children_populated && !force_fetch)
			return

		await this.node.fetchChildren()
		this.children_populated = true
	}//}}}
	async render(force_update, force_refetch_children) {//{{{
		if (this.rendered && force_update !== true)
			return this

		if (this.sidebar.getNodeExpanded(this.node.UUID) || force_refetch_children)
			await this.fetchChildren(force_refetch_children)

		// Update the name and selected status.
		this.elName.querySelector('span').innerText = this.node.get('Name')

		if (this.sidebar.isSelected(this.node))
			this.elName.classList.add('selected')
		else
			this.elName.classList.remove('selected')

		// Update expansion state
		const expanded = this.node.hasChildren() && this.sidebar.getNodeExpanded(this.node.UUID)
		if (expanded) {
			this.elChildren.classList.add('expanded')
			this.elChildren.classList.remove('collapsed')
		} else {
			this.elChildren.classList.remove('expanded')
			this.elChildren.classList.add('collapsed')
		}

		// The expand icon <img> is only changed to not get a flickering when re-rendering.
		if (this.node.UUID === ROOT_NODE)
			this.setImgSrc(this.elExpand, `/images/${window._VERSION}/icon_home.svg`)

		else if (this.node.UUID === DELETED_NODE) {
			this.setImgSrc(this.elExpand, `/images/${window._VERSION}/leaf_deleted.svg`)
			this.elExpand.classList.add('deleted')
		}

		else if (this.node.UUID === ORPHANED_NODE) {
			this.setImgSrc(this.elExpand, `/images/${window._VERSION}/leaf_orphaned.svg`)
			this.elExpand.classList.add('deleted')
		}

		else if (!this.node.hasChildren())
			this.setImgSrc(this.elExpand, `/images/${window._VERSION}/leaf.svg`)
		else if (this.sidebar.getNodeExpanded(this.node.UUID))
			this.setImgSrc(this.elExpand, `/images/${window._VERSION}/expanded.svg`)
		else
			this.setImgSrc(this.elExpand, `/images/${window._VERSION}/collapsed.svg`)

		// Should children be rendered?
		let children = []
		if (expanded)
			children = this.node.Children.map(node => {
				let treenode = this.sidebar.treeNodeComponents[node.UUID]
				if (treenode === undefined) {
					treenode = new N2TreeNode(this.sidebar, node, this)
					this.sidebar.treeNodeComponents[node.UUID] = treenode
				}
				return treenode
			})

		const renderedChildren = []
		for (const c of children)
			renderedChildren.push(await c.render())
		this.elChildren.replaceChildren(...renderedChildren)

		this.rendered = true
		return this
	}//}}}

	setImgSrc(img, newSrc) {// {{{
		if (img.getAttribute('src') === newSrc)
			return
		img.setAttribute('src', newSrc)
	}// }}}
}

class SpecialNodeDeleted extends N2TreeNode {
	constructor(sidebar, node, parent) {//{{{
		super(sidebar, node, parent)
		this.removeAttribute('draggable')
	}//}}}
}

class SpecialNodeOrphaned extends N2TreeNode {
	constructor(sidebar, node, parent) {//{{{
		super(sidebar, node, parent)
		this.removeAttribute('draggable')
	}//}}}
}

customElements.define('n2-sidebar', N2Sidebar)
customElements.define('n2-treenode', N2TreeNode)
customElements.define('n2-specialnodedeleted', SpecialNodeDeleted)
customElements.define('n2-specialnodeorphaned', SpecialNodeOrphaned)

// vim: foldmethod=marker
