<template>
	<aside :class="{'is-active': menuActive}" class="namespace-container">
		<nav class="menu top-menu">
			<router-link :to="{name: 'home'}" class="logo">
				<Logo width="164" height="48"/>
			</router-link>
			<ul class="menu-list">
				<li>
					<router-link :to="{ name: 'home'}" v-shortcut="'g o'">
						<span class="menu-item-icon icon">
							<icon icon="calendar"/>
						</span>
						{{ $t('navigation.overview') }}
					</router-link>
				</li>
				<li>
					<router-link :to="{ name: 'tasks.range'}" v-shortcut="'g u'">
						<span class="menu-item-icon icon">
							<icon :icon="['far', 'calendar-alt']"/>
						</span>
						{{ $t('navigation.upcoming') }}
					</router-link>
				</li>
				<li>
					<router-link :to="{ name: 'namespaces.index'}" v-shortcut="'g n'">
						<span class="menu-item-icon icon">
							<icon icon="layer-group"/>
						</span>
						{{ $t('namespace.title') }}
					</router-link>
				</li>
				<li>
					<router-link :to="{ name: 'labels.index'}" v-shortcut="'g a'">
						<span class="menu-item-icon icon">
							<icon icon="tags"/>
						</span>
						{{ $t('label.title') }}
					</router-link>
				</li>
				<li>
					<router-link :to="{ name: 'teams.index'}" v-shortcut="'g m'">
						<span class="menu-item-icon icon">
							<icon icon="users"/>
						</span>
						{{ $t('team.title') }}
					</router-link>
				</li>
			</ul>
		</nav>

		<nav class="menu namespaces-lists loader-container is-loading-small" :class="{'is-loading': loading}">
			<template v-for="(n, nk) in namespaces" :key="n.id">
				<div class="namespace-title" :class="{'has-menu': n.id > 0}">
					<BaseButton
						@click="toggleProjects(n.id)"
						class="menu-label"
						v-tooltip="namespaceTitles[nk]"
					>
						<ColorBubble
							v-if="n.hexColor !== ''"
							:color="n.hexColor"
							class="mr-1"
						/>
						<span class="name">{{ namespaceTitles[nk] }}</span>
						<div
							class="icon menu-item-icon is-small toggle-projects-icon pl-2"
							:class="{'active': typeof projectsVisible[n.id] !== 'undefined' ? projectsVisible[n.id] : true}"
						>
							<icon icon="chevron-down"/>
						</div>
						<span class="count" :class="{'ml-2 mr-0': n.id > 0}">
							({{ namespaceProjectsCount[nk] }})
						</span>
					</BaseButton>
					<namespace-settings-dropdown class="menu-list-dropdown" :namespace="n" v-if="n.id > 0"/>
				</div>
					<!--
						NOTE: a v-model / computed setter is not possible, since the updateActiveProjects function
						triggered by the change needs to have access to the current namespace
					-->
					<draggable
						v-if="projectsVisible[n.id] ?? true"
						v-bind="dragOptions"
						:modelValue="activeProjects[nk]"
						@update:modelValue="(projects) => updateActiveProjects(n, projects)"
						group="namespace-projects"
						@start="() => drag = true"
						@end="saveProjectPosition"
						handle=".handle"
						:disabled="n.id < 0 || undefined"
						tag="ul"
						item-key="id"
						:data-namespace-id="n.id"
						:data-namespace-index="nk"
						:component-data="{
							type: 'transition-group',
							name: !drag ? 'flip-list' : null,
							class: [
								'menu-list can-be-hidden',
								{ 'dragging-disabled': n.id < 0 }
							]
						}"
					>
						<template #item="{element: l}">
							<li
								class="project-menu loader-container is-loading-small"
								:class="{'is-loading': projectUpdating[l.id]}"
							>
								<BaseButton
									:to="{ name: 'project.index', params: { projectId: l.id} }"
									class="project-menu-link"
									:class="{'router-link-exact-active': currentProject.id === l.id}"
								>
									<span class="icon menu-item-icon handle">
										<icon icon="grip-lines"/>
									</span>
									<ColorBubble
										v-if="l.hexColor !== ''"
										:color="l.hexColor"
										class="mr-1"
									/>
									<span class="project-menu-title">{{ getProjectTitle(l) }}</span>
								</BaseButton>
								<BaseButton
									class="favorite"
									:class="{'is-favorite': l.isFavorite}"
									@click="projectStore.toggleProjectFavorite(l)"
								>
									<icon :icon="l.isFavorite ? 'star' : ['far', 'star']"/>
								</BaseButton>
								<project-settings-dropdown class="menu-list-dropdown" :project="l" v-if="l.id > 0">
									<template #trigger="{toggleOpen}">
										<BaseButton class="menu-list-dropdown-trigger" @click="toggleOpen">
											<icon icon="ellipsis-h" class="icon"/>
										</BaseButton>
									</template>
								</project-settings-dropdown>
								<span class="project-setting-spacer" v-else></span>
							</li>
						</template>
					</draggable>
			</template>
		</nav>
		<PoweredByLink/>
	</aside>
