import { ActionTree, GetterTree, MutationTree } from 'vuex';
import { Config, CreationProvider, Profile, ViewTask } from '~/typings/models';

import { Optional } from '~/typings/helpers';
import { ServerApiError } from '~/plugins/api';
import { Store } from 'vuex';
import authenticateQuery from '~/assets/query/store/authenticate.gql';
import displayTaskQuery from '~/assets/query/store/displayTask.gql';
import fetchConfigQuery from '~/assets/query/store/fetchConfig.gql';
import refreshProfileQuery from '~/assets/query/store/refreshProfile.gql';
import signInQuery from '~/assets/query/store/signIn.gql';
import signOutQuery from '~/assets/query/store/signOut.gql';
import signUpQuery from '~/assets/query/store/signUp.gql';

export const state = () => ({
	profile: undefined as Optional<Profile>,
	language: 'en-US',
	isAuthed: false,
	isDark: false,
	taskView: undefined as Optional<ViewTask>,
	taskViewOpen: false,
	newProjectDialog: false,
	newTeamDialog: false,
	newTaskDialog: false,
	labelManageDialog: false,
	creationItems: [] as CreationProvider[],
	showLabelNames: false,
	expandSidebar: false,
	expandToolbar: false,
	authError: '',
	config: {
		instanceName: '',
		maintenance: false,
		allowAnonymous: false,
		allowRegister: false,
		verification: false,
		installing: false
	} as Config
});

export type RootState = ReturnType<typeof state>;

export const mutations: MutationTree<RootState> = {

	/**
	 * Save the config instance
	 */
	storeConfig(state, config) {
		state.config = config;
	},

	/**
	 * Store the current user profile
	 */
	storeProfile(state, profile) {
		state.profile = profile;
	},

	/**
	 * Set the authenticated status
	 */
	setAuthStatus(state, value) {
		state.isAuthed = value;

		if(value && state.config.installing) {
			state.config.installing = false;
		}
	},

	/**
	 * Set the current theme
	 */
	setDarkMode(this: Store<any>, state, value) {
		this.$cookies.set('theme', value ? 'dark' : 'light');
		state.isDark = value;
	},

	/**
	 * Set the currently loaded interface language
	 */
	setLanguage(state, language) {
		localStorage.setItem('vindigo:lang', language);
		state.language = language;
	},
	
	/**
	 * Set whether label names are displayed
	 */
	setShowLabelNames(this: Store<any>, state, value) {
		this.$cookies.set('showLabelNames', value);
		state.showLabelNames = value;
	},

	/**
	 * Set whether the sidebar is expanded
	 */
	setExpandSidebar(this: Store<any>, state, value) {
		this.$cookies.set('expandSidebar', value);
		state.expandSidebar = value;
	},

	/**
	 * Set whether the task viewer toolbar is expanded
	 */
	setExpandToolbar(this: Store<any>, state, value) {
		this.$cookies.set('expandToolbar', value);
		state.expandToolbar = value;
	},

	/**
	 * Open the task view with no task inside
	 */
	openTaskViewer(state) {
		state.taskView = undefined;
		state.taskViewOpen = true;

		document.body.style.overflowY = 'hidden';
		document.body.style.paddingRight = '11px';
	},

	/**
	 * Set the currently displayed task
	 */
	setCurrentTask(state, task) {
		state.taskView = task;
		state.taskViewOpen = true;
	},

	/**
	 * Patch the currently displayed task
	 */
	patchCurrentTask(state, patch) {
		if(!state.taskView || !state.taskViewOpen) {
			throw new Error('No task is currently open');
		}

		state.taskView = { ...state.taskView, ...patch };
	},

	/**
	 * Hide the currently displayed task
	 */
	hideCurrentTask(state) {
		state.taskViewOpen = false;
		
		document.body.style.overflowY = 'scroll';
		document.body.style.paddingRight = '0';
	},

	/**
	 * Set the visiblity of the project creation dialog
	 */
	viewNewProjectDialog(state, value) {
		state.newProjectDialog = value;
	},

	/**
	 * Set the visiblity of the team creation dialog
	 */
	viewNewTeamDialog(state, value) {
		state.newTeamDialog = value;
	},

	/**
	 * Set the visiblity of the task creation dialog
	 */
	viewNewTaskDialog(state, value) {
		state.newTaskDialog = value;
	},

	/**
	 * Set the visiblity of the label management dialog
	 */
	viewLabelManageDialog(state, value) {
		state.labelManageDialog = value;
	},

	/**
	 * Add a creation provider group to the creation menu
	 */
	addCreationProvider(state, provider) {
		state.creationItems.push(provider);
	},

	/**
	 * If there is an auth error, set it
	 */
	setAuthError(state, value) {
		state.authError = value;
	},

	/**
	 * Remove a creation provider from the creation menu
	 */
	removeCreationProvider(state, id) {
		let index = -1;

		for(let i = 0; i < state.creationItems.length; i++) {
			if(state.creationItems[i].id == id) {
				index = i;
			}
		}

		if(index >= 0) {
			state.creationItems.splice(index, 1);
		} else {
			throw new Error('Creation provider does not exist');
		}
	}

};

