import {ImportList} from "./ImportList.js";
import {Vetools} from "./Vetools.js";
import {LGT} from "./Util.js";
import {DataConverterItem} from "./DataConverterItem.js";
import {Config} from "./Config.js";
import {UtilList2} from "./UtilList2.js";
import {UtilApplications} from "./UtilApplications.js";
import {UtilDataSource} from "./UtilDataSource.js";

class ImportListItem extends ImportList {
	// region External
	static init () {
		this._initCreateSheetItemHook({
			prop: "item",
			importerName: "Item",
		});
	}
	// endregion

	constructor (externalData) {
		externalData = externalData || {};
		super(
			{title: "Import Items"},
			externalData,
			{
				namespace: "item",
				titleSearch: "items",
				sidebarTab: "items",
				gameProp: "items",
				defaultFolderPath: ["Items"],
				folderType: "Item",
				fnListSort: PageFilterItems.sortItems,
				pageFilter: new PageFilterItems(),
				page: UrlUtil.PG_ITEMS,
				isPreviewable: true,
				isDedupable: true,
			},
		);

		this._fluffLookup = null;
	}

	async pGetSources () {
		return this.constructor.pGetSources();
	}

	static async pGetSources () {
		const nonVetoolsOpts = {
			pPostLoad: async json => {
				return Renderer.item.pGetItemsFromHomebrew(json);
			},
		};

		return [
			new UtilDataSource.DataSourceSpecial(
				Config.get("ui", "isStreamerMode") ? "SRD" : "5etools",
				async () => (await Vetools.pGetItems()).item,
				{
					cacheKey: "5etools-items",
					filterTypes: [UtilDataSource.SOURCE_TYP_OFFICIAL_ALL],
					isDefault: true,
				},
			),
			new UtilDataSource.DataSourceUrl(
				"Custom URL",
				"",
				{
					...nonVetoolsOpts,
					filterTypes: [UtilDataSource.SOURCE_TYP_CUSTOM],
				},
			),
			new UtilDataSource.DataSourceFile(
				"Upload File",
				{
					...nonVetoolsOpts,
					filterTypes: [UtilDataSource.SOURCE_TYP_CUSTOM],
				},
			),
			...(await Vetools.pGetHomebrewSources("item")).map(({name, url}) => new UtilDataSource.DataSourceUrl(
				name,
				url,
				{
					...nonVetoolsOpts,
					filterTypes: [UtilDataSource.SOURCE_TYP_BREW],
				},
			)),
		]
	}

	getData () {
		return {
			isPreviewable: this._isPreviewable,
			titleButtonRun: this._titleButtonRun,
			titleSearch: this._titleSearch,
			cols: [
				{
					name: "Name",
					width: 3,
					field: "name",
				},
				{
					name: "Type",
					width: "3-2",
					field: "type",
				},
				{
					name: "Cost",
					width: "1-4",
					field: "cost",
					rowClassName: "text-center",
				},
				{
					name: "Weight",
					width: "1-4",
					field: "weight",
					rowClassName: "text-center",
				},
				{
					name: "A.",
					width: "0-5",
					field: "attunement",
					rowClassName: "text-center",
				},
				{
					name: "Rarity",
					width: "1-5",
					field: "rarity",
					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);

				// region Re-used in fnGetValues
				it._vType = it._typeListText.join(", ").uppercaseFirst();
				// endregion

				return {
					name: it.name,
					type: it._vType,
					cost: it.value || it.valueMult ? Parser.itemValueToFull(it, {isShortForm: true}).replace(/ +/g, "\u00A0") : "\u2014",
					weight: Parser.itemWeightToFull(it, true) || "\u2014",
					rarity: (it.rarity || "Unknown").toTitleCase(),
					attunement: it._attunementCategory === "No" ? "" : "×",
					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,
					type: it._vType,
					cost: it.value || 0,
					rarity: it.rarity,
					attunement: it._attunementCategory !== "No",
					weight: Parser.weightValueToNumber(it.weight),
					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 pInit () {
		await super.pInit();

		this._fluffLookup = {};

		const fluff = await Vetools.pGetItemFluff();
		fluff.itemFluff.forEach(it => (this._fluffLookup[it.source] = this._fluffLookup[it.source] || {})[it.name] = it);

		await Renderer.item.populatePropertyAndTypeReference();
	}

	getFolderPathMeta () {
		return {
			...super.getFolderPathMeta(),
			rarity: {
				label: "Rarity",
				getter: it => ((!it.rarity || it.rarity === "Unknown") ? "Unknown Rarity" : it.rarity).toTitleCase(),
			},
			type: {
				label: "Type",
				getter: it => {
					if (it.type) return Renderer.item.getItemTypeName(it.type).toTitleCase();
					else if (it.typeText) return it.typeText;
					else if (it.wondrous) return "Wondrous Item";
					else if (it.poison) return "Poison";
					else return "Unknown Type";
				},
			},
		}
	}

	/**
	 * @param item
	 * @param importOpts Options object.
	 * @param [importOpts.isTemp] if the item should be temporary, and displayed.
	 * @param [importOpts.quantity]
	 * @param [importOpts.filterValues] Saved filter values to be used instead of our own.
	 * @param [importOpts.isDataOnly] If this import should simply return the data, rather than import anything.
	 * @return *
	 */
	async pImportEntry (item, importOpts) {
		importOpts = importOpts || {};

		console.log(...LGT, `Importing item "${item.name}" (from "${Parser.sourceJsonToAbv(item.source)}")`);

		const fluff = item.fluff || MiscUtil.get(this._fluffLookup, item.source, item.name);
		const opts = {fluff};
		if (importOpts.quantity != null) opts.quantity = importOpts.quantity;

		await this._pImportEntry_pDoLoadBrewMeta(item);

		if (importOpts.isDataOnly) return {imported: [await this._pImportEntry_pImportToDataOnly(item, importOpts, opts)], status: UtilApplications.TASK_EXIT_COMPLETE_DATA_ONLY};

		if (importOpts.isTemp) return this._pImportEntry_pImportToDirectoryGeneric(item, importOpts, opts);
		else if (this._actor) return this._pImportEntry_pImportToActor(item, importOpts, opts);
		else return this._pImportEntry_pImportToDirectoryGeneric(item, importOpts, opts);
	}

	/**
	 * When importing an item, if it is homebrew drag-dropped from the items directory, we may not have initialised
	 * metadata (item properties, etc.) for it. Ensure we do so before attempting to process it further.
	 */
	async _pImportEntry_pDoLoadBrewMeta (item) {
		if (BrewUtil.hasSourceJson(item.source)) return;
		if (SourceUtil.isSiteSource(item.source)) return;

		if (!Vetools.HOMEBREW_INDEX__SOURCE[item.source]) return;
		const brewUrl = DataUtil.brew.getFileUrl(Vetools.HOMEBREW_INDEX__SOURCE[item.source], Config.get("import", "baseBrewUrl"));
		await BrewUtil.pDoHandleBrewJson((await DataUtil.loadJSON(brewUrl)), UrlUtil.getCurrentPage());

		// This adds the properties/etc to the items cache
		await Renderer.item.pGetItemsFromHomebrew((await DataUtil.loadJSON(brewUrl)));
	}

	async _pImportEntry_pImportToDataOnly (item, importOpts, opts) {
		if (!this._actor || !this._isPackSplitImport(item)) return [await DataConverterItem.pGetItemItem(item)];
		return this._pGetPackItems(item, importOpts, opts);
	}

	_isPackSplitImport (item) {
		return item.packContents
			&& Config.get("importItem", "isSplitPacksActor")
			&& (!item.atomicPackContents || Config.get("importItem", "isSplitAtomicPacksActor"));
	}

	async _pImportEntry_pImportToActor (item, importOpts, opts) {
		opts = {...opts, isActorItem: true};

		opts.filterValues = importOpts.filterValues;

		// Pass actor ammo items in, so bows/etc. can link to ammo consumables
		opts.sheetItemsAmmo = this._pImportEntry_getSheetItemsAmmo(item);

		if (this._isPackSplitImport(item)) {
			await this._pImportEntry_pImportToActor_pImportPackItem(item, importOpts, opts);
		} else {
			const itemData = await DataConverterItem.pGetItemItem(item, opts);
			await this._pImportEntry_pImportToActor_pAddItem(itemData);
		}

		if (this._actor.isToken) this._actor.sheet.render();

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

	async _pGetPackItems (item, importOpts, opts) {
		const packErrors = [];

		const packContentsItems = await Promise.all(item.packContents.map(async it => {
			const quantity = it.quantity || 1;
			if (it.item || typeof it === "string") {
				let [name, source] = (it.item || it).split("|");
				if (!source) source = SRC_DMG;
				const hash = UrlUtil.URL_TO_HASH_BUILDER[UrlUtil.PG_ITEMS]({name, source});

				const packItem = await Renderer.hover.pCacheAndGet(UrlUtil.PG_ITEMS, source, hash);

				return DataConverterItem.pGetItemItem(packItem, {filterValues: importOpts.filterValues, ...opts, quantity});
			} else if (it.special) {
				const fauxItem = {
					name: it.special.toTitleCase(),
					type: "G",
					source: item.source,
					page: item.page,
					srd: item.srd,
				};
				Renderer.item.enhanceItem(fauxItem);

				return DataConverterItem.pGetItemItem(fauxItem, {filterValues: importOpts.filterValues, ...opts, quantity});
			} else {
				packErrors.push(`Unhandled pack contents type "${JSON.stringify(it)}"`);
			}
		}));

		if (packErrors.length) {
			ui.notifications.error(`Item "${item.name}" (from "${Parser.sourceJsonToAbv(item.source)}") could not be broken down into constituent items! ${VeCt.STR_SEE_CONSOLE}`);
			console.error(...LGT, `Error(s) when breaking ${item.name} (${item.source}) into constituent items: ${packErrors.join("; ")}`);
		}

		return packContentsItems.filter(Boolean);
	}

	async _pImportEntry_pImportToActor_pImportPackItem (item, importOpts, opts) {
		const packContentsItems = await this._pGetPackItems(item, importOpts, opts);

		// Do these sequentially, so we can set throwable usage if required
		for (const packContentsItem of packContentsItems) {
			await this._pImportEntry_pImportToActor_pAddItem(packContentsItem);
		}
	}

	async _pImportEntry_pImportToActor_pAddItem (itemData) {
		const importeds = await this._actor.createEmbeddedDocuments("Item", [itemData], {});

		const throwableSet = new Set((Config.get("importItem", "throwables") || []).map(it => it.trim().toLowerCase()));
		if (throwableSet.has((itemData.name || "").toLowerCase().trim())) {
			await this._actor.updateEmbeddedDocuments(
				"Item",
				[
					{
						_id: importeds[0].id,
						data: {
							consume: {
								type: "ammo",
								target: importeds[0].id,
								amount: 1,
							},
						},
					},
				],
			);
		}
	}

	_pImportEntry_pImportToDirectoryGeneric_pGetImportableData (it, getItemOpts) {
		return DataConverterItem.pGetItemItem(it, getItemOpts);
	}

	_pImportEntry_getSheetItemsAmmo (item) {
		if (!item.ammoType) return null;
		if (!this._actor) return null;
		return this._actor.items.filter(it => it.type === "consumable" && it.data.data.consumableType === "ammo");
	}

	static sortEntries (a, b) {
		// Ensure weapons with ammo types are always imported last.
		//   This allows us to attempt to link up weapons with an appropriate ammo type on an actor.
		if (a.ammoType && !b.ammoType) return 1;
		if (!a.ammoType && b.ammoType) return -1;
		return SortUtil.ascSortLower(a.name, b.name) || SortUtil.ascSortLower(Parser.sourceJsonToFull(a.source), Parser.sourceJsonToFull(b.source));
	}
}

export {ImportListItem};
