import {UtilApplications} from "./UtilApplications.js";
import {Config} from "./Config.js";
import {DataConverter} from "./DataConverter.js";
import {UtilCompendium} from "./UtilCompendium.js";
import {Vetools} from "./Vetools.js";

class DataConverterTable {
	/**
	 * @param tg
	 * @param rollableTables Foundry entities.
	 * @param [opts] Options object.
	 * @param [opts.isAddPermission]
	 */
	static getTableGroupJournal (tg, rollableTables, opts) {
		opts = opts || {};

		const entry = {
			type: "list",
			items: rollableTables.map(it => `${DataConverter.SYM_AT}RollTable[${it.id}]{${it.caption || it.name}}`),
		};

		const out = {
			name: UtilApplications.getCleanEntityName(tg.name),
			permission: {default: 0},
			entryTime: Date.now(),
			content: `<div>
				<p>The following tables belong to this group:</p>
				${Renderer.get().setFirstSection(true).render(entry).replace(new RegExp(DataConverter.SYM_AT, "g"), "@")}
				</div>`,
		};

		if (opts.isAddPermission) out.permission = {default: Config.get("importTable", "permissions")};

		return out;
	}

	/**
	 * @param tbl
	 * @param [opts] Options object.
	 * @param [opts.isAddPermission]
	 */
	static pGetTableRollableTable (tbl, opts) {
		opts = opts || {};

		if (Renderer.getTableRollMode(tbl)) return this._pGetTableRollableTable_pRollTable(tbl, opts);
		else return this._pGetTableRollableTable_pOtherTable(tbl, opts);
	}

	static async _pGetTableRollableTable_pRollTable (tbl, opts) {
		opts = opts || {};

		const toRoll = (tbl.colLabels[0] || "").trim().replace(/{@dice ([^}]+)}/, "$1");

		const out = {
			name: UtilApplications.getCleanEntityName(tbl.name),
			formula: toRoll,
			results: await tbl.rows.pSerialAwaitMap(async (r, i) => {
				let rangeLow;
				let rangeHigh;

				// format: "95-00" or "12"
				const m = /^(\d+)([-\u2013](\d+))?$/.exec(String(r[0]).trim());
				if (m) {
					if (m[1] && !m[2]) {
						rangeLow = Number(m[1]);
						// Convert "00" to "100"
						if (rangeLow === 0) rangeLow = this._getHighestRoll(toRoll);
						rangeHigh = rangeLow;
					} else {
						rangeLow = Number(m[1]);
						rangeHigh = Number(m[3]);

						// Convert "95-00" to "95-100"
						if (rangeHigh < rangeLow) {
							rangeHigh = this._getHighestRoll(toRoll);
						}
					}
				} else {
					// format: "12+"
					const m = /^(\d+)\+$/.exec(r[0]);
					rangeLow = Number(m[1]);
					rangeHigh = rangeLow;
				}

				return {
					id: i + 1, // this is required
					flags: {},
					type: 0, // "Text" type
					text: await this._pGetResultText(r.slice(1)),
					img: "icons/svg/d20-black.svg",
					weight: 1,
					range: [
						rangeLow,
						rangeHigh,
					],
					drawn: false,
				};
			}),
			replacement: true,
			displayRoll: true,
		};

		if (opts.isAddPermission) out.permission = {default: Config.get("importTable", "permissions")};

		return out;
	}

	static async _pGetResultText (cells) {
		const walker = MiscUtil.getAsyncWalker({keyBlacklist: MiscUtil.GENERIC_WALKER_ENTRIES_KEY_BLACKLIST});

		const tagMetas = Object.keys(Parser.TAG_TO_DEFAULT_SOURCE).map(tag => ({tag, re: RegExp(`{@${tag} ([^}]+)}`, "g")}));

		const cellsMut = await walker.pWalk(
			MiscUtil.copy(cells),
			{
				string: async str => {
					for (const {tag, re} of tagMetas) str = await this._pGetResultText_pReplace({str, tag, re});
					return Renderer.stripTags(this._getFoundryDiceTagged(str));
				},
			},
		);

		return cellsMut
			.map(it => Renderer.get().setFirstSection(true).render(it))
			.join(" | ");
	}

	static _getHighestRoll (toRoll) {
		const mFirstNum = /\d+/.exec(toRoll); // catch e.g. d1000's
		if (mFirstNum) return Number(mFirstNum[0]);
		return 100; // Fall back on d100, as this is the most common cause
	}

	/** Async string find-replace. */
	static async _pGetResultText_pReplace ({str, re, tag}) {
		let m;
		while ((m = re.exec(str))) {
			const prefix = str.slice(0, m.index);
			const suffix = str.slice(re.lastIndex);
			const replacement = await this._pGetResultText_pGetReplacement({tag, m});
			str = `${prefix}${replacement}${suffix}`;
			re.lastIndex = prefix.length + replacement.length;
		}
		return str;
	}

	static async _pGetResultText_pGetReplacement ({tag, m}) {
		const pipeText = m[1];
		return `@${tag}[${pipeText}]`;
	}

	static async _pGetTableRollableTable_pOtherTable (tbl, opts) {
		opts = opts || {};

		const out = {
			name: UtilApplications.getCleanEntityName(tbl.name),
			formula: `1d${tbl.rows ? tbl.rows.length : "1"}`,
			results: await tbl.rows.pSerialAwaitMap(async (r, i) => {
				r = r.row || r;
				return {
					id: i + 1, // this is required
					flags: {},
					type: 0, // "Text" type
					text: await this._pGetResultText(r),
					img: "icons/svg/d20-black.svg",
					weight: 1,
					range: [
						i + 1,
						i + 1,
					],
					drawn: false,
				};
			}),
			replacement: true,
			displayRoll: true,
		};

		if (opts.isAddPermission) out.permission = {default: Config.get("importTable", "permissions")};

		return out;
	}

	static _getFoundryDiceTagged (str) {
		return str.replace(/{@(?:dice|damage) ([^}]+)}/gi, (...m) => {
			const [rollText, displayText] = Renderer.splitTagByPipe(m[1]);
			const rollTextClean = Vetools.getCleanDiceString(rollText);
			return `[[/r ${rollTextClean}]]${displayText && rollTextClean.toLowerCase().trim() !== displayText.toLowerCase().trim() ? ` (${displayText})` : ""}`;
		});
	}
}

export {DataConverterTable};
