<template>
	<section
		class="project-kanban"
		:class="kanbanClass"
	>
		<div v-if="!project.lists.length" class="project-view-empty">
			<ProjectKanbanEmptyGraphic />
			<h2>
				Organize your tasks
			</h2>
			<p>
				Introduce a kanban to your project and start categorizing your tasks into lists
			</p>
			<w-button
				bg-color="primary"
				color="white"
				@click="requestList"
			>
				Create list
				<w-icon class="right">
					mdi mdi-plus
				</w-icon>
			</w-button>
		</div>
		<template v-else>
			<Draggable
				v-model="project.lists"
				class="project-kanban__grid"
				handle=".kanban-list__header"
				:group="{ name: 'kanban-list' }"
				:animation="200"
				:style="kanbanStyle"
				@input="updateListOrder"
			>
				<div
					v-for="list in project.lists" :key="list.id"
					class="kanban-list"
				>
					<div class="kanban-list__header">
						<w-flex align-center>
							<div class="kanban-list__header-left">
								<w-icon v-if="list.icon" class="left">
									{{ list.icon }}
								</w-icon>
								<h1>
									{{ list.name }}
								</h1>
								<div
									class="kanban-list__list-size"
									:class="computeListSizeClass(list)"
								>
									{{ list.tasks.length }}
									<template v-if="list.taskLimit">
										/ {{ list.taskLimit }}
									</template>
								</div>
							</div>
							<VindiSpacer />
							<div class="kanban-list__header-right">
								<w-button
									icon="mdi mdi-plus"
									bg-color="transparent"
									lg
									:disabled="isListFull(list)"
									@click="showTaskCreator(list)"
								/>
								
								<w-menu
									bottom 
									custom
									align-left
								>
									<template #activator="{ on }">
										<w-button
											icon="mdi mdi-chevron-down"
											bg-color="transparent"
											lg
											v-on="on"
										/>
									</template>
									<div class="simple-list">
										<p class="simple-list__title">
											{{ $t('LIST_OPTIONS') }}
										</p>
										<w-divider />
										<div class="simple-list__list">
											<div
												class="simple-list__item"
												@click="editList(list)"
											>
												<w-icon size="1.1rem">
													mdi mdi-pencil
												</w-icon>
												{{ $t("LIST_EDIT") }}
											</div>
											<div class="simple-list__item" @click="moveList(list.id)">
												<w-icon size="1.1rem">
													mdi mdi-arrow-right-bottom-bold
												</w-icon>
												{{ $t("LIST_MOVE") }}
											</div>
											<div class="simple-list__item" @click="duplicateList(list)">
												<w-icon size="1.1rem">
													mdi mdi-content-duplicate
												</w-icon>
												{{ $t("LIST_DUPLICATE") }}
											</div>
											<w-divider />
											<div class="simple-list__item error" @click="deleteList(list)">
												<w-icon size="1.1rem">
													mdi mdi-delete
												</w-icon>
												{{ $t("LIST_DELETE") }}
											</div>
										</div>
									</div>
								</w-menu>
							</div>
						</w-flex>
						<div
							class="kanban-list__header-line"
							:style="{ backgroundColor: list.color || 'var(--accent-color)' }"
						/>
					</div>
					<Draggable
						v-model="list.tasks"
						class="kanban-list__body"
						ghost-class="task-tile--ghost"
						draggable=".kanban-list__task"
						:group="{ name: 'kanban-task', put: isListFull(list) ? false : undefined }"
						:data-id="list.id"
						:force-fallback="true"
						:fallback-tolerance="10"
						@end="updateTaskOrder"
					>
						<TaskTile
							v-for="task in list.tasks"
							:key="task.id"
							:task="task"
							:data-id="task.id"
							class="kanban-list__task"
						/>
						<template #header>
							<div
								v-if="newTaskInList == list.id && newTaskPosition == 'start'"
								class="kanban-list__creator"
							>
								<w-input
									ref="taskCreator"
									v-model="newTaskSummary"
									@keydown.esc="hideTaskCreator(true)"
									@keydown.enter="createTask"
								/>
								<div class="kanban-list__creator-options">
									<w-button
										bg-color="shade-2"
										icon="mdi mdi-close"
										@click="hideTaskCreator(true)"
									/>
									<w-button
										class="kanban-list__creator-move"
										icon="mdi mdi-chevron-down"
										bg-color="transparent"
										@click="newTaskPosition = 'end'"
									/>
									<VindiSpacer />
									<w-button
										bg-color="primary"
										color="white"
										:loading="newTaskLoading"
										@click="createTask"
									>
										Save task
									</w-button>
								</div>
							</div>
						</template>
						<template #footer>
							<div
								v-if="newTaskInList == list.id && newTaskPosition == 'end'"
								class="kanban-list__creator"
							>
								<w-input
									ref="taskCreator"
									v-model="newTaskSummary"
									@keydown.esc="hideTaskCreator(true)"
									@keydown.enter="createTask"
								/>
								<div class="kanban-list__creator-options">
									<w-button
										bg-color="shade-2"
										icon="mdi mdi-close"
										@click="hideTaskCreator(true)"
									/>
									<w-button
										class="kanban-list__creator-move"
										icon="mdi mdi-chevron-up"
										bg-color="transparent"
										@click="newTaskPosition = 'start'"
									/>
									<VindiSpacer />
									<w-button
										bg-color="primary"
										color="white"
										:loading="newTaskLoading"
										@click="createTask"
									>
										Save task
									</w-button>
								</div>
							</div>
						</template>
					</Draggable>
				</div>
			</Draggable>
		</template>
		
		<!-- ANCHOR List moving dialog -->
		<w-dialog
			v-model="moveListDialog" 
			dialog-class="simple-dialog"
			width="400"
		>
			<VindiTitle>
				Select Project
			</VindiTitle>

			<w-divider />

			<w-select
				v-model="moveListProject"
				:items="selectableProjects"
				:menu-props="{'z-index': 600}"
				placeholder="Select a project"
			/>

			<w-flex class="simple-dialog__options">
				<w-button
					bg-color="shade-2"
					@click="moveListDialog = false"
				>
					Close
				</w-button>

				<VindiSpacer />

				<w-button
					color="white"
					bg-color="primary"
					:loading="moveListLoading"
					:disabled="!moveListProject"
					@click="moveListConfirm"
				>
					<w-icon class="left">
						mdi mdi-check
					</w-icon>
					Confirm Move
				</w-button>
			</w-flex>
		</w-dialog>

		<!-- ANCHOR List creation dialog -->
		<w-dialog
			v-model="newListDialog"
			dialog-class="simple-dialog"
			width="600"
		>
			<VindiTitle icon="mdi mdi-view-column">
				{{ newListEditing ? `Editing ${newListEditing.name}` : $t("CREATE_NEW_LIST") }}
			</VindiTitle>

			<label v-html="$t('CREATE_NEW_LIST_NAME')" />
			<w-input
				v-model="newListName"
				placeholder="Backlog, Sprint 1, etc."
			/>

			<w-flex :gap="5" class="simple-dialog__section">
				<div class="xs12 md6">
					<label v-html="$t('CREATE_NEW_LIST_ICON')" />
					<VindiIconPicker
						v-model="newListIcon"
					/>
				</div>
				<div class="xs12 md6">
					<label v-html="$t('CREATE_NEW_LIST_LIMIT')" />
					<w-select
						v-model="newListLimit"
						:items="listLimitOptions"
						:menu-props="{'z-index': 600}"
						color="black"
						no-unselect
					/>
				</div>
			</w-flex>

			<label v-html="$t('CREATE_NEW_LIST_COLOR')" />
			<VindiColorPicker
				v-model="newListColor"
			/>

			<w-flex class="simple-dialog__options">
				<w-button
					bg-color="shade-2"
					@click="newListDialog = false"
				>
					Close
				</w-button>
				<VindiSpacer />
				<w-button
					color="white"
					bg-color="primary"
					:loading="newListLoading"
					:disabled="!newListName"
					@click="saveList"
				>
					<w-icon class="left">
						mdi mdi-check
					</w-icon>
					{{ $t("CREATE_NEW_LIST_SAVE") }}
				</w-button>
			</w-flex>
		</w-dialog>
	</section>