</template>

<script setup lang="ts">
import {ref, computed, onMounted, onBeforeMount} from 'vue'
import draggable from 'zhyswan-vuedraggable'
import type {SortableEvent} from 'sortablejs'

import BaseButton from '@/components/base/BaseButton.vue'
import ProjectSettingsDropdown from '@/components/project/project-settings-dropdown.vue'
import NamespaceSettingsDropdown from '@/components/namespace/namespace-settings-dropdown.vue'
import PoweredByLink from '@/components/home/PoweredByLink.vue'
import Logo from '@/components/home/Logo.vue'

import {calculateItemPosition} from '@/helpers/calculateItemPosition'
import {getNamespaceTitle} from '@/helpers/getNamespaceTitle'
import {getProjectTitle} from '@/helpers/getProjectTitle'
import {useEventListener} from '@vueuse/core'
import type {IProject} from '@/modelTypes/IProject'
import type {INamespace} from '@/modelTypes/INamespace'
import ColorBubble from '@/components/misc/colorBubble.vue'

import {useBaseStore} from '@/stores/base'
import {useProjectStore} from '@/stores/projects'
import {useNamespaceStore} from '@/stores/namespaces'

const drag = ref(false)
const dragOptions = {
	animation: 100,
	ghostClass: 'ghost',
}

const baseStore = useBaseStore()
const namespaceStore = useNamespaceStore()
const currentProject = computed(() => baseStore.currentProject)
const menuActive = computed(() => baseStore.menuActive)
const loading = computed(() => namespaceStore.isLoading)


const namespaces = computed(() => {
	return namespaceStore.namespaces.filter(n => !n.isArchived)
})
const activeProjects = computed(() => {
	return namespaces.value.map(({projects}) => {
		return projects?.filter(item => {
			return typeof item !== 'undefined' && !item.isArchived
		})
	})
})

const namespaceTitles = computed(() => {
	return namespaces.value.map((namespace) => getNamespaceTitle(namespace))
})

const namespaceProjectsCount = computed(() => {
	return namespaces.value.map((_, index) => activeProjects.value[index]?.length ?? 0)
})


useEventListener('resize', resize)
onMounted(() => resize())

const projectStore = useProjectStore()

function resize() {
	// Hide the menu by default on mobile
	baseStore.setMenuActive(window.innerWidth >= 770)
}

function toggleProjects(namespaceId: INamespace['id']) {
	projectsVisible.value[namespaceId] = !projectsVisible.value[namespaceId]
}

