import {MixinUserChooseImporter} from "./ImportList.js";
import {Vetools} from "./Vetools.js";
import {Config} from "./Config.js";
import {UtilList2} from "./UtilList2.js";
import {UtilDataSource} from "./UtilDataSource.js";
import {PageFilterClassFeatures} from "./PageFilterClassFeatures.js";
import {DataConverterClassSubclassFeature} from "./DataConverterClassSubclassFeature.js";
import {ImportListFeature} from "./ImportListFeature.js";
import {UtilApplications} from "./UtilApplications.js";
import {Charactermancer_Class_FeatureOptionsSelect, Charactermancer_Class_Util} from "./UtilCharactermancerClass.js";

class ImportListClassFeature extends ImportListFeature {
	// region External
	static init () {
		this._initCreateSheetItemHook({
			prop: "classFeature",
			importerName: "Class Feature",
		});
		this._initCreateSheetItemHook({
			prop: "subclassFeature",
			importerName: "Subclass Feature",
		});
	}
	// endregion

	constructor (externalData, applicationOptsOverride, subclassOptsOverride) {
		applicationOptsOverride = applicationOptsOverride || {}
		subclassOptsOverride = subclassOptsOverride || {};
		super(
			{
				title: "Import Class & Subclass Features",
				...applicationOptsOverride,
			},
			externalData,
			{
				props: ["classFeature", "subclassFeature"],
				titleSearch: "class and subclass features",
				sidebarTab: "items",
				gameProp: "items",
				defaultFolderPath: ["Class & Subclass Features"],
				folderType: "Item",
				fnListSort: PageFilterClassFeatures.sortClassFeatures,
				listInitialSortBy: "className",
				pageFilter: new PageFilterClassFeatures(),
				page: UrlUtil.PG_CLASS_SUBCLASS_FEATURES,
				isPreviewable: true,
				isDedupable: true,
				...subclassOptsOverride,
			},
			{
				titleLog: "class/subclass feature",
			},
		);

		this._contentDereferenced = null;
	}

	async pGetSources () {
		return [
			new UtilDataSource.DataSourceSpecial(
				Config.get("ui", "isStreamerMode") ? "SRD" : "5etools",
				Vetools.pGetClassSubclassFeatures,
				{
					cacheKey: `5etools-class-subclass-features`,
					filterTypes: [UtilDataSource.SOURCE_TYP_OFFICIAL_ALL],
					isUseProps: true,
					isDefault: true,
					pPostLoad: loadedData => this.constructor._pPostLoad(loadedData),
				},
			),
			new UtilDataSource.DataSourceUrl(
				"Custom URL",
				"",
				{
					filterTypes: [UtilDataSource.SOURCE_TYP_CUSTOM],
					pPostLoad: loadedData => this.constructor._pPostLoad(loadedData),
				},
			),
			new UtilDataSource.DataSourceFile(
				"Upload File",
				{
					filterTypes: [UtilDataSource.SOURCE_TYP_CUSTOM],
					pPostLoad: loadedData => this.constructor._pPostLoad(loadedData),
				},
			),
			...([...await Vetools.pGetHomebrewSources("classFeature"), ...await Vetools.pGetHomebrewSources("subclassFeature")]).map(({name, url}) => new UtilDataSource.DataSourceUrl(
				name,
				url,
				{
					filterTypes: [UtilDataSource.SOURCE_TYP_BREW],
					pPostLoad: loadedData => this.constructor._pPostLoad(loadedData),
				},
			)),
		]
	}

	static _pPostLoad (loadedData) {
		return loadedData.filter(it => it.name.toLowerCase() !== "ability score improvement");
	}

	getData () {
		return {
			isRadio: this._isRadio,
			isPreviewable: this._isPreviewable,
			titleButtonRun: this._titleButtonRun,
			titleSearch: this._titleSearch,
			cols: [
				{
					name: "Name",
					width: 5,
					field: "name",
				},
				{
					name: "Class",
					width: 2,
					field: "className",
				},
				{
					name: "Subclass",
					width: 2,
					field: "subclassShortName",
				},
				{
					name: "Level",
					width: 1,
					field: "level",
					rowClassName: "text-center",
				},
				{
					name: "Source",
					width: 1,
					field: "source",
					titleProp: "sourceLong",
					displayProp: "sourceShort",
					classNameProp: "sourceClassName",
					rowClassName: "text-center",
				},
			],
			rows: this._content.map((it, ix) => {
				this._pageFilter.constructor.mutateForFilters(it);

				return {
					name: it.name,
					className: it.className,
					subclassShortName: it.subclassShortName,
					level: it.level,
					source: it.source,
					sourceShort: Parser.sourceJsonToAbv(it.source),
					sourceLong: Parser.sourceJsonToFull(it.source),
					sourceClassName: Parser.sourceJsonToColor(it.source),
					ix,
				};
			}),
		};
	}