</template>

<script lang="ts">
import Vue from 'vue';
import { find, findIndex, isNumber } from 'lodash';
import { focusInput, parseSlug, proxyComparator } from '~/src/helpers';
import fetchKanbanPage from '~/assets/query/kanban/fetchKanbanPage.gql';
import onListAdd from '~/assets/query/kanban/onListAdd.gql';
import onListOrder from '~/assets/query/kanban/onListOrder.gql';
import onListRemove from '~/assets/query/kanban/onListRemove.gql';
import onListUpdate from '~/assets/query/kanban/onListUpdate.gql';
import onTaskMove from '~/assets/query/kanban/onTaskMove.gql';
import onTaskRemove from '~/assets/query/kanban/onTaskRemove.gql';
import onTaskUpdate from '~/assets/query/kanban/onTaskUpdate.gql';
import createList from '~/assets/query/kanban/createList.gql';
import updateList from '~/assets/query/kanban/updateList.gql';
import deleteList from '~/assets/query/kanban/deleteList.gql';
import moveList from '~/assets/query/kanban/moveList.gql';
import duplicateList from '~/assets/query/kanban/duplicateList.gql';
import moveTask from '~/assets/query/kanban/moveTask.gql';
import createTask from '~/assets/query/project/createTask.gql';
import fetchAccessibleProjects from '~/assets/query/kanban/fetchAccessibleProjects.gql';
import updateProject from '~/assets/query/project/updateProject.gql';
import { CreationProvider } from '~/typings/models';

const NO_LIMIT = Symbol('no-limit');

export default Vue.extend({

	async asyncData(context) {
		const result = await context.$api.query(fetchKanbanPage, {
			pid: parseSlug(context.params.pid)
		});

		const accessible = await context.$api.query(fetchAccessibleProjects);

		return {
			project: result.project,
			moveListProjects: accessible.projects.filter((project: any) => project.id != result.project.id),
		};
	},

	data: () => ({
		project: null as any,
		manager: null as any,
		session: Math.floor(Date.now() * Math.random()) % 2147483647,

		// FIXME We should probably clarify these variables
		// since "new" is now also used for editing.
		newListDialog: false,
		newListEditing: null as any,
		newListLoading: false,
		newListName: '',
		newListIcon: '',
		newListColor: '',
		newListLimit: NO_LIMIT as any,
		
		newTaskInList: undefined,
		newTaskPosition: 'start',
		newTaskSummary: '',
		newTaskLoading: false,

		moveListDialog: false,
		moveListLoading: false,
		moveListList: null as unknown as number,
		moveListProject: null as unknown as number,
		moveListProjects: [] as any
	}),

	computed: {
		kanbanStyle(): any {
			return {
				backgroundColor: this.project.kanbanColor
			};
		},
		kanbanClass(): any {
			return {
				'project-kanban--background': !!this.project.kanbanColor
			};
		},
		listLimitOptions(): any {
			const options = Array.from(Array(50).keys()).map(n => ({
				label: n + 1, value: (n + 1).toString()
			}));

			return [
				{ label: 'No limit', value: NO_LIMIT },
				...options
			];
		},

		selectableProjects(): any {
			const options = this.moveListProjects.map((project: any) => ({
				label: project.name, 
				value: project.id
			}));

			return [
				...options
			];
		}
	},

	mounted() {
		this.manager = this.$api.createManager();

		const slug = parseSlug(this.$route.params.pid);

		// TODO Move each "next" handler to a dedicated function
		
		// Task list ordering subscription
		this.manager.subscribe({
			query: onListOrder,
			variables: {
				id: slug
			},
			next: (result: any) => {
				const ordering = result.projectUpdated.lists.map((list: any) => list.id);

				this.project.lists.sort(
					proxyComparator(ordering, 'id')
				);
			}
		});

		// Add new list to kanban
		this.manager.subscribe({
			query: onListAdd,
			variables: {
				id: slug
			},
			next: (result: any) => {
				const list = result.taskListCreated;

				this.project.lists.push({
					...list,
					tasks: []
				});
			}
		});

		// Remove a list from the kanban
		this.manager.subscribe({
			query: onListRemove,
			variables: {
				id: slug
			},
			next: (result: any) => {
				const listId = result.taskListRemoved.id;

				this.project.lists = this.project.lists.filter((list: any) => {
					return list.id != listId;
				});
			}
		});

		// Remove a list from the kanban
		this.manager.subscribe({
			query: onListUpdate,
			variables: {
				id: slug
			},
			next: (result: any) => {
				const listId = result.taskListUpdated.id;
				const index = findIndex(this.project.lists, (list: any) => list.id == listId);
				const current = this.project.lists[index];

				this.project.lists.splice(index, 1, {
					...result.taskListUpdated,
					tasks: current.tasks || []
				});
			}
		});

		// Remove a task from the board
		this.manager.subscribe({
			query: onTaskRemove,
			variables: {
				id: slug
			},
			next: (result: any) => {
				const taskId = result.taskRemoved.id;
				const listId = result.taskRemoved.list?.id;

				if(listId === undefined) {
					return;
				}

				const list = find(this.project.lists, (list: any) => list.id == listId);
			
				if(!list) {
					return;
				}

				const index = findIndex(list.tasks, (task: any) => task.id == taskId);

				if(index >= 0) {
					list.tasks.splice(index, 1);
				}
			}
		});

		// Move a task to a different position
		this.manager.subscribe({
			query: onTaskMove,
			variables: {
				id: slug
			},
			next: (result: any) => {
				if(result.taskMoved.moveId == this.session) {
					return;
				}

				const fromListId = result.taskMoved.fromList?.id;
				const toListId = result.taskMoved.toList?.id;

				// Remove from previous list
				if(fromListId !== undefined) {
					const fromList = find(this.project.lists, (list: any) => list.id == fromListId);
					const oldIndex = findIndex(fromList.tasks, (task: any) => task.id == result.taskMoved.task.id);

					if(oldIndex >= 0) {
						fromList.tasks.splice(oldIndex, 1);
					}
				}

				// Add to target list
				if(toListId !== undefined) {
					const task = result.taskMoved.task;
					const index = result.taskMoved.index;
					const toList = find(this.project.lists, (list: any) => list.id == toListId);

					toList.tasks.splice(index, 0, task);
				}
			}
		});

		// Update a task currently on the board
		this.manager.subscribe({
			query: onTaskUpdate,
			variables: {
				id: slug
			},
			next: (result: any) => {
				const task = result.taskUpdated;
				
				if(!task.list) {
					return;
				}

				const list = find(this.project.lists, (list: any) => list.id == task.list.id);
				const index = findIndex(list.tasks, (listTask: any) => listTask.id == task.id);

				this.$set(list.tasks, index, task);
			}
		});

		// Register creation providers
		this.$store.commit('addCreationProvider', {
			id: 'list',
			order: 30,
			icon: 'mdi mdi-view-column',
			title: 'Create list',
			description: 'Kanban lists provide an effective way to categorize your tasks',
			handler: () => this.requestList()
		} as CreationProvider);
	},

	beforeDestroy() {
		this.$store.commit('removeCreationProvider', 'list');
		this.manager.dispose();
	},

	methods: {

		computeListSizeClass(list: any): any {
			return {
				'kanban-list__list-size--max': this.isListFull(list)
			};
		},

		isListFull(list: any): boolean {
			return isNumber(list.taskLimit) && list.tasks.length >= list.taskLimit;
		},

		requestList() {
			this.newListName = '';
			this.newListIcon = '';
			this.newListColor = '';
			this.newListLimit = NO_LIMIT;
			this.newListDialog = true;
			this.newListEditing = null;
		},

		editList(list: any) {
			this.newListName = list.name;
			this.newListIcon = list.icon || '';
			this.newListColor = list.color || '';
			this.newListLimit = list.taskLimit?.toString() || NO_LIMIT;
			this.newListDialog = true;
			this.newListEditing = list;
		},

		moveList(list: number) {
			this.moveListList = list;
			this.moveListDialog = true;
		},

		async duplicateList(list: any) {
			const res = await this.$api.query(duplicateList, {
				id: list.id
			});

			if(!res || res == false) {
				this.$waveui.notify({
					message: `Duplication of list ${list.name} failed`,
					error: true
				});
			} else {
				this.$waveui.notify({
					message: `Successfully duplicated list ${list.name}`,
					success: true
				});
			}
		},

		async moveListConfirm() {
			this.moveListLoading = true;

			const res = await this.$api.query(moveList, {
				listId: this.moveListList,
				projectId: this.moveListProject
			});

			this.moveListLoading = false;
			this.moveListDialog = false;

			if(!res || res == false) {
				this.$waveui.notify({
					message: 'List move failed',
					error: true
				});
			} else {
				this.$waveui.notify({
					message: 'List successfully moved',
					success: true
				});
			}
		},

		deleteList(list: any) {
			this.$api.query(deleteList, {
				id: list.id
			});
		},

		showTaskCreator(list: any) {
			this.newTaskInList = list.id;
			this.newTaskSummary = '';
			
			this.$nextTick(() => {
				focusInput((this.$refs.taskCreator as Vue[])[0]);
			});
		},

		hideTaskCreator(force: boolean) {
			if(force || !this.newTaskSummary.length) {
				this.newTaskInList = undefined;
			}
		},

		async createTask() {
			if(!this.newTaskSummary.length) return;

			this.newTaskLoading = true;

			// Find the insertion index
			const index = this.newTaskPosition == 'start' ? 0 : -1;

			// Create the task
			const result = await this.$api.query(createTask, {
				pid: this.project.id,
				summary: this.newTaskSummary
			});

			// Move the task into place
			await this.$api.query(moveTask, {
				id: result.createTask.id,
				list: this.newTaskInList,
				session: -1, // We want to refresh
				index: index
			});

			this.newTaskLoading = false;
			this.newTaskInList = undefined;
		},

		updateListOrder(list: any) {
			this.$api.query(updateProject, {
				id: this.project.id,
				details: {
					listOrder: list.map((list: any) => list.id)
				}
			});
		},

		updateTaskOrder(event: any) {
			const taskId = parseInt(event.item.getAttribute('data-id'));
			const listId = parseInt(event.to.getAttribute('data-id'));

			this.$api.query(moveTask, {
				id: taskId,
				list: listId,
				index: event.newIndex,
				session: this.session
			});
		},

		async saveList() {
			this.newListLoading = true;

			const taskLimit = this.newListLimit != NO_LIMIT
				? parseInt(this.newListLimit)
				: null;

			const details = {
				name: this.newListName,
				icon: this.newListIcon,
				color: this.newListColor,
				limit: taskLimit
			};

			if(this.newListEditing) {
				await this.$api.query(updateList, {
					id: this.newListEditing.id,
					details: details
				});
			} else {
				await this.$api.query(createList, {
					pid: this.project.id,
					details: details
				});
			}

			this.newListLoading = false;
			this.newListDialog = false;

			this.$waveui.notify({
				message: this.$t('NOTIFICATION_SAVE_LIST', [ this.newListName ]),
				success: true,
			});
		}

	}
});
</script>

