import { isInKnowledgeSpaceScope, } from './spaces.js';
import { knowledgeNodeMatchesScope, } from './scope-records.js';
import { emitKnowledgePacketBuilt } from '../runtime/emitters/index.js';
import { DEFAULT_PACKET_BUDGET, DEFAULT_PACKET_LIMIT, estimateTokens, isSourcePastRefreshWindow, renderPacket, scoreHaystack, summarizeCompact, tokenize, trimForDetail, } from './shared.js';
export function searchKnowledge(context, query, limit = 10, scope = {}) {
    const taskTokens = tokenize(query);
    if (taskTokens.length === 0)
        return [];
    const scopeLookup = knowledgeScopeLookup(context);
    const sourceResults = context.store.listSources(Number.MAX_SAFE_INTEGER).filter((source) => isInKnowledgeSpaceScope(source, scope)).map((source) => {
        const extraction = context.store.getExtractionBySourceId(source.id);
        const haystack = [
            source.title ?? '',
            source.summary ?? '',
            source.description ?? '',
            source.sourceUri ?? '',
            source.canonicalUri ?? '',
            source.folderPath ?? '',
            source.tags.join(' '),
            extraction?.summary ?? '',
            extraction?.excerpt ?? '',
            extraction?.sections.join(' ') ?? '',
        ].join(' ').toLowerCase();
        const { score, reason } = scoreHaystack(haystack, taskTokens, []);
        return {
            kind: 'source',
            id: source.id,
            score: score + (source.status === 'indexed' ? 10 : 0) + (extraction ? 8 : 0),
            reason,
            source,
        };
    });
    const nodeResults = context.store.listNodes(Number.MAX_SAFE_INTEGER).filter((node) => knowledgeNodeMatchesScope(node, scope, scopeLookup)).map((node) => {
        const haystack = [
            node.title,
            node.summary ?? '',
            node.aliases.join(' '),
            JSON.stringify(node.metadata),
        ].join(' ').toLowerCase();
        const { score, reason } = scoreHaystack(haystack, taskTokens, []);
        return {
            kind: 'node',
            id: node.id,
            score,
            reason,
            node,
        };
    });
    const results = [...sourceResults, ...nodeResults]
        .filter((entry) => entry.score > 0)
        .sort((a, b) => b.score - a.score || a.id.localeCompare(b.id))
        .slice(0, Math.max(1, limit));
    for (const result of results.slice(0, Math.min(results.length, 6))) {
        context.deferUsage({
            targetKind: result.kind,
            targetId: result.id,
            usageKind: 'search-hit',
            task: query,
            score: result.score,
            metadata: { reason: result.reason },
        });
    }
    return results;
}
export async function buildKnowledgePacket(context, task, writeScope = [], limit = DEFAULT_PACKET_LIMIT, options = {}) {
    return buildKnowledgePacketFromCurrentState(context, task, writeScope, limit, options);
}
export function buildKnowledgePacketSync(context, task, writeScope = [], limit = DEFAULT_PACKET_LIMIT, options = {}) {
    return buildKnowledgePacketFromCurrentState(context, task, writeScope, limit, options);
}
export function buildKnowledgePromptPacketSync(context, task, writeScope = [], limit = DEFAULT_PACKET_LIMIT, options = {}) {
    const packet = buildKnowledgePacketSync(context, task, writeScope, limit, options);
    return packet ? renderPacket(packet.items, packet) : null;
}
export async function buildKnowledgePromptPacket(context, task, writeScope = [], limit = DEFAULT_PACKET_LIMIT, options = {}) {
    const packet = await buildKnowledgePacket(context, task, writeScope, limit, options);
    return renderPacket(packet.items, packet);
}
function buildKnowledgePacketFromCurrentState(context, task, writeScope, limit, options) {
    const detail = options.detail ?? 'standard';
    const budgetLimit = Math.max(80, options.budgetLimit ?? DEFAULT_PACKET_BUDGET[detail]);
    const taskTokens = tokenize(task);
    const scopeTokens = writeScope.flatMap((entry) => tokenize(entry));
    const usageStats = buildUsageStats(context);
    const candidates = [];
    for (const source of context.store.listSources(Number.MAX_SAFE_INTEGER)) {
        if (!isInKnowledgeSpaceScope(source, options))
            continue;
        const extraction = context.store.getExtractionBySourceId(source.id);
        const haystack = [
            source.title ?? '',
            source.summary ?? '',
            source.description ?? '',
            source.sourceUri ?? '',
            source.canonicalUri ?? '',
            source.folderPath ?? '',
            source.tags.join(' '),
            extraction?.summary ?? '',
            extraction?.excerpt ?? '',
            extraction?.sections.join(' ') ?? '',
        ].join(' ').toLowerCase();
        const scored = scoreHaystack(haystack, taskTokens, scopeTokens);
        if (scored.score <= 0)
            continue;
        const evidence = detail === 'compact'
            ? []
            : (extraction?.sections.slice(0, detail === 'detailed' ? 4 : 2) ?? []);
        const summary = trimForDetail(extraction?.summary ?? source.summary ?? source.description, detail);
        const relationLabels = collectRelatedLabels(context, 'source', source.id, options);
        const usageBoost = scoreUsageBoost(usageStats.get(`source:${source.id}`));
        const relationBoost = Math.min(18, relationLabels.length * 3);
        const freshnessBoost = isSourcePastRefreshWindow(source) ? -8 : 6;
        const item = {
            kind: 'source',
            id: source.id,
            title: source.title ?? source.canonicalUri ?? source.sourceUri ?? source.id,
            summary,
            uri: source.canonicalUri ?? source.sourceUri,
            reason: scored.reason,
            score: scored.score + (source.status === 'indexed' ? 8 : 0) + (extraction ? 6 : 0) + usageBoost + relationBoost + freshnessBoost,
            estimatedTokens: estimateTokens(summary, extraction?.excerpt, evidence.join(' ')),
            related: relationLabels,
            evidence,
            metadata: {
                sourceType: source.sourceType,
                status: source.status,
                extractionFormat: extraction?.format,
                usageCount: usageStats.get(`source:${source.id}`)?.count ?? 0,
            },
        };
        candidates.push({ score: item.score, item });
    }
    const scopeLookup = knowledgeScopeLookup(context);
    for (const node of context.store.listNodes(Number.MAX_SAFE_INTEGER)) {
        if (!knowledgeNodeMatchesScope(node, options, scopeLookup))
            continue;
        const haystack = [
            node.title,
            node.summary ?? '',
            node.aliases.join(' '),
            JSON.stringify(node.metadata),
        ].join(' ').toLowerCase();
        const scored = scoreHaystack(haystack, taskTokens, scopeTokens);
        if (scored.score <= 0)
            continue;
        const related = collectRelatedLabels(context, 'node', node.id, options);
        const usageBoost = scoreUsageBoost(usageStats.get(`node:${node.id}`));
        const relationBoost = Math.min(20, context.store.edgesFor('node', node.id).length * 2);
        const kindBoost = nodeKindBoost(node.kind);
        const evidence = detail === 'compact' ? related.slice(0, 1) : related.slice(0, detail === 'detailed' ? 4 : 2);
        const summary = trimForDetail(node.summary, detail);
        const item = {
            kind: 'node',
            id: node.id,
            title: node.title,
            summary,
            reason: scored.reason,
            score: scored.score + usageBoost + relationBoost + kindBoost,
            estimatedTokens: estimateTokens(summary, evidence.join(' ')),
            related,
            evidence,
            metadata: {
                kind: node.kind,
                status: node.status,
                usageCount: usageStats.get(`node:${node.id}`)?.count ?? 0,
            },
        };
        candidates.push({ score: item.score, item });
    }
    const items = [];
    let estimatedTokens = 0;
    for (const candidate of candidates
        .sort((a, b) => b.score - a.score || a.item.id.localeCompare(b.item.id))
        .slice(0, Math.max(1, limit * 4))) {
        if (items.length >= Math.max(1, limit))
            break;
        if (estimatedTokens + candidate.item.estimatedTokens > budgetLimit && items.length > 0)
            continue;
        items.push(candidate.item);
        estimatedTokens += candidate.item.estimatedTokens;
    }
    const packet = {
        task,
        writeScope: [...writeScope],
        generatedAt: Date.now(),
        detail,
        strategy: 'graph-ranked extraction-aware packet',
        budgetLimit,
        estimatedTokens,
        items,
    };
    for (const item of items) {
        context.deferUsage({
            targetKind: item.kind,
            targetId: item.id,
            usageKind: 'packet-item',
            task,
            score: item.score,
            metadata: {
                detail,
                writeScope: [...writeScope],
            },
        });
    }
    context.emitIfReady((bus, ctx) => emitKnowledgePacketBuilt(bus, ctx, {
        task,
        itemCount: items.length,
        estimatedTokens,
        detail,
    }));
    return packet;
}
function collectRelatedLabels(context, kind, id, scope) {
    const related = context.store.edgesFor(kind, id);
    const scopeLookup = knowledgeScopeLookup(context);
    const labels = [];
    for (const edge of related) {
        const otherKind = edge.fromKind === kind && edge.fromId === id ? edge.toKind : edge.fromKind;
        const otherId = edge.fromKind === kind && edge.fromId === id ? edge.toId : edge.fromId;
        if (otherKind === 'node') {
            const node = context.store.getNode(otherId);
            if (node && knowledgeNodeMatchesScope(node, scope, scopeLookup))
                labels.push(node.title);
        }
        else if (otherKind === 'source') {
            const source = context.store.getSource(otherId);
            if (source && isInKnowledgeSpaceScope(source, scope))
                labels.push(source.title ?? source.canonicalUri ?? source.id);
        }
    }
    return [...new Set(labels)].slice(0, 8);
}
function knowledgeScopeLookup(context) {
    return {
        getSource: (id) => context.store.getSource(id),
        getNode: (id) => context.store.getNode(id),
        edges: context.store.listEdges(),
    };
}
function buildUsageStats(context, limit = 10_000) {
    const stats = new Map();
    const cutoff = Date.now() - (30 * 24 * 60 * 60 * 1000);
    for (const record of context.store.listUsageRecords(limit)) {
        if (record.createdAt < cutoff)
            continue;
        const key = `${record.targetKind}:${record.targetId}`;
        const current = stats.get(key) ?? {
            count: 0,
            scoreTotal: 0,
            lastUsedAt: 0,
            usageKinds: new Set(),
            sessionIds: new Set(),
        };
        current.count += 1;
        current.scoreTotal += Number(record.score ?? 0);
        current.lastUsedAt = Math.max(current.lastUsedAt, record.createdAt);
        current.usageKinds.add(record.usageKind);
        if (record.sessionId)
            current.sessionIds.add(record.sessionId);
        stats.set(key, current);
    }
    return stats;
}
function scoreUsageBoost(stats) {
    if (!stats)
        return 0;
    const frequency = Math.min(28, stats.count * 4);
    const diversity = Math.min(14, stats.usageKinds.size * 3 + stats.sessionIds.size * 2);
    const averageScore = stats.count > 0 ? stats.scoreTotal / stats.count : 0;
    const scoreBoost = Math.min(12, Math.max(0, averageScore / 12));
    const ageMs = Math.max(0, Date.now() - stats.lastUsedAt);
    const recency = ageMs <= (24 * 60 * 60 * 1000) ? 10 : ageMs <= 7 * (24 * 60 * 60 * 1000) ? 6 : ageMs <= 14 * (24 * 60 * 60 * 1000) ? 3 : 0;
    return frequency + diversity + scoreBoost + recency;
}
function nodeKindBoost(kind) {
    switch (kind) {
        case 'project':
        case 'capability':
        case 'repo':
        case 'service':
        case 'provider':
        case 'environment':
            return 12;
        case 'memory':
            return 10;
        case 'user':
            return 8;
        case 'domain':
        case 'bookmark_folder':
            return 4;
        default:
            return 0;
    }
}
