/**
 * Created by rockyl on 2020-01-04.
 */

import db from "../../utils/db-storage";
import {packageApi} from "../../api";
import semverMaxSatisfying from "semver/ranges/max-satisfying";
import semverRSort from "semver/functions/rsort";
import i18n from "../../i18n";
import events from "@/global-events.js"
import {clonePureObj, timeFormat, typeMapping} from "../../utils";

const storeName = 'packages';

export const packageStore = {
	state: {
		packageInfos: {
			library: [],
			process: [],
			custom: [],
			script: [],
		},
		packages: {
			library: {},
			process: {},
			custom: {},
			script: {},
		},
		processTree: [],
		triggerProcesses: [],
		customProcesses: [],
	},
	mutations: {
		resetPackageStore(state, sourceState) {
			state.packageInfos = sourceState.packageInfos;
			state.packages = sourceState.packages;
			let builtins = state.processTree.find(item=>item.name === 'normal').children.find(item=>item.name === 'builtin');
			builtins.children = sourceState.processTree.find(item=>item.name === 'normal').children.find(item=>item.name === 'builtin').children;
		},
		updatePackageInfos(state, packageInfos) {
			for (let i = 0, li = typeMapping.length; i < li; i++) {
				state.packageInfos[typeMapping[i]].splice(0);
			}
			for (let packageInfo of packageInfos) {
				let {versions, remarks, update_times, update_time} = packageInfo;

				versions = versions.split(',');
				remarks = remarks.split(',');
				update_times = update_times.split(',').map(time => timeFormat(time));

				let sortedVersions = semverRSort(versions.concat());
				let sortedRemarks = [];
				let sortedUpdate_times = [];
				for (let i = 0, li = sortedVersions.length; i < li; i++) {
					const version = sortedVersions[i];

					let index = versions.indexOf(version);
					sortedRemarks[i] = remarks[index];
					sortedUpdate_times[i] = update_times[index];
				}

				packageInfo.versions = sortedVersions;
				packageInfo.remarks = sortedRemarks;
				packageInfo.update_times = sortedUpdate_times;

				packageInfo.update_time = timeFormat(update_time);
				state.packageInfos[typeMapping[packageInfo.type]].push(packageInfo);
			}
		},
		updatePackage(state, packageItem) {
			let group = state.packages[typeMapping[packageItem.type]];
			let pid = packageItem.package_id;
			group[pid] = JSON.parse(packageItem.data);
		},
		updateProcessTree(state) {
			let processes = this.getters.processPackages;
			const prefabs = groupProcesses(processes, process => process.isPrefab);
			const builtins = groupProcesses(processes, process => !process.isPrefab);

			const normalsTree = [];

			let tree = [
				{
					name: 'prefab',
					children: prefabs,
				},
				{
					name: 'normal',
					children: normalsTree,
				}
			];

			const customProcess = {
				id: 'custom',
				name: 'Custom',
				desc: i18n.t('Custom node desc'),
			};
			normalsTree.push(customProcess);
			const dividerProcess = {
				id: 'divider',
				name: 'Divider',
				desc: i18n.t('Divider node desc'),
			};
			normalsTree.push(dividerProcess);

			normalsTree.push({
				name: 'builtin',
				children: builtins,
			});

			normalsTree.push(state.triggerProcesses = {
				name: 'trigger',
				from: 'trigger',
				children: [],
			});
			normalsTree.push(state.customProcesses = {
				name: 'custom',
				from: 'custom',
				children: [],
			});

			state.processTree = tree;
		},
		updateCustomProcesses(state, processes) {
			state.triggerProcesses.children = processes;
			state.customProcesses.children = processes;
		},
	},
	getters: {
		processPackages: state => {
			return state.packages[typeMapping[1]];
		},
		scriptPackages: state => {
			return state.packages[typeMapping[2]];
		},
		customPackages: state => {
			return state.packages[typeMapping[3]];
		},
		processTree: state => {
			return state.processTree;
		}
	},
	actions: {
		async loadPackageInfos({state, commit}) {
			const packageInfos = await packageApi.fetchPackageInfos();
			commit('updatePackageInfos', packageInfos);
		},
		getAllPackageMetas() {
			return db.getAll(storeName);
		},
		async downloadDependencies({state, commit, getters, dispatch}, dependencies) {
			let schema = {};
			let packageMetas = await db.getAll(storeName);
			for (let packageId in dependencies) {
				let version = dependencies[packageId];
				let exists = packageExists(packageMetas, packageId, version);
				if (!exists) {
					schema[packageId] = version;
				}
			}
			if (Object.keys(schema).length > 0) {
				let packages = await packageApi.fetchPackages(schema);
				for (let packageItem of packages) {
					let versions = await db.get(storeName, packageItem.package_id);
					if (!versions) {
						versions = {
							id: packageItem.package_id,
							type: packageItem.type,
						};
					}
					versions[packageItem.version] = packageItem;
					db.put(storeName, versions);
				}
			}
		},
		async applyDependencies({commit}, dependencies) {
			let missings = [];
			for (let packageId in dependencies) {
				let version = dependencies[packageId];
				let versions = await db.get(storeName, packageId);
				if (versions) {
					if (versions[version]) {
						commit('updatePackage', versions[version]);
					} else {
						missings.push({
							packageId,
							version,
						})
					}
				} else {
					missings.push({
						packageId,
						version,
					})
				}
			}
			commit('updateProcessTree');

			if (missings.length > 0) {
				setTimeout(function () {
					events.$emit('show-missing-packages', missings);
				}, 500);
			}

			return missings;
		},
		async fillLastVersion({dispatch, state, getters}) {
			const dependencies = getters.dependencies;
			for (let group in state.packageInfos) {
				for (let packageInfo of state.packageInfos[group]) {
					const {package_id, versions} = packageInfo;
					if (!dependencies[package_id]) {
						dependencies[package_id] = semverMaxSatisfying(versions, '*');
					}
				}
			}
			await dispatch('applyDependencies', dependencies);
		}
	},
};

export function packageExists(packageMetas, packageId, version) {
	let exists;
	let versions = packageMetas.find(item => item.id === packageId);
	if (versions) {
		exists = versions[version];
	}
	return exists;
}

function groupProcesses(processes, filterFunc) {
	const result = [];
	for (let pid in processes) {
		let process = processes[pid];

		if (filterFunc(process)) {
			const groupName = process.group || 'others';
			let group = result.find(group => group.name === groupName);
			if (!group) {
				group = {name: groupName, children: []};
				result.push(group);
			}
			group.children.push(process);
		}
	}
	return result;
}
