import { randomUUID } from 'node:crypto';
import {} from '@pellux/goodvibes-sdk/platform/automation/schedules';
import { ArtifactStore } from '@pellux/goodvibes-sdk/platform/artifacts/index';
import { createDefaultKnowledgeConnectorRegistry, KnowledgeConnectorRegistry } from '@pellux/goodvibes-sdk/platform/knowledge/connectors';
import { KnowledgeProjectionService } from '@pellux/goodvibes-sdk/platform/knowledge/projections';
import { KnowledgeStore } from '@pellux/goodvibes-sdk/platform/knowledge/store';
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 { KnowledgeScheduleService } from './scheduling.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 '@pellux/goodvibes-sdk/platform/knowledge/extractors';
import { canonicalizeUri as internalCanonicalizeUri, DEFAULT_PACKET_BUDGET as internalDefaultPacketBudget, DEFAULT_PACKET_LIMIT as internalDefaultPacketLimit, isHttpUri as internalIsHttpUri, isSourcePastRefreshWindow as internalIsSourcePastRefreshWindow, tokenize as internalTokenize, } from './internal.js';
const DEFAULT_PACKET_LIMIT = internalDefaultPacketLimit;
const DEFAULT_PACKET_BUDGET = internalDefaultPacketBudget;
const tokenize = internalTokenize;
const canonicalizeUri = internalCanonicalizeUri;
const isHttpUri = internalIsHttpUri;
export class KnowledgeService {
    store;
    artifactStore;
    connectorRegistry;
    options;
    projectionService;
    scheduleService;
    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();
        this.projectionService = new KnowledgeProjectionService(this.store, this.artifactStore, {
            connectors: () => this.listConnectors(),
        });
        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),
            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() {
        await this.store.init();
        return {
            ...this.store.status(),
            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.store.listSources(limit);
    }
    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(10_000).filter((source) => {
            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.store.listNodes(limit);
    }
    queryNodes(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.listNodes(10_000).filter((node) => {
            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.store.listIssues(limit);
    }
    queryIssues(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.listIssues(10_000).filter((issue) => {
            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),
        };
    }
    listExtractions(limit = 100, sourceId) {
        const records = this.store.listExtractions(sourceId ? 10_000 : limit);
        return sourceId ? records.filter((entry) => entry.sourceId === sourceId).slice(0, Math.max(1, limit)) : records;
    }
    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;
    }
    getItems(ids) {
        return ids.map((id) => this.getItem(id)).filter((item) => Boolean(item));
    }
    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 listProjectionTargets(limit = 25) {
        return this.projectionService.listTargets(limit);
    }
    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 reindex() {
        await this.store.init();
        for (const source of this.store.listSources(10_000)) {
            await recompileKnowledgeSource(this.getIngestContext(), source);
        }
        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);
    }
    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);
    }
    async runJob(id, input = {}) {
        return this.scheduleService.runJob(id, input);
    }
    async runJobByKind(kind, input) {
        switch (kind) {
            case 'lint': {
                const issues = await lintKnowledgeStore({ store: this.store, emitIfReady: this.emitIfReady.bind(this) });
                return { issueCount: issues.length };
            }
            case 'reindex': {
                const result = await this.reindex();
                return { sourceCount: result.status.sourceCount, issueCount: result.issues.length };
            }
            case 'refresh-stale': {
                const refreshed = await refreshKnowledgeSources(this.getIngestContext(), pickKnowledgeRefreshCandidates({ store: this.store }, 'stale', input.sourceIds, input.limit));
                return { refreshed };
            }
            case 'refresh-bookmarks': {
                const refreshed = await refreshKnowledgeSources(this.getIngestContext(), pickKnowledgeRefreshCandidates({ store: this.store }, 'bookmark', input.sourceIds, input.limit));
                return { refreshed };
            }
            case 'rebuild-projections': {
                const overview = await this.materializeProjection({ kind: 'overview', limit: Math.max(8, input.limit ?? 12) });
                const bundle = await this.materializeProjection({ kind: 'bundle', limit: Math.max(12, input.limit ?? 18) });
                return {
                    projections: [
                        { targetId: overview.bundle.target.targetId, artifactId: overview.artifact.id },
                        { targetId: bundle.bundle.target.targetId, artifactId: bundle.artifact.id },
                    ],
                };
            }
            case 'light-consolidation': {
                const report = await runKnowledgeConsolidation(this.getConsolidationContext(), 'light-consolidation', {
                    limit: input.limit,
                    autoPromote: false,
                });
                return { reportId: report.id, metrics: report.metrics };
            }
            case 'deep-consolidation': {
                const report = await runKnowledgeConsolidation(this.getConsolidationContext(), 'deep-consolidation', {
                    limit: input.limit,
                    autoPromote: true,
                });
                return { reportId: report.id, metrics: report.metrics };
            }
            default:
                return {};
        }
    }
    deferUsage(input) {
        queueMicrotask(() => {
            void this.recordUsage(input).catch(() => { });
        });
    }
    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);
}
