/**
 * activation.js — Spreading activation utilities for el-ui.
 *
 * This module provides standalone activation functions that operate on any
 * Graph instance. Useful for implementing custom reactive behavior outside
 * of the standard component setState() lifecycle.
 *
 * The activation model mirrors engram-core/src/activation.rs exactly:
 *   - Best-first BFS from seed nodes
 *   - Multiplicative strength: parent_strength × edge.weight × target.importance
 *   - Winner-take-most: strongest path to each node wins
 *   - Prune: paths below threshold are cut (models attention filter)
 */

export const PRUNE_THRESHOLD = 0.01;
export const DEFAULT_MAX_DEPTH = 3;
export const DEFAULT_LIMIT = 20;

/**
 * Run spreading activation from multiple seed nodes.
 *
 * @param {import('./graph.js').Graph} graph
 * @param {string[]} seeds  starting node IDs
 * @param {object} opts
 * @param {number} [opts.maxDepth=3]
 * @param {number} [opts.limit=20]
 * @param {number} [opts.pruneThreshold=0.01]
 * @returns {Array<{nodeId: string, strength: number, hops: number, node: object}>}
 */
export function spreadActivation(graph, seeds, {
    maxDepth = DEFAULT_MAX_DEPTH,
    limit = DEFAULT_LIMIT,
    pruneThreshold = PRUNE_THRESHOLD,
} = {}) {
    /** @type {Map<string, {strength: number, hops: number}>} */
    const bestStrength = new Map();
    /** @type {Array<{id: string, strength: number, hops: number}>} */
    const queue = [];

    const seedSet = new Set(seeds);

    for (const seed of seeds) {
        const node = graph.get(seed);
        if (!node) continue;
        queue.push({ id: seed, strength: 1.0, hops: 0 });
        bestStrength.set(seed, { strength: 1.0, hops: 0 });
    }

    while (queue.length > 0) {
        // Best-first: pop the highest-strength candidate
        let bestIdx = 0;
        for (let i = 1; i < queue.length; i++) {
            if (queue[i].strength > queue[bestIdx].strength) bestIdx = i;
        }
        const { id, strength, hops } = queue.splice(bestIdx, 1)[0];

        if (hops >= maxDepth) continue;

        const node = graph.get(id);
        if (!node) continue;

        for (const edgeId of node.edges) {
            const edge = graph.edges.get(edgeId);
            if (!edge) continue;

            const target = graph.get(edge.to);
            if (!target) continue;

            const targetStrength = strength * edge.weight * Math.max(0, target.importance);

            if (targetStrength <= pruneThreshold) continue;

            const prev = bestStrength.get(edge.to);
            if (!prev || targetStrength > prev.strength) {
                const nextHops = hops + 1;
                bestStrength.set(edge.to, { strength: targetStrength, hops: nextHops });
                queue.push({ id: edge.to, strength: targetStrength, hops: nextHops });
            }
        }
    }

    // Collect results — exclude seeds, sort by strength
    const results = [];
    for (const [nodeId, { strength, hops }] of bestStrength) {
        if (seedSet.has(nodeId)) continue;
        const node = graph.get(nodeId);
        if (node) results.push({ nodeId, strength, hops, node });
    }

    results.sort((a, b) => b.strength - a.strength);
    return results.slice(0, limit);
}

/**
 * Compute activation strength between two specific nodes.
 * Returns 0 if no path exists within maxDepth.
 *
 * @param {import('./graph.js').Graph} graph
 * @param {string} fromId
 * @param {string} toId
 * @param {number} [maxDepth=3]
 * @returns {number}
 */
export function activationStrength(graph, fromId, toId, maxDepth = DEFAULT_MAX_DEPTH) {
    const results = spreadActivation(graph, [fromId], { maxDepth });
    const found = results.find(r => r.nodeId === toId);
    return found ? found.strength : 0;
}

/**
 * Find all nodes reachable from a seed within the activation surface.
 * Unlike spreadActivation(), this includes the seed itself.
 *
 * @param {import('./graph.js').Graph} graph
 * @param {string} seedId
 * @param {number} [maxDepth=3]
 * @returns {Set<string>}
 */
export function reachableNodes(graph, seedId, maxDepth = DEFAULT_MAX_DEPTH) {
    return graph.activate(seedId, maxDepth);
}