export const actions: ActionTree<RootState, RootState> = {
	
	/**
	 * Initialize the application
	 */
	async nuxtServerInit({ dispatch, commit }, { $cookies }) {
		const currentTheme = $cookies.get('theme');
		const showLabelNames = $cookies.get('showLabelNames');

		commit('setDarkMode', currentTheme == 'dark');
		commit('setShowLabelNames', showLabelNames);

		await Promise.all([
			dispatch('fetchConfig'),
			dispatch('authenticate'),
		]);
	},
	
	/**
	 * Request the latest client config
	 */
	async fetchConfig({ commit }) {
		const { config } = await this.$api.query(fetchConfigQuery);

		commit('storeConfig', config);
	},

	/**
	 * Authenticate with the server
	 */
	async authenticate({ commit }) {
		const res = await this.$api.query(authenticateQuery);

		commit('storeProfile', res.profile);
		commit('setAuthStatus', !!res.profile);
	},

	/**
	 * Sign in to a valid user account
	 */
	async signIn({ commit }, details) {
		try {
			const result = await this.$api.query(signInQuery, { details });

			commit('storeProfile', result.authenticate);
			commit('setAuthStatus', !!result.authenticate);
			
			this.$api.connect();

			return result.authenticate;
		} catch(err: any) {
			
			// TODO Use transition key
			if(err instanceof ServerApiError && err.hasErrorOfCode('account-disabled')) {
				commit('setAuthError', "Your account is currently disabled!");
			}

			return null;
		}
	},

	/**
	 * Sign up using the specified registration details
	 */
	async signUp({ commit }, details) {
		const result = await this.$api.query(signUpQuery, { details });

		commit('storeProfile', result.register);
		commit('setAuthStatus', !!result.register);

		this.$api.connect();

		return result.register;
	},

	/**
	 * Sign the user out from the application
	 */
	async signOut(this: Store<any>, { commit }) {
		this.$api.query(signOutQuery);

		commit('setAuthStatus', false);
		
		this.$api.connect();
		this.$router.push('/authenticate');
	},

	/**
	 * Refresh the current users profile
	 */
	async updateProfile({ commit }) {
		const result = await this.$api.query(refreshProfileQuery);

		commit('storeProfile', result.profile);
	},

	/**
	 * Display the given task in the task view
	 */
	async openTaskViewer({ commit }, id) {
		commit('openTaskViewer');

		const result = await this.$api.query(displayTaskQuery, {
			id: id
		});

		if(result.task) {
			commit('setCurrentTask', result.task);
		} else {
			commit('hideCurrentTask');
		}
	}

};

export const getters: GetterTree<RootState, RootState> = {
	appClass(state) {
		return state.isDark ? 'w-app--dark' : 'w-app--light';
	}
};