import { h, Component, createRef } from 'preact'
import htm from 'htm'
import { signal } from 'preact/signals'
const html = htm.bind(h)

export class ChecklistGroup {
	static sort(a, b) {//{{{
		if (a.Order < b.Order) return -1
		if (a.Order > b.Order) return 1
		return 0
	}//}}}
	constructor(data) {//{{{
		Object.keys(data).forEach(key => {
			if (key == 'Items')
				this.items = data[key].map(itemData => {
					let item = new ChecklistItem(itemData)
					item.checklistGroup = this
					return item
				})
			else
				this[key] = data[key]
		})
	}//}}}
	addItem(label, okCallback) {//{{{
		window._app.current.request('/node/checklist_group/item_add', {
			ChecklistGroupID: this.ID,
			Label: label,
		})
			.then(json => {
				let item = new ChecklistItem(json.Item)
				item.checklistGroup = this
				this.items.push(item)
				okCallback()
			})
			.catch(window._app.current.responseError)
		return
	}//}}}
	updateLabel(newLabel, okCallback, errCallback) {//{{{
		window._app.current.request('/node/checklist_group/label', {
			ChecklistGroupID: this.ID,
			Label: newLabel,
		})
			.then(okCallback)
			.catch(errCallback)
	}//}}}
	delete(okCallback, errCallback) {//{{{
		window._app.current.request('/node/checklist_group/delete', {
			ChecklistGroupID: this.ID,
		})
			.then(() => {
				okCallback()
			})
			.catch(errCallback)
	}//}}}
}

export class ChecklistItem {
	static sort(a, b) {//{{{
		if (a.Order < b.Order) return -1
		if (a.Order > b.Order) return 1
		return 0
	}//}}}
	constructor(data) {//{{{
		Object.keys(data).forEach(key => {
			this[key] = data[key]
		})
	}//}}}
	updateState(newState, okCallback, errCallback) {//{{{
		window._app.current.request('/node/checklist_item/state', {
			ChecklistItemID: this.ID,
			State: newState,
		})
			.then(okCallback)
			.catch(errCallback)
	}//}}}
	updateLabel(newLabel, okCallback, errCallback) {//{{{
		window._app.current.request('/node/checklist_item/label', {
			ChecklistItemID: this.ID,
			Label: newLabel,
		})
			.then(okCallback)
			.catch(errCallback)
	}//}}}
	delete(okCallback, errCallback) {//{{{
		window._app.current.request('/node/checklist_item/delete', {
			ChecklistItemID: this.ID,
		})
			.then(() => {
				this.checklistGroup.items = this.checklistGroup.items.filter(item => item.ID != this.ID)
				okCallback()
			})
			.catch(errCallback)
	}//}}}
	move(to, okCallback) {//{{{
		window._app.current.request('/node/checklist_item/move', {
			ChecklistItemID: this.ID,
			AfterItemID: to.ID,
		})
			.then(okCallback)
			.catch(_app.current.responseError)
	}//}}}
}

export class Checklist extends Component {
	constructor() {//{{{
		super()
		this.edit = signal(false)
		this.dragItemSource = null
		this.dragItemTarget = null
		this.groupElements = {}
		this.state = {
			confirmDeletion: true,
			continueAddingItems: true,
		}
		window._checklist = this
	}//}}}
	render({ ui, groups }, { confirmDeletion, continueAddingItems }) {//{{{
		this.groupElements = {}
		if (groups.length == 0 && !ui.node.value.ShowChecklist.value)
			return

		if (typeof groups.sort != 'function')
			groups = []

		groups.sort(ChecklistGroup.sort)
		let groupElements = groups.map(group => {
			this.groupElements[group.ID] = createRef()
			return html`<${ChecklistGroupElement} ref=${this.groupElements[group.ID]} key="group-${group.ID}" ui=${this} group=${group} />`
		})

		let edit = 'edit-list-gray.svg'
		let confirmDeletionEl = ''
		if (this.edit.value) {
			edit = 'edit-list.svg'
			confirmDeletionEl = html`
				<div>
					<input type="checkbox" id="confirm-checklist-delete" checked=${confirmDeletion} onchange=${() => this.setState({ confirmDeletion: !confirmDeletion })} />
					<label for="confirm-checklist-delete">Confirm checklist deletion</label>
				</div>
				<div>
					<input type="checkbox" id="continue-adding-items" checked=${continueAddingItems} onchange=${() => this.setState({ continueAddingItems: !continueAddingItems })} />
					<label for="continue-adding-items">Continue adding items</label>
				</div>
			`
		}

		let addGroup = () => {
			if (this.edit.value)
				return html`<img src="/images/${_VERSION}/add-gray.svg" onclick=${() => this.addGroup()} />`
		}

		return html`
			<div id="checklist">
				<div class="header">
					<h1>Checklist</h1>
					<img src="/images/${_VERSION}/${edit}" onclick=${() => this.toggleEdit()} />
					<${addGroup} />
				</div>
				${confirmDeletionEl}
				${groupElements}
			</div>
		`
	}//}}}

