import { randomUUID } from 'node:crypto';
import {} from '../automation/schedules.js';
import { summarizeError } from '../utils/error-display.js';
import { logger } from '../utils/logger.js';
import { ArtifactStore } from '../artifacts/index.js';
import { createDefaultKnowledgeConnectorRegistry, KnowledgeConnectorRegistry } from './connectors.js';
import { renderKnowledgeMap } from './map.js';
import { KnowledgeProjectionService } from './projections.js';
import { KnowledgeSemanticService } from './semantic/index.js';
import { KnowledgeStore } from './store.js';
import { ingestBrowserKnowledge } from './browser-history/index.js';
import { buildKnowledgePacket, buildKnowledgePacketSync, buildKnowledgePromptPacket, buildKnowledgePromptPacketSync, searchKnowledge, } from './packet.js';
import { ingestKnowledgeArtifact, ingestKnowledgeBookmarkSeeds, ingestKnowledgeConnectorInput, ingestKnowledgeUrl, ingestKnowledgeWithConnector, importKnowledgeBookmarksFromFile, importKnowledgeUrlsFromFile, refreshKnowledgeSources, pickKnowledgeRefreshCandidates, recompileKnowledgeSource, } from './ingest.js';
import { decideKnowledgeConsolidationCandidate, runKnowledgeConsolidation, } from './consolidation.js';
import { reviewKnowledgeIssue, } from './review.js';
import { KnowledgeScheduleService } from './scheduling.js';
import { runKnowledgeServiceJobByKind } from './service-jobs.js';
import { lintKnowledgeStore } from './lint.js';
import { syncKnowledgeMemoryNodes } from './memory-sync.js';
import { emitKnowledgeExtractionCompleted, emitKnowledgeExtractionFailed, emitKnowledgeIngestCompleted, emitKnowledgeIngestFailed, emitKnowledgeIngestStarted, emitKnowledgePacketBuilt, emitKnowledgeProjectionMaterialized, emitKnowledgeProjectionRendered, } from '../runtime/emitters/index.js';
import { extractKnowledgeArtifact } from './extractors.js';
import { DEFAULT_PACKET_BUDGET, DEFAULT_PACKET_LIMIT, tokenize, } from './shared.js';
import { isGeneratedKnowledgeSource } from './generated-projections.js';
import { knowledgeIssueMatchesScope, knowledgeNodeMatchesScope, } from './scope-records.js';
import { isInKnowledgeSpaceScope, resolveKnowledgeSpaceScope, } from './spaces.js';
export class KnowledgeService {
    store;
    artifactStore;
    connectorRegistry;
    options;
    projectionService;
    scheduleService;
    semanticService;
    runtimeBus;
    constructor(store, artifactStore, connectorRegistry = createDefaultKnowledgeConnectorRegistry(), options) {
        this.store = store;
        this.artifactStore = artifactStore;
        this.connectorRegistry = connectorRegistry;
        this.options = options;
        this.runtimeBus = options.runtimeBus ?? null;
        void this.store.init().catch((error) => {
            logger.error('[knowledge] store initialization failed', { error: summarizeError(error) });
        });
        this.projectionService = new KnowledgeProjectionService(this.store, this.artifactStore, {
            connectors: () => this.listConnectors(),
        });
        this.semanticService = options.semanticService ?? new KnowledgeSemanticService(this.store);
        this.scheduleService = new KnowledgeScheduleService({
            store: this.store,
            emitIfReady: this.emitIfReady.bind(this),
            runJobByKind: this.runJobByKind.bind(this),
        });
    }
    getIngestContext() {
        return {
            store: this.store,
            artifactStore: this.artifactStore,
            connectorRegistry: this.connectorRegistry,
            emitIfReady: this.emitIfReady.bind(this),
            syncReviewedMemory: this.syncReviewedMemory.bind(this),
            semanticEnrichSource: async (sourceId, knowledgeSpaceId) => {
                await this.semanticService.enrichSource(sourceId, { knowledgeSpaceId });
                await this.semanticService.selfImprove({
                    knowledgeSpaceId,
                    sourceIds: [sourceId],
                    reason: 'ingest',
                    limit: 8,
                });
            },
            lint: this.lint.bind(this),
            listConnectors: () => this.listConnectors(),
        };
    }
    getPacketContext() {
        return {
            store: this.store,
            deferUsage: this.deferUsage.bind(this),
            emitIfReady: this.emitIfReady.bind(this),
        };
    }
    getConsolidationContext() {
        return {
            store: this.store,
            memoryRegistry: this.options.memoryRegistry,
            syncReviewedMemory: this.syncReviewedMemory.bind(this),
        };
    }
    attachRuntimeBus(runtimeBus) {
        if (runtimeBus)
            this.runtimeBus = runtimeBus;
    }
    async getStatus(scope = {}) {
        await this.store.init();
        const allStatus = this.store.status();
        const spaceId = resolveKnowledgeSpaceScope(scope);
        if (spaceId === null) {
            return {
                ...allStatus,
                note: 'Structured knowledge uses SQL-backed sources, nodes, edges, issues, extractions, and job runs. Markdown is an optional projection, not the source of truth.',
            };
        }
        const scoped = { knowledgeSpaceId: spaceId };
        return {
            ...allStatus,
            sourceCount: this.querySources({ limit: Number.MAX_SAFE_INTEGER, ...scoped }).total,
            nodeCount: this.queryNodes({ limit: Number.MAX_SAFE_INTEGER, ...scoped }).total,
            edgeCount: this.store.listEdges().filter((edge) => this.edgeInKnowledgeSpaceScope(edge, scoped)).length,
            issueCount: this.queryIssues({ limit: Number.MAX_SAFE_INTEGER, ...scoped }).total,
            extractionCount: this.listExtractions(Number.MAX_SAFE_INTEGER, undefined, scoped).length,
            refinementTaskCount: this.store.listRefinementTasks(Number.MAX_SAFE_INTEGER, { spaceId }).length,
            note: 'Structured knowledge uses SQL-backed sources, nodes, edges, issues, extractions, and job runs. Markdown is an optional projection, not the source of truth.',
        };
    }
    listUsageRecords(limit = 100, input = {}) {
        return this.store.listUsageRecords(limit, input);
    }
    listConsolidationCandidates(limit = 100, input = {}) {
        return this.store.listConsolidationCandidates(limit, input);
    }
    getConsolidationCandidate(id) {
        return this.store.getConsolidationCandidate(id);
    }
    listConsolidationReports(limit = 100) {
        return this.store.listConsolidationReports(limit);
    }
    getConsolidationReport(id) {
        return this.store.getConsolidationReport(id);
    }
    listSchedules(limit = 100) {
        return this.store.listSchedules(limit);
    }
    getSchedule(id) {
        return this.store.getSchedule(id);
    }
    listSources(limit = 100) {
        return this.querySources({ limit }).items;
    }
    querySources(input = {}) {
        const limit = Math.max(1, input.limit ?? 100);
        const offset = Math.max(0, input.offset ?? 0);
        const queryTokens = tokenize(input.query ?? '');
        const items = this.store.listSources(Number.MAX_SAFE_INTEGER).filter((source) => {
            if (!isInKnowledgeSpaceScope(source, input))
                return false;
            if (input.status && source.status !== input.status)
                return false;
            if (input.connectorId && source.connectorId !== input.connectorId)
                return false;
            if (input.sourceType && source.sourceType !== input.sourceType)
                return false;
            if (input.tag && !source.tags.includes(input.tag))
                return false;
            if (queryTokens.length === 0)
                return true;
            const extraction = this.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();
            return queryTokens.every((token) => haystack.includes(token));
        });
        return {
            total: items.length,
            items: items.slice(offset, offset + limit),
        };
    }
    listNodes(limit = 100) {
        return this.queryNodes({ limit }).items;
    }
    queryNodes(input = {}) {
        const limit = Math.max(1, input.limit ?? 100);
        const offset = Math.max(0, input.offset ?? 0);
        const queryTokens = tokenize(input.query ?? '');
        const scopeLookup = this.getScopeLookup();
        const items = this.store.listNodes(Number.MAX_SAFE_INTEGER).filter((node) => {
            if (!knowledgeNodeMatchesScope(node, input, scopeLookup))
                return false;
            if (input.kind && node.kind !== input.kind)
                return false;
            if (input.status && node.status !== input.status)
                return false;
            if (queryTokens.length === 0)
                return true;
            const haystack = [
                node.title,
                node.summary ?? '',
                node.aliases.join(' '),
                JSON.stringify(node.metadata),
            ].join(' ').toLowerCase();
            return queryTokens.every((token) => haystack.includes(token));
        });
        return {
            total: items.length,
            items: items.slice(offset, offset + limit),
        };
    }
    listIssues(limit = 100) {
        return this.queryIssues({ limit }).items;
    }
    queryIssues(input = {}) {
        const limit = Math.max(1, input.limit ?? 100);
        const offset = Math.max(0, input.offset ?? 0);
        const queryTokens = tokenize(input.query ?? '');
        const scopeLookup = this.getScopeLookup();
        const items = this.store.listIssues(Number.MAX_SAFE_INTEGER).filter((issue) => {
            if (!knowledgeIssueMatchesScope(issue, input, scopeLookup))
                return false;
            if (input.severity && issue.severity !== input.severity)
                return false;
            if (input.status && issue.status !== input.status)
                return false;
            if (input.code && issue.code !== input.code)
                return false;
            if (queryTokens.length === 0)
                return true;
            const haystack = [issue.message, issue.code, JSON.stringify(issue.metadata)].join(' ').toLowerCase();
            return queryTokens.every((token) => haystack.includes(token));
        });
        return {
            total: items.length,
            items: items.slice(offset, offset + limit),
        };
    }
    async reviewIssue(input) {
        return reviewKnowledgeIssue(this.store, input);
    }
    listExtractions(limit = 100, sourceId, scope = {}) {
        const records = this.store.listExtractions(sourceId ? 10_000 : limit);
        return records
            .filter((entry) => !sourceId || entry.sourceId === sourceId)
            .filter((entry) => isInKnowledgeSpaceScope(this.store.getSource(entry.sourceId) ?? entry, scope))
            .slice(0, Math.max(1, limit));
    }
    getExtraction(id) {
        return this.store.getExtraction(id);
    }
    getSourceExtraction(sourceId) {
        return this.store.getExtractionBySourceId(sourceId);
    }
    listConnectors() {
        return this.connectorRegistry.list();
    }
    getConnector(id) {
        return this.connectorRegistry.get(id) ?? null;
    }
    async doctorConnector(id) {
        return this.connectorRegistry.doctor(id);
    }
    registerConnector(connector, options = {}) {
        this.connectorRegistry.register(connector, options);
    }
    getItem(id) {
        const item = this.store.getItem(id);
        if (item?.source)
            this.deferUsage({ targetKind: 'source', targetId: item.source.id, usageKind: 'item-open' });
        if (item?.node)
            this.deferUsage({ targetKind: 'node', targetId: item.node.id, usageKind: 'item-open' });
        if (item?.issue)
            this.deferUsage({ targetKind: 'issue', targetId: item.issue.id, usageKind: 'item-open' });
        return item;
    }
    getItemScoped(id, scope = {}) {
        const item = this.store.getItem(id);
        const primary = item?.source ?? item?.node ?? item?.issue;
        if (!item || !primary || !this.recordMatchesKnowledgeSpaceScope(primary, scope))
            return null;
        const scoped = {
            ...item,
            linkedSources: item.linkedSources.filter((source) => isInKnowledgeSpaceScope(source, scope)),
            linkedNodes: item.linkedNodes.filter((node) => this.nodeMatchesKnowledgeSpaceScope(node, scope)),
            relatedEdges: item.relatedEdges.filter((edge) => this.edgeInKnowledgeSpaceScope(edge, scope)),
        };
        if (scoped.source)
            this.deferUsage({ targetKind: 'source', targetId: scoped.source.id, usageKind: 'item-open' });
        if (scoped.node)
            this.deferUsage({ targetKind: 'node', targetId: scoped.node.id, usageKind: 'item-open' });
        if (scoped.issue)
            this.deferUsage({ targetKind: 'issue', targetId: scoped.issue.id, usageKind: 'item-open' });
        return scoped;
    }
    getItems(ids) {
        return ids.map((id) => this.getItem(id)).filter((item) => Boolean(item));
    }
    edgeInKnowledgeSpaceScope(edge, scope) {
        return this.recordReferenceInKnowledgeSpaceScope(edge.fromKind, edge.fromId, scope)
            && this.recordReferenceInKnowledgeSpaceScope(edge.toKind, edge.toId, scope);
    }
    recordReferenceInKnowledgeSpaceScope(kind, id, scope) {
        if (kind === 'source')
            return isInKnowledgeSpaceScope(this.store.getSource(id), scope);
        if (kind === 'node') {
            const node = this.store.getNode(id);
            return Boolean(node && this.nodeMatchesKnowledgeSpaceScope(node, scope));
        }
        if (kind === 'issue') {
            const issue = this.store.getIssue(id);
            return Boolean(issue && this.issueMatchesKnowledgeSpaceScope(issue, scope));
        }
        return true;
    }
    recordMatchesKnowledgeSpaceScope(record, scope) {
        if ('sourceType' in record)
            return isInKnowledgeSpaceScope(record, scope);
        if ('kind' in record)
            return this.nodeMatchesKnowledgeSpaceScope(record, scope);
        return this.issueMatchesKnowledgeSpaceScope(record, scope);
    }
    nodeMatchesKnowledgeSpaceScope(node, scope) {
        return knowledgeNodeMatchesScope(node, scope, this.getScopeLookup());
    }
    issueMatchesKnowledgeSpaceScope(issue, scope) {
        return knowledgeIssueMatchesScope(issue, scope, this.getScopeLookup());
    }
    getScopeLookup() {
        return {
            getSource: (id) => this.store.getSource(id),
            getNode: (id) => this.store.getNode(id),
            edges: this.store.listEdges(),
        };
    }
    async recordUsage(input) {
        await this.store.init();
        return this.store.upsertUsageRecord(input);
    }
    getNeighbors(kind, id, input = {}) {
        const limit = Math.max(1, input.limit ?? 20);
        const edges = this.store.edgesFor(kind, id)
            .filter((edge) => !input.relation || edge.relation === input.relation)
            .slice(0, limit);
        this.deferUsage({
            targetKind: kind,
            targetId: id,
            usageKind: 'neighbor-open',
            metadata: input.relation ? { relation: input.relation } : {},
        });
        const sources = [];
        const nodes = [];
        for (const edge of edges) {
            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 === 'source') {
                const source = this.store.getSource(otherId);
                if (source)
                    sources.push(source);
            }
            else if (otherKind === 'node') {
                const node = this.store.getNode(otherId);
                if (node)
                    nodes.push(node);
            }
        }
        return { edges, sources, nodes };
    }
    async ingestUrl(input) {
        return ingestKnowledgeUrl(this.getIngestContext(), input);
    }
    async ingestArtifact(input) {
        return ingestKnowledgeArtifact(this.getIngestContext(), input);
    }
    async importBookmarksFromFile(input) {
        return importKnowledgeBookmarksFromFile(this.getIngestContext(), input);
    }
    async importUrlsFromFile(input) {
        return importKnowledgeUrlsFromFile(this.getIngestContext(), input);
    }
    async ingestBookmarkSeeds(seeds, sessionId, sourceType = 'bookmark', connectorId = 'bookmark', allowPrivateHosts) {
        return ingestKnowledgeBookmarkSeeds(this.getIngestContext(), seeds, sessionId, sourceType, connectorId, allowPrivateHosts);
    }
    async ingestWithConnector(connectorId, input, sessionId, allowPrivateHosts) {
        return ingestKnowledgeWithConnector(this.getIngestContext(), connectorId, input, sessionId, allowPrivateHosts);
    }
    async ingestConnectorInput(input) {
        return ingestKnowledgeConnectorInput(this.getIngestContext(), input);
    }
    async syncBrowserHistory(input = {}) {
        return ingestBrowserKnowledge(this.getIngestContext(), input);
    }
    async listProjectionTargets(limit = 25, scope = {}) {
        return this.projectionService.listTargets(limit, scope);
    }
    async renderProjection(input) {
        const bundle = await this.projectionService.render(input);
        this.emitIfReady((bus, ctx) => emitKnowledgeProjectionRendered(bus, ctx, {
            targetId: bundle.target.targetId,
            pageCount: bundle.pageCount,
        }));
        return bundle;
    }
    async materializeProjection(input) {
        const materialized = await this.projectionService.materialize(input);
        this.emitIfReady((bus, ctx) => emitKnowledgeProjectionMaterialized(bus, ctx, {
            targetId: materialized.bundle.target.targetId,
            artifactId: materialized.artifact.id,
            pageCount: materialized.bundle.pageCount,
        }));
        return materialized;
    }
    async map(input = {}) {
        await this.store.init();
        return renderKnowledgeMap({
            title: 'Knowledge Map',
            sources: this.store.listSources(Number.MAX_SAFE_INTEGER),
            nodes: this.store.listNodes(Number.MAX_SAFE_INTEGER),
            edges: this.store.listEdges(),
            issues: this.store.listIssues(Number.MAX_SAFE_INTEGER),
        }, input);
    }
    async reindex() {
        await this.store.init();
        for (const source of this.store.listSources(Number.MAX_SAFE_INTEGER)) {
            if (isGeneratedKnowledgeSource(source))
                continue;
            await recompileKnowledgeSource(this.getIngestContext(), source);
        }
        await this.semanticService.reindex({ force: false });
        await this.syncReviewedMemory();
        const issues = await lintKnowledgeStore({ store: this.store, emitIfReady: this.emitIfReady.bind(this) });
        return { status: this.store.status(), issues };
    }
    search(query, limit = 10) {
        return searchKnowledge(this.getPacketContext(), query, limit);
    }
    searchScoped(input) {
        return searchKnowledge(this.getPacketContext(), input.query, input.limit ?? 10, input);
    }
    async ask(input) {
        return this.semanticService.answer(input);
    }
    async buildPacket(task, writeScope = [], limit = DEFAULT_PACKET_LIMIT, options = {}) {
        return buildKnowledgePacket(this.getPacketContext(), task, writeScope, limit, options);
    }
    buildPacketSync(task, writeScope = [], limit = DEFAULT_PACKET_LIMIT, options = {}) {
        return buildKnowledgePacketSync(this.getPacketContext(), task, writeScope, limit, options);
    }
    buildPromptPacketSync(task, writeScope = [], limit = DEFAULT_PACKET_LIMIT, options = {}) {
        return buildKnowledgePromptPacketSync(this.getPacketContext(), task, writeScope, limit, options);
    }
    async buildPromptPacket(task, writeScope = [], limit = DEFAULT_PACKET_LIMIT, options = {}) {
        return buildKnowledgePromptPacket(this.getPacketContext(), task, writeScope, limit, options);
    }
    listJobs() {
        return this.scheduleService.listJobs();
    }
    getJob(id) {
        return this.scheduleService.getJob(id);
    }
    async saveSchedule(input) {
        return this.scheduleService.saveSchedule(input);
    }
    async deleteSchedule(id) {
        return this.scheduleService.deleteSchedule(id);
    }
    async setScheduleEnabled(id, enabled) {
        return this.scheduleService.setScheduleEnabled(id, enabled);
    }
    async decideConsolidationCandidate(id, decision, input = {}) {
        return decideKnowledgeConsolidationCandidate(this.getConsolidationContext(), id, decision, input);
    }
    listJobRuns(limit = 100, jobId) {
        return this.scheduleService.listJobRuns(limit, jobId);
    }
    listRefinementTasks(limit = 100, input = {}) {
        return this.store.listRefinementTasks(limit, input);
    }
    getRefinementTask(id) {
        return this.store.getRefinementTask(id);
    }
    async cancelRefinementTask(id) {
        const task = this.store.getRefinementTask(id);
        if (!task)
            return null;
        return this.store.upsertRefinementTask({
            id: task.id,
            spaceId: task.spaceId,
            subjectKind: task.subjectKind,
            subjectId: task.subjectId,
            subjectTitle: task.subjectTitle,
            subjectType: task.subjectType,
            gapId: task.gapId,
            issueId: task.issueId,
            state: 'cancelled',
            priority: task.priority,
            trigger: task.trigger,
            budget: task.budget,
            attemptCount: task.attemptCount,
            appendTrace: [{
                    at: Date.now(),
                    state: 'cancelled',
                    message: 'Refinement task was cancelled by request.',
                }],
            metadata: task.metadata,
        });
    }
    async runRefinement(input = {}) {
        return this.semanticService.selfImprove({
            knowledgeSpaceId: input.knowledgeSpaceId,
            gapIds: input.gapIds,
            sourceIds: input.sourceIds,
            limit: input.limit,
            maxRunMs: input.maxRunMs,
            force: input.force,
            reason: 'manual',
        });
    }
    async runJob(id, input = {}) {
        return this.scheduleService.runJob(id, input);
    }
    async runJobByKind(kind, input) {
        return runKnowledgeServiceJobByKind(kind, input, {
            lint: () => lintKnowledgeStore({ store: this.store, emitIfReady: this.emitIfReady.bind(this) }),
            reindex: () => this.reindex(),
            refreshSources: async (refreshKind, sourceIds, limit) => refreshKnowledgeSources(this.getIngestContext(), pickKnowledgeRefreshCandidates({ store: this.store }, refreshKind, sourceIds, limit)),
            syncBrowserHistory: (options) => this.syncBrowserHistory(options),
            materializeProjection: (options) => this.materializeProjection(options),
            semanticService: this.semanticService,
            runConsolidation: (jobKind, options) => runKnowledgeConsolidation(this.getConsolidationContext(), jobKind, options),
        });
    }
    deferUsage(input) {
        queueMicrotask(() => {
            void this.recordUsage(input).catch((error) => {
                logger.warn('Knowledge usage recording failed', {
                    targetKind: input.targetKind,
                    targetId: input.targetId,
                    usageKind: input.usageKind,
                    error: summarizeError(error),
                });
            });
        });
    }
    async syncReviewedMemory() {
        await syncKnowledgeMemoryNodes(this.store, this.options.memoryRegistry);
    }
    dispose() {
        this.scheduleService.dispose();
    }
    async lint() {
        return lintKnowledgeStore({ store: this.store, emitIfReady: this.emitIfReady.bind(this) });
    }
    emitIfReady(fn, sessionId) {
        if (!this.runtimeBus)
            return;
        fn(this.runtimeBus, {
            traceId: randomUUID(),
            sessionId: sessionId ?? 'knowledge-runtime',
            source: 'knowledge.service',
        });
    }
}
export function buildCuratedKnowledgePromptSync(service, task, writeScope = []) {
    return service.buildPromptPacketSync(task, writeScope);
}