const projectsVisible = ref<{ [id: INamespace['id']]: boolean }>({})
// FIXME: async action will be unfinished when component mounts
onBeforeMount(async () => {
	const namespaces = await namespaceStore.loadNamespaces()
	namespaces.forEach(n => {
		if (typeof projectsVisible.value[n.id] === 'undefined') {
			projectsVisible.value[n.id] = true
		}
	})
})

function updateActiveProjects(namespace: INamespace, activeProjects: IProject[]) {
	// This is a bit hacky: since we do have to filter out the archived items from the project
	// for vue draggable updating it is not as simple as replacing it.
	// To work around this, we merge the active projects with the archived ones. Doing so breaks the order
	// because now all archived projects are sorted after the active ones. This is fine because they are sorted 
	// later when showing them anyway, and it makes the merging happening here a lot easier.
	const projects = [
		...activeProjects,
		...namespace.projects.filter(l => l.isArchived),
	]

	namespaceStore.setNamespaceById({
		...namespace,
		projects,
	})
}

const projectUpdating = ref<{ [id: INamespace['id']]: boolean }>({})

async function saveProjectPosition(e: SortableEvent) {
	if (!e.newIndex && e.newIndex !== 0) return

	const namespaceId = parseInt(e.to.dataset.namespaceId as string)
	const newNamespaceIndex = parseInt(e.to.dataset.namespaceIndex as string)

	const projectsActive = activeProjects.value[newNamespaceIndex]
	// If the project was dragged to the last position, Safari will report e.newIndex as the size of the projectsActive
	// array instead of using the position. Because the index is wrong in that case, dragging the project will fail.
	// To work around that we're explicitly checking that case here and decrease the index.
	const newIndex = e.newIndex === projectsActive.length ? e.newIndex - 1 : e.newIndex 
	
	const project = projectsActive[newIndex]
	const projectBefore = projectsActive[newIndex - 1] ?? null
	const projectAfter = projectsActive[newIndex + 1] ?? null
	projectUpdating.value[project.id] = true

	const position = calculateItemPosition(
		projectBefore !== null ? projectBefore.position : null,
		projectAfter !== null ? projectAfter.position : null,
	)

	try {
		// create a copy of the project in order to not violate pinia manipulation
		await projectStore.updateProject({
			...project,
			position,
			namespaceId,
		})
	} finally {
		projectUpdating.value[project.id] = false
	}
}
</script>

<style lang="scss" scoped>
$navbar-padding: 2rem;
$vikunja-nav-background: var(--site-background);
$vikunja-nav-color: var(--grey-700);
$vikunja-nav-selected-width: 0.4rem;

.logo {
	display: block;

	padding-left: 1rem;
	margin-right: 1rem;
	margin-bottom: 1rem;

	@media screen and (min-width: $tablet) {
		display: none;
	}
}

.namespace-container {
	background: $vikunja-nav-background;
	color: $vikunja-nav-color;
	padding: 0 0 1rem;
	transition: transform $transition-duration ease-in;
	position: fixed;
	top: $navbar-height;
	bottom: 0;
	left: 0;
	transform: translateX(-100%);
	overflow-x: auto;
	width: $navbar-width;

	@media screen and (max-width: $tablet) {
		top: 0;
		width: 70vw;
		z-index: 20;
	}

	&.is-active {
		transform: translateX(0);
		transition: transform $transition-duration ease-out;
	}
}