	_activateListeners_absorbListItems () {
		this._list.doAbsorbItems(
			this._content,
			{
				fnGetName: it => it.name,
				// values used for sorting/search
				fnGetValues: it => ({
					source: it.source,
					className: it.className,
					subclassShortName: it.subclassShortName || "",
					level: it.level,
					hash: UrlUtil.URL_TO_HASH_BUILDER[this._page](it),
				}),
				fnGetData: UtilList2.absorbFnGetData,
				fnBindListeners: it => this._isRadio
					? UtilList2.absorbFnBindListenersRadio(this._list, it)
					: UtilList2.absorbFnBindListeners(this._list, it),
			},
		);
	}

	async pSetContent (val) {
		await super.pSetContent(val);
		this._contentDereferenced = await Promise.all(val.map(feature => DataConverterClassSubclassFeature.pGetDereferencedClassSubclassFeatureItem(feature)));
	}

	_activateListeners_initPreviewButton (item, btnShowHidePreview) {
		ListUiUtil.bindPreviewButton(this._page, this._contentDereferenced, item, btnShowHidePreview);
	}

	/**
	 * @param feature
	 * @param importOpts Options object.
	 * @param [importOpts.isTemp] if the item should be temporary, and displayed.
	 * @param [importOpts.isDataOnly] If this import should simply return the data, rather than import anything.
	 * @param [importOpts.filterValues] Saved filter values to be used instead of our own.
	 * @param [importOpts.isCharactermancer]
	 *
	 * @param [importOpts.isLeaf] If this feature should be loaded directly, without any sub-loading applied.
	 * @param [importOpts.isSkippableLeaf] If this leaf feature's entries can (optionally) be skipped.
	 *
	 * @param [importOpts.isPreLoadedFeature] If the feature has already been loaded by e.g. the Class Importer.
	 * @param [importOpts.featureEntriesPageFilter]
	 * @param [importOpts.featureEntriesPageFilterValues]
	 * @param [importOpts.existingFeatureChecker]
	 * @param [importOpts.spellcastingAbilityAbv] Optional spellcasting ability for attached "additional spells."
	 *
	 * @return {*}
	 */
	async pImportEntry (feature, importOpts) {
		importOpts = importOpts || {};

		// If there's no actor, simply de-reference all the references and use the basic import flow.
		if (!this._actor) {
			const dereferenced = await DataConverterClassSubclassFeature.pGetDereferencedClassSubclassFeatureItem(feature);
			return super.pImportEntry(dereferenced, importOpts);
		}

		// If we're a "leaf" feature, there is nothing more to load, so use the basic import flow.
		if (importOpts.isLeaf) {
			// If this is is a stub feature that just contains more options, avoid importing it.
			if (importOpts.isSkippableLeaf && feature.entries?.[0]?.type === "options" && feature.entries?.length === 1) return {status: UtilApplications.TASK_EXIT_SKIPPED_OTHER};

			return super.pImportEntry(feature, importOpts);
		}

		let importListOptFeature = null;

		const pageFilter = importOpts.isPreLoadedFeature
			? importOpts.featureEntriesPageFilter
			: this._pageFilter;
		const filterValues = importOpts.isPreLoadedFeature
			? (importOpts.featureEntriesPageFilterValues)
			: (importOpts.filterValues || this._pageFilter.filterBox.getValues());

		// If we're using the advanced flow, dredge up all the loadable features
		let allFeatures;
		if (importOpts.isPreLoadedFeature) {
			allFeatures = [feature];
		} else {
			const wrappedFeature = await DataConverterClassSubclassFeature.pGetInitClassSubclassFeatureLoadeds(feature);
			allFeatures = [wrappedFeature];
		}

		// Filter down the `.loadeds` according to our current filter settings
		allFeatures = Charactermancer_Class_Util.getFilteredFeatures(
			allFeatures,
			pageFilter,
			filterValues,
		);

		// (Should never occur)
		if (!allFeatures.length) return {status: UtilApplications.TASK_EXIT_CANCELLED};

		allFeatures = Charactermancer_Class_Util.getImportableFeatures(allFeatures);

		Charactermancer_Class_Util.doApplyFilterToFeatureEntries(
			allFeatures,
			pageFilter,
			filterValues,
		);

		const allFeaturesGrouped = Charactermancer_Class_Util.getFeaturesGroupedByOptionsSet(allFeatures);
		const actorUpdate = {};

		for (const topLevelFeatureMeta of allFeaturesGrouped) {
			const {topLevelFeature, optionsSets} = topLevelFeatureMeta;

			for (let ixOptionSet = 0; ixOptionSet < optionsSets.length; ++ixOptionSet) {
				const optionsSet = optionsSets[ixOptionSet];

				const formDataOptionSet = await Charactermancer_Class_FeatureOptionsSelect.pGetUserInput({
					actor: this._actor,
					optionsSet,
					level: topLevelFeature.level,
					existingFeatureChecker: importOpts.existingFeatureChecker,
				});

				if (!formDataOptionSet) return {status: UtilApplications.TASK_EXIT_CANCELLED};
				if (formDataOptionSet === VeCt.SYM_UI_SKIP) continue;

				for (const loaded of formDataOptionSet.data?.features) {
					const {entity, type} = loaded;

					// Remove properties which are handled in the options selection component
					const cpyEntity = MiscUtil.copy(entity);
					delete cpyEntity.additionalSpells;

					switch (type) {
						case "classFeature":
						case "subclassFeature": {
							const importResult = await this.pImportEntry(cpyEntity, {...importOpts, isLeaf: true, isSkippableLeaf: ixOptionSet === 0 && optionsSets.length > 1});
							if (importResult?.status === UtilApplications.TASK_EXIT_CANCELLED) return {status: UtilApplications.TASK_EXIT_CANCELLED};
							break;
						}
						case "optionalfeature": {
							if (!importListOptFeature) {
								const {ImportListOptionalFeature} = await import("./ImportListOptionalFeature.js");
								importListOptFeature = new ImportListOptionalFeature({actor: this._actor});
								await importListOptFeature.pInit();
							}

							const nxtOpts = {...importOpts};
							delete nxtOpts.filterValues;
							delete nxtOpts.existingFeatureChecker;
							delete nxtOpts.isLeaf;

							const importResult = await importListOptFeature.pImportEntry(cpyEntity, nxtOpts);
							if (importResult?.status === UtilApplications.TASK_EXIT_CANCELLED) return {status: UtilApplications.TASK_EXIT_CANCELLED};
						}
					}

					// Track which features we add as we import them, to warn the user against double-dipping e.g. fighting styles
					if (importOpts.existingFeatureChecker) importOpts.existingFeatureChecker.addImportFeature(loaded.page, loaded.source, loaded.hash);
				}

				Charactermancer_Class_FeatureOptionsSelect.doApplyProficiencyFormDataToActorUpdate(
					this._actor,
					actorUpdate,
					formDataOptionSet,
				);

				await Charactermancer_Class_FeatureOptionsSelect.pDoApplyAdditionalSpellsFormDataToActor({
					actor: this._actor,
					formData: formDataOptionSet,
					abilityAbv: importOpts.spellcastingAbilityAbv,
				});
			}
		}

		await this._pDoMergeAndApplyActorUpdate(actorUpdate);

		return {imported: [{name: feature.name, actor: this._actor}], status: UtilApplications.TASK_EXIT_COMPLETE};
	}

	static async _pGetEntityItem (actor, feature) {
		return DataConverterClassSubclassFeature.pGetClassSubclassFeatureItem(feature, {actor});
	}

	static async _pGetSideData (actor, feature) {
		return DataConverterClassSubclassFeature.pGetSideData(feature);
	}

	static async _pHasSideLoadedEffects (actor, feature) {
		return DataConverterClassSubclassFeature.pHasClassSubclassSideLoadedEffects(actor, feature);
	}

	static async _pGetItemEffects (actor, feature, importedEmbed, dataBuilderOpts) {
		return DataConverterClassSubclassFeature.pGetClassSubclassFeatureItemEffects(
			actor,
			feature,
			importedEmbed,
			{
				additionalData: {
					import: {chosenAbilityScoreIncrease: dataBuilderOpts.chosenAbilityScoreIncrease},
				},
			},
		);
	}

	async _pMutActorUpdateFeature (feature, actUpdate, dataBuilderOpts) {
		await DataConverterClassSubclassFeature.pMutActorUpdateClassSubclassFeatureItem(this._actor, actUpdate, feature, dataBuilderOpts);
	}

	_pImportEntry_pImportToDirectoryGeneric_pGetImportableData (it, getItemOpts) {
		return DataConverterClassSubclassFeature.pGetClassSubclassFeatureItem(it, {actor: this._actor, ...getItemOpts});
	}
}

ImportListClassFeature.UserChoose = class extends MiscUtil.mix(ImportListClassFeature).with(MixinUserChooseImporter) {
	constructor (externalData) {
		super(
			externalData,
			{
				title: "Select Class or Subclass Feature",
			},
			{
				titleButtonRun: "Select",
			},
		);
	}
};

export {ImportListClassFeature};