<style lang="scss">
.project-kanban {
	height: 100%;
	position: relative;

	&__grid {
		display: flex;
		height: 100%;
		overflow-x: auto;
		background-size: cover;
		background-attachment: fixed;
		background-position: center center;
		background-repeat: no-repeat;
	}
}

.kanban-list {
	min-width: 20rem;
	max-width: 20rem;
	display: flex;
	flex-direction: column;
	background-color: hsl(225, 25%, 92%);

	@include dark {
		background-color: hsl(240, 3%, 13%);
	}

	&__header {
		@include font-size(sm);
		@include padding(2, y);
		@include padding(3, x);

		cursor: move;
		position: relative;

		.w-button {
			margin: 0;
		}
	}

	// Set alternating background
	&:nth-child(even) {
		background-color: hsl(225, 25%, 89%);

		@include dark {
			background-color: hsl(240, 3%, 15%);
		}
	}

	// Override in case of background image
	.project-kanban--background & {
		background-color: rgba(0, 0, 0, 0.175);

		&:nth-child(even) {
			background-color: rgba(0, 0, 0, 0.25);
		}

		&__header {
			filter: drop-shadow(1px 1px 1px rgba(29, 29, 29, 0.35));
			color: white;
		}

		.task-tile--ghost {
			background-color: rgba(255, 255, 255, 0.25);
		}
	}

	&__header-line {
		@include margin(1, t);
		@include rounded(full);

		height: 4px;
	}

	&__header-left {
		display: flex;
		align-items: center;

		h1 {
			@include font-weight(bold);

			pointer-events: none;
			margin-top: 2px;
		}
	}

	&__header-right {
		display: flex;
		align-items: center;
	}

	&__header-left, &__header-right {
		cursor: auto;
	}

	&__list-size {
		@include font-size(xs);
		@include rounded(full);
		@include padding(0.5, y);
		@include padding(1.8, x);
		@include margin(2, l);

		background-color: #bec3ce;

		@include dark {
			background-color: #5a5f68;
		}

		.project-kanban--background & {
			background-color: #454950;
		}

		&--max {
			background-color: #F65E55 !important;
			color: white !important;
		}
	}

	&__create-button {
		color: white;
		opacity: 0.8;
	}

	&__menu-button {
		margin-left: 0;
		color: white;
		opacity: 0.8;
	}

	&__body {
		@include padding(2.75);

		flex: 1;
		height: 0;
		overflow-y: auto;
		max-height: calc(100vh - 56px - $toolbar-height);

		.sortable-drag {
			opacity: 1;
			transform: rotate(5deg);
		}
	}

	&__task, &__creator {
		@include margin(2.75, b);
	}

	&__creator {
		@include rounded(xl);
		@include padding(3);

		cursor: pointer;
		user-select: none;
		background-color: var(--color-shade-0);
		box-shadow: rgb(0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, rgba(0, 0, 0, 0.06) 0px 1px 2px 0px;

		@include dark {
			background-color: var(--color-shade-3);
		}
	}

	&__creator-options {
		@include padding(3, t);

		display: flex;
	}

	&__creator-move {
		@include margin(1, l);

		background-color: var(--color-shade-2) !important;
	}

	::-webkit-scrollbar {
		width: 8px;
	}
}
</style>