// Spintax Resolver - Handles {A|B|C} syntax
import crypto from 'crypto';

export interface SpintaxChoice {
    path: string;
    chosen: string;
    allOptions: string[];
}

/**
 * Resolves spintax syntax {A|B|C} to a single choice
 * Tracks which choices were made for uniqueness
 */
export class SpintaxResolver {
    private choices: SpintaxChoice[] = [];
    private seed: string;

    constructor(seed?: string) {
        this.seed = seed || crypto.randomBytes(16).toString('hex');
    }

    /**
     * Resolve all spintax in text
     */
    resolve(text: string): string {
        let resolved = text;
        let iteration = 0;

        // Handle nested spintax with multiple passes
        while (resolved.includes('{') && resolved.includes('}') && iteration < 10) {
            resolved = this.resolvePass(resolved, iteration);
            iteration++;
        }

        return resolved;
    }

    private resolvePass(text: string, iteration: number): string {
        const regex = /\{([^{}]+)\}/g;
        let result = text;
        let match;
        let offset = 0;

        while ((match = regex.exec(text)) !== null) {
            const fullMatch = match[0];
            const options = match[1].split('|');

            // Deterministic choice based on seed + position
            const choiceIndex = this.getChoiceIndex(match.index + iteration, options.length);
            const chosen = options[choiceIndex].trim();

            // Track the choice
            this.choices.push({
                path: `pos_${match.index}_iter_${iteration}`,
                chosen,
                allOptions: options
            });

            // Replace in result
            const beforeMatch = result.substring(0, match.index + offset);
            const afterMatch = result.substring(match.index + offset + fullMatch.length);
            result = beforeMatch + chosen + afterMatch;

            offset += chosen.length - fullMatch.length;
        }

        return result;
    }

    private getChoiceIndex(position: number, optionsCount: number): number {
        const hash = crypto.createHash('sha256')
            .update(`${this.seed}_${position}`)
            .digest('hex');
        return parseInt(hash.substring(0, 8), 16) % optionsCount;
    }

    /**
     * Get all choices made during resolution
     */
    getChoices(): SpintaxChoice[] {
        return this.choices;
    }

    /**
     * Generate hash of choices for uniqueness checking
     */
    getChoicesHash(): string {
        const choiceString = this.choices
            .map(c => c.chosen)
            .join('::');
        return crypto.createHash('sha256')
            .update(choiceString)
            .digest('hex')
            .substring(0, 32);
    }

    /**
     * Reset for new resolution
     */
    reset(newSeed?: string) {
        this.choices = [];
        if (newSeed) this.seed = newSeed;
    }
}

/**
 * Expand variables like {{CITY}} in text
 */
export function expandVariables(text: string, variables: Record<string, string>): string {
    let result = text;

    for (const [key, value] of Object.entries(variables)) {
        const regex = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
        result = result.replace(regex, value);
    }

    return result;
}

/**
 * Generate cartesian product of pipe-separated values
 * Example: { CITY: "A|B", STATE: "X|Y" } => 4 combinations
 */
export function generateCartesianProduct(
    variables: Record<string, string>
): Array<Record<string, string>> {
    const keys = Object.keys(variables);
    const values = keys.map(key => variables[key].split('|').map(v => v.trim()));

    function* cartesian(arrays: string[][]): Generator<string[]> {
        if (arrays.length === 0) {
            yield [];
            return;
        }

        const [first, ...rest] = arrays;
        for (const value of first) {
            for (const combo of cartesian(rest)) {
                yield [value, ...combo];
            }
        }
    }

    const results: Array<Record<string, string>> = [];
    for (const combo of cartesian(values)) {
        const obj: Record<string, string> = {};
        keys.forEach((key, i) => {
            obj[key] = combo[i];
        });
        results.push(obj);
    }

    return results;
}