// these are general menu styles
// should be in own components
.menu {
	.menu-label,
	.menu-list .project-menu-link,
	.menu-list a {
		display: flex;
		align-items: center;
		justify-content: space-between;
		cursor: pointer;

		.color-bubble {
			height: 12px;
			flex: 0 0 12px;
		}
	}

	.menu-list {
		li {
			height: 44px;
			display: flex;
			align-items: center;

			&:hover {
				background: var(--white);
			}

			.menu-list-dropdown {
				opacity: 0;
				transition: $transition;
			}

			&:hover .menu-list-dropdown {
				opacity: 1;
			}
		}

		.menu-item-icon {
			color: var(--grey-400);
		}

		.menu-list-dropdown-trigger {
			display: flex;
			padding: 0.5rem;
		}

		.flip-list-move {
			transition: transform $transition-duration;
		}

		.ghost {
			background: var(--grey-200);

			* {
				opacity: 0;
			}
		}

		a:hover {
			background: transparent;
		}

		.project-menu-link,
		li > a {
			color: $vikunja-nav-color;
			padding: 0.75rem .5rem 0.75rem ($navbar-padding * 1.5 - 1.75rem);
			transition: all 0.2s ease;

			border-radius: 0;
			white-space: nowrap;
			text-overflow: ellipsis;
			overflow: hidden;
			width: 100%;
			border-left: $vikunja-nav-selected-width solid transparent;

			&:hover {
				border-left: $vikunja-nav-selected-width solid var(--primary);
			}

			&.router-link-exact-active {
				color: var(--primary);
				border-left: $vikunja-nav-selected-width solid var(--primary);
			}

			.icon {
				height: 1rem;
				vertical-align: middle;
				padding-right: 0.5rem;
			}

			&.router-link-exact-active .icon:not(.handle) {
				color: var(--primary);
			}

			.handle {
				opacity: 0;
				transition: opacity $transition;
				margin-right: .25rem;
				cursor: grab;
			}
			&:hover .handle {
				opacity: 1;
			}
		}
	}
}

.top-menu {
	margin-top: math.div($navbar-padding, 2);
 
	.menu-list {
		li {
			font-weight: 600;
			font-family: $vikunja-font;
		}

		.project-menu-link,
		li > a {
			padding-left: 2rem;
			display: inline-block;

			.icon {
				padding-bottom: .25rem;
			}
		}
	}
}

.namespaces-lists {
	padding-top: math.div($navbar-padding, 2);

	.menu-label {
		font-size: 1rem;
		font-weight: 700;
		font-weight: bold;
		font-family: $vikunja-font;
		color: $vikunja-nav-color;
		font-weight: 600;
		min-height: 2.5rem;
		padding-top: 0;
		padding-left: $navbar-padding;

		overflow: hidden;
		margin-bottom: 0;
		flex: 1 1 auto;

		.name {
			overflow: hidden;
			text-overflow: ellipsis;
			white-space: nowrap;
			margin-right: auto;
		}

		.count {
			color: var(--grey-500);
			margin-right: .5rem;
			// align brackets with number
			font-feature-settings: "case";
		}
	}

	.favorite {
		margin-left: .25rem;
		transition: opacity $transition, color $transition;
		opacity: 0;

		&:hover,
		&.is-favorite {
			color: var(--warning);
		}
	}

	.favorite.is-favorite,
	.list-menu:hover .favorite {
		opacity: 1;
	}

	.project-menu-title {
		overflow: hidden;
		text-overflow: ellipsis;
		width: 100%;
	}

	.color-bubble {
		width: 14px;
		height: 14px;
		flex-basis: auto;
	}

	.is-archived {
		min-width: 85px;
	}
}

.namespace-title {
	display: flex;
	align-items: center;
	justify-content: space-between;
	color: $vikunja-nav-color;
	padding: 0 .25rem;

	.toggle-lists-icon {
		svg {
			transition: all $transition;
			transform: rotate(90deg);
			opacity: 1;
		}

		&.active svg {
			transform: rotate(0deg);
			opacity: 0;
		}
	}

	&:hover .toggle-lists-icon svg {
		opacity: 1;
	}

	&:not(.has-menu) .toggle-lists-icon {
		padding-right: 1rem;
	}
}

.project-setting-spacer {
	width: 2.5rem;
	flex-shrink: 0;
}

.namespaces-project.loader-container.is-loading {
	min-height: calc(100vh - #{$navbar-height + 1.5rem + 1rem + 1.5rem});
}
</style>