	toggleEdit() {//{{{
		this.edit.value = !this.edit.value
	}//}}}
	addGroup() {//{{{
		let label = prompt("Create a new group")
		if (label === null)
			return
		label = label.trim()
		if (label == '')
			return

		window._app.current.request('/node/checklist_group/add', {
			NodeID: window._app.current.nodeUI.current.node.value.ID,
			Label: label,
		})
			.then(json => {
				let group = new ChecklistGroup(json.Group)
				this.props.groups.push(group)
				this.forceUpdate()
			})
			.catch(window._app.current.responseError)
		return
	}//}}}
	dragTarget(target) {//{{{
		if (this.dragItemTarget)
			this.dragItemTarget.setDragTarget(false)
		this.dragItemTarget = target
		target.setDragTarget(true)
	}//}}}
	dragReset() {//{{{
		if (this.dragItemTarget) {
			this.dragItemTarget.setDragTarget(false)
			this.dragItemTarget = null
		}
	}//}}}
}

class InputElement extends Component {
	render({ placeholder, label }) {//{{{
		return html`
			<dialog id="input-text">
				<div class="container">
					<div class="label">${label}</div>
					<input id="input-text-el" type="text" placeholder=${placeholder} />
					<div class="buttons">
						<div></div>
						<button onclick=${()=>this.cancel()}>Cancel</button>
						<button onclick=${()=>this.ok()}>OK</button>
					</div>
				</div>
			</dialog>
		`
	}//}}}
	componentDidMount() {//{{{
		const dlg = document.getElementById('input-text')
		const input = document.getElementById('input-text-el')
		dlg.showModal()
		dlg.addEventListener("keydown", evt => this.keyhandler(evt))
		input.addEventListener("keydown", evt => this.keyhandler(evt))
		input.focus()
	}//}}}
	ok() {//{{{
		const input = document.getElementById('input-text-el')
		this.props.callback(true, input.value)
	}//}}}
	cancel() {//{{{
		this.props.callback(false)
	}//}}}
	keyhandler(evt) {//{{{
		let handled = true
		switch (evt.key) {
			case 'Enter':
				this.ok()
				break;

			case 'Escape':
				this.cancel()
				break;
			default:
				handled = false
		}
		if (handled) {
			evt.stopPropagation()
			evt.preventDefault()
		}
	}//}}}
}

class ChecklistGroupElement extends Component {
	constructor() {//{{{
		super()
		this.label = createRef()
		this.addingItem = signal(false)
	}//}}}
	render({ ui, group }) {//{{{
		let items = ({ ui, group }) =>
			group.items
				.sort(ChecklistItem.sort)
				.map(item => html`<${ChecklistItemElement} key="item-${item.ID}" ui=${ui} group=${this} item=${item} />`)

		let label = () => html`<div class="label" style="cursor: pointer" ref=${this.label} onclick=${() => this.editLabel()}>${group.Label}</div>`
		let addItem = () => {
			if (this.addingItem.value)
				return html`<${InputElement} label="New item" callback=${(ok, val) => this.addItem(ok, val)} />`
		}

		return html`
			<${addItem} />
			<div class="checklist-group-container">
				<div class="checklist-group ${ui.edit.value ? 'edit' : ''}">
					<div class="reorder" style="cursor: grab">☰</div>
					<img src="/images/${_VERSION}/trashcan.svg" onclick=${() => this.delete()} />
					<${label} />
					<img src="/images/${_VERSION}/add-gray.svg" onclick=${() => this.addingItem.value = true} />
				</div>
				<${items} ui=${ui} group=${group} />
			</div>
		`
	}//}}}
	addItem(ok, label) {//{{{
		if (!ok) {
			this.addingItem.value = false
			return
		}

		label = label.trim()
		if (label == '') {
			this.addingItem.value = false
			return
		}

		this.props.group.addItem(label, () => {
			this.forceUpdate()
		})

		if (!this.props.ui.state.continueAddingItems)
			this.addingItem.value = false
	}//}}}
	editLabel() {//{{{
		let label = prompt('Edit label', this.props.group.Label)
		if (label === null)
			return

		label = label.trim()
		if (label == '') {
			alert(`A label can't be empty.`)
			return
		}

		this.label.current.classList.remove('error')
		this.props.group.updateLabel(label, () => {
			this.props.group.Label = label
			this.label.current.innerHTML = label
			this.label.current.classList.add('ok')
			this.forceUpdate()
			setTimeout(() => this.label.current.classList.remove('ok'), 500)
		}, () => {
			this.label.current.classList.add('error')
		})

	}//}}}
	delete() {//{{{
		if (this.props.ui.state.confirmDeletion) {
			if (!confirm(`Delete '${this.props.group.Label}'?`))
				return
		}

		this.props.group.delete(() => {
			this.props.ui.props.groups = this.props.ui.props.groups.filter(g => g.ID != this.props.group.ID)
			this.props.ui.forceUpdate()
		}, err => {
			console.log(err)
			console.log('error')
		})
	}//}}}
}

class ChecklistItemElement extends Component {
	constructor(props) {//{{{
		super(props)
		this.state = {
			checked: props.item.Checked,
			dragTarget: false,
		}
		this.checkbox = createRef()
		this.label = createRef()
	}//}}}
	render({ ui, item }, { checked, dragTarget }) {//{{{
		let checkbox = () => {
			if (ui.edit.value)
				return html`<label ref=${this.label} onclick=${() => this.editLabel()} style="cursor: pointer">${item.Label}</label>`
			else
				return html`
					<input type="checkbox" ref=${this.checkbox} key="checkbox-${item.ID}" id="checkbox-${item.ID}" checked=${checked} onchange=${evt => this.update(evt.target.checked)} />
					<label ref=${this.label} for="checkbox-${item.ID}">${item.Label}</label>
			`
		}
		return html`
			<div class="checklist-item ${checked ? 'checked' : ''} ${ui.edit.value ? 'edit' : ''} ${dragTarget ? 'drag-target' : ''}" draggable=true>
				<div class="reorder" style="user-select: none;">☰</div>
				<img src="/images/${_VERSION}/trashcan.svg" onclick=${() => this.delete()} />
				<${checkbox} />
			</div>
		`
	}//}}}
	componentDidMount() {//{{{
		this.base.addEventListener('dragstart', evt => this.dragStart(evt))
		this.base.addEventListener('dragend', () => this.dragEnd())
		this.base.addEventListener('dragenter', evt => this.dragEnter(evt))
	}//}}}

	update(checked) {//{{{
		this.setState({ checked })
		this.checkbox.current.classList.remove('error')
		this.props.item.updateState(checked, () => {
			this.checkbox.current.classList.add('ok')
			setTimeout(() => this.checkbox.current.classList.remove('ok'), 500)
		}, () => {
			this.checkbox.current.classList.add('error')
		})
	}//}}}
	editLabel() {//{{{
		let label = prompt('Edit label', this.props.item.Label)
		if (label === null)
			return

		label = label.trim()
		if (label == '') {
			alert(`A label can't be empty.`)
			return
		}

		this.label.current.classList.remove('error')
		this.props.item.updateLabel(label, () => {
			this.props.item.Label = label
			this.label.current.innerHTML = label
			this.label.current.classList.add('ok')
			setTimeout(() => this.label.current.classList.remove('ok'), 500)
		}, () => {
			this.label.current.classList.add('error')
		})

	}//}}}
	delete() {//{{{
		if (this.props.ui.state.confirmDeletion) {
			if (!confirm(`Delete '${this.props.item.Label}'?`))
				return
		}

		this.props.item.delete(() => {
			this.props.group.forceUpdate()
		}, err => {
			console.log(err)
			console.log('error')
		})
	}//}}}

	setDragTarget(state) {//{{{
		this.setState({ dragTarget: state })
	}//}}}
	dragStart(evt) {//{{{
		// Shouldn't be needed, but in case the previous drag was bungled up, we reset.
		this.props.ui.dragReset()
		this.props.ui.dragItemSource = this

		const img = new Image();
		evt.dataTransfer.setDragImage(img, 10, 10);
	}//}}}
	dragEnter(evt) {//{{{
		evt.preventDefault()
		this.props.ui.dragTarget(this)
	}//}}}
	dragEnd() {//{{{
		let groups = this.props.ui.props.groups
		let from = this.props.ui.dragItemSource.props.item
		let to = this.props.ui.dragItemTarget.props.item

		this.props.ui.dragReset()

		if (from.ID == to.ID)
			return

		let fromGroup = groups.find(g => g.ID == from.GroupID)
		let toGroup = groups.find(g => g.ID == to.GroupID)


		from.Order = to.Order
		from.GroupID = toGroup.ID
		toGroup.items.forEach(i => {
			if (i.ID == from.ID)
				return
			if (i.Order <= to.Order)
				i.Order--
		})

		if (fromGroup.ID != toGroup.ID) {
			fromGroup.items = fromGroup.items.filter(i => i.ID != from.ID)
			toGroup.items.push(from)
		}

		this.props.ui.groupElements[fromGroup.ID].current.forceUpdate()
		this.props.ui.groupElements[toGroup.ID].current.forceUpdate()

		from.move(to, () => {})
	}//}}}
}

// vim: foldmethod=marker
