/* eslint-disable react/display-name */ import { ReactNode } from "react"; import { Dice } from "../dice"; import { sum } from "../utils/sum"; import { DiceChart } from "./DiceChart"; import { TTCQueryParser } from "./TTCQueryParser"; interface StackItem { value: any; display: ReactNode | (() => ReactNode); } export class TTCQueryResolver { private parser: TTCQueryParser | null; private context: QueryableObject | null = null; private stack: StackItem[] = []; constructor(parser?: TTCQueryParser) { this.parser = parser || null; } public setParser(parser: TTCQueryParser) { this.parser = parser; } public setContext(obj: QueryableObject) { this.context = obj; } public resolve(resolver: string, onDemand?: boolean): StackItem | undefined { try { const resList = resolver.split(","); for (const res of resList) { this.stack.push(this.parseResolver(res)); } const last = this.stack.at(-1); if (typeof last?.display === "function" && !onDemand) { last.display = last.display(); } return last; } catch (e) { return { value: e?.toString(), display: e?.toString() }; } } private parseResolver(resolver: string): StackItem { if (this.isArithmetic(resolver)) return this.solveArithmetic(resolver); if (this.isQuery(resolver)) return this.runQuery(resolver); if (Dice.isDice(resolver)) { const value = new Dice(resolver); const dice: StackItem = { value, display: () => value.toString(), }; return dice; } return { value: resolver, display: resolver }; } private isQuery(resolver: string) { return ( resolver.startsWith("?") || resolver.startsWith("_") || /^\$\d\./.test(resolver) ); } private runQuery(query: string): StackItem { if (!this.parser) throw "Parser not defined in query resolver"; if (query.startsWith("$")) { const [_, idx, q] = query.match(/^(\$\d+)\.(.*)/) || []; if (!_) throw "Detected stack query but did not match the regex"; const stackItem = this.getFromStack(idx); if (stackItem.value instanceof Dice) { return { value: query, display: this.handleDice(stackItem.value, q), }; } const [res] = this.parser.search(q, stackItem.value as QueryableObject); debugger; if (Dice.isDice(res)) { const value = new Dice(res); return { value, display: () => value.toString(), }; } return { value: res, display() { return ( this.value.render ?? this.value ?? "Error resolving query: " + query ); }, }; } // if (query.startsWith("?") || query.startsWith("_")) const res = query.startsWith("_") && this.context ? this.parser.search(query.replace("_", "^"), this.context).at(0) : this.parser.search(query.replace(/^[?_].?/, "")).at(0); if (Dice.isDice(res)) { const value = new Dice(res); return { value, display: () => value.toString(), }; } return { value: res, display() { return ( this.value.render ?? this.value ?? "Error resolving query: " + query ); }, }; } private isArithmetic(resolver: string) { return resolver.split(/\+|\/|-|\*|\^/).filter((e) => !!e).length > 1; } private solveArithmetic(resolver: string): StackItem { const [n1, op, n2] = resolver .match(/(\$?\d+)([+\-*\/^])(\$?\d+)/) ?.slice(1) || ["", "+", ""]; let num1: number = Number(n1), num2: number = Number(n2); if (n1.startsWith("$")) { const result = this.getFromStack(n1).value; num1 = result instanceof Dice ? result.roll().total : Number(result); } if (n2.startsWith("$")) { const result = this.getFromStack(n1).value; num2 = result instanceof Dice ? result.roll().total : Number(result); } const thing: StackItem = { value: () => { switch (op) { case "+": return num1 + num2; case "-": return num1 - num2; case "*": return num1 * num2; case "/": return num1 / num2; case "^": return num1 ^ num2; default: throw "Arithmetic detected but no proper operator assigned"; } }, display() { return typeof this.value === "function" ? this.value() : this.value; }, }; return thing; } public getFromStack(stackIndex: string): StackItem { const i = Number(stackIndex.replace("$", "")); const val = this.stack[i]; return val; } private handleDice(dice: Dice, query: string) { const [method, n] = query.split(":"); let num = Number(n); // if (n && n.startsWith("$")) num = this.getFromStack(n); switch (method) { case "roll": return () => dice.roll.apply(dice).total; case "rollAvg": return () => dice.rollAvg.apply(dice); case "rollTimes": return () => dice.rollTimes.apply(dice, [num]); case "rollTimesAvg": return () => dice.rollTimesAvg.apply(dice, [num]); case "rollLowest": return () => dice.rollMin.apply(dice); case "rollHighest": return () => dice.rollMax.apply(dice); case "rollDropHighest": return () => sum(...dice.roll.apply(dice).results.toSorted().toSpliced(-1, 1)); case "rollDropLowest": return () => sum(...dice.roll.apply(dice).results.toSorted().toSpliced(0, 1)); case "distribution": return () => DiceChart({ dice: dice.getRollDistribution.apply(dice) }); case "distribution.normalized": return () => DiceChart({ dice: dice.getNormalizedRollDistribution.apply(dice) }); default: return () => "No valid method provided for dice"; } } }