import { ConfigManager, getConfiguredEmbeddingProviderId } from '../config/index.js';
import { logger } from '@pellux/goodvibes-sdk/platform/utils/logger';
import { createBuiltinMemoryEmbeddingProviders } from './memory-embedding-http.js';
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils/error-display';
export const DEFAULT_MEMORY_EMBEDDING_DIMS = 384;
export function embedMemoryText(text, dims = DEFAULT_MEMORY_EMBEDDING_DIMS) {
    const vector = new Float32Array(dims);
    const normalized = text.toLowerCase();
    const tokens = normalized
        .split(/[^a-z0-9_./-]+/)
        .map((token) => token.trim())
        .filter((token) => token.length >= 2);
    for (const token of tokens) {
        addHashedFeature(vector, `tok:${token}`, 1.2);
        if (token.includes('/') || token.includes('.') || token.includes('-') || token.includes('_')) {
            for (const part of token.split(/[./_-]+/).filter((entry) => entry.length >= 2)) {
                addHashedFeature(vector, `part:${part}`, 0.8);
            }
        }
    }
    for (let i = 0; i < tokens.length - 1; i++) {
        addHashedFeature(vector, `bigram:${tokens[i]} ${tokens[i + 1]}`, 0.9);
    }
    const compact = normalized.replace(/\s+/g, ' ').trim();
    for (let i = 0; i < compact.length - 2; i++) {
        const tri = compact.slice(i, i + 3);
        if (/\s{2,}/.test(tri))
            continue;
        addHashedFeature(vector, `tri:${tri}`, 0.18);
    }
    normalizeVector(vector);
    return vector;
}
export function normalizeMemoryEmbeddingVector(vector, dimensions) {
    if (vector instanceof Float32Array && vector.length === dimensions)
        return vector;
    const normalized = new Float32Array(dimensions);
    const limit = Math.min(vector.length, dimensions);
    for (let i = 0; i < limit; i++) {
        const value = Number(vector[i]);
        normalized[i] = Number.isFinite(value) ? value : 0;
    }
    normalizeVector(normalized);
    return normalized;
}
export class MemoryEmbeddingProviderRegistry {
    providers = new Map();
    activeProviderId = HASHED_MEMORY_EMBEDDING_PROVIDER.id;
    configManager;
    constructor(options) {
        this.configManager = options.configManager;
        this.register(HASHED_MEMORY_EMBEDDING_PROVIDER, { replace: true });
        for (const provider of createBuiltinMemoryEmbeddingProviders()) {
            this.register(provider, { replace: true });
        }
        const configuredDefault = getConfiguredEmbeddingProviderId(this.configManager).trim();
        if (configuredDefault) {
            this.activeProviderId = configuredDefault;
        }
    }
    register(provider, options = {}) {
        const id = provider.id.trim();
        if (!id)
            throw new Error('Memory embedding provider id is required');
        if (this.providers.has(id) && !options.replace) {
            throw new Error(`Memory embedding provider already registered: ${id}`);
        }
        const registered = { ...provider, id };
        this.providers.set(id, registered);
        if (options.makeDefault) {
            this.activeProviderId = id;
            persistDefaultEmbeddingProviderId(this.configManager, id);
        }
        return () => {
            if (this.providers.get(id) === registered)
                this.unregister(id);
        };
    }
    unregister(id) {
        if (id === HASHED_MEMORY_EMBEDDING_PROVIDER.id)
            return false;
        return this.providers.delete(id);
    }
    setDefaultProvider(id) {
        if (!this.providers.has(id))
            throw new Error(`Unknown memory embedding provider: ${id}`);
        this.activeProviderId = id;
        persistDefaultEmbeddingProviderId(this.configManager, id);
    }
    getDefaultProvider() {
        return this.providers.get(this.activeProviderId) ?? HASHED_MEMORY_EMBEDDING_PROVIDER;
    }
    get(id) {
        return this.providers.get(id) ?? null;
    }
    list() {
        return [...this.providers.values()].sort((a, b) => a.label.localeCompare(b.label) || a.id.localeCompare(b.id));
    }
    embedSync(request) {
        const provider = this.getDefaultProvider();
        if (provider.embedSync)
            return provider.embedSync(request);
        return HASHED_MEMORY_EMBEDDING_PROVIDER.embedSync(request);
    }
    async embedAsync(request) {
        const provider = this.getDefaultProvider();
        if (provider.embed) {
            try {
                return await provider.embed(request);
            }
            catch (error) {
                logger.warn('Memory embedding provider failed; falling back to hashed local embeddings', {
                    providerId: provider.id,
                    error: summarizeError(error),
                });
            }
        }
        if (provider.embedSync) {
            return provider.embedSync(request);
        }
        return HASHED_MEMORY_EMBEDDING_PROVIDER.embedSync(request);
    }
    async status() {
        const statuses = [];
        for (const provider of this.providers.values()) {
            if (provider.status) {
                statuses.push(await provider.status());
                continue;
            }
            statuses.push({
                id: provider.id,
                label: provider.label,
                state: 'healthy',
                dimensions: provider.dimensions,
                configured: true,
                deterministic: provider.deterministic,
                metadata: {
                    local: provider.local === true,
                    hasSyncEmbed: typeof provider.embedSync === 'function',
                    hasAsyncEmbed: typeof provider.embed === 'function',
                },
            });
        }
        if (this.activeProviderId !== HASHED_MEMORY_EMBEDDING_PROVIDER.id && !this.providers.has(this.activeProviderId)) {
            statuses.push({
                id: this.activeProviderId,
                label: `Persisted default (${this.activeProviderId})`,
                state: 'unconfigured',
                dimensions: DEFAULT_MEMORY_EMBEDDING_DIMS,
                configured: false,
                detail: `Persisted embedding provider '${this.activeProviderId}' is not registered; sqlite-vec will use '${HASHED_MEMORY_EMBEDDING_PROVIDER.id}' until it returns.`,
                metadata: {
                    local: true,
                    hasSyncEmbed: false,
                    hasAsyncEmbed: false,
                    persistedDefault: true,
                },
            });
        }
        return statuses.sort((a, b) => a.label.localeCompare(b.label) || a.id.localeCompare(b.id));
    }
    async doctor() {
        const providers = await this.status();
        const syncProviders = this.list().filter((provider) => typeof provider.embedSync === 'function').map((provider) => provider.id);
        const asyncProviders = this.list().filter((provider) => typeof provider.embed === 'function').map((provider) => provider.id);
        const active = this.getDefaultProvider();
        const activeStatus = providers.find((provider) => provider.id === active.id);
        const warnings = [];
        if (!this.providers.has(this.activeProviderId)) {
            warnings.push(`Persisted embedding provider '${this.activeProviderId}' is not currently registered; falling back to '${HASHED_MEMORY_EMBEDDING_PROVIDER.id}'.`);
        }
        if (activeStatus && !activeStatus.configured) {
            warnings.push(`Active embedding provider '${active.id}' is not fully configured: ${activeStatus.detail ?? 'configuration is incomplete'}`);
        }
        if (!active.embedSync) {
            if (active.embed) {
                warnings.push(`Active embedding provider '${active.id}' is async-only; sqlite-vec indexing will fall back to '${HASHED_MEMORY_EMBEDDING_PROVIDER.id}' for live writes and can be rebuilt asynchronously.`);
            }
            else {
                warnings.push(`Active embedding provider '${active.id}' has no sync embed path; sqlite-vec indexing will fall back to '${HASHED_MEMORY_EMBEDDING_PROVIDER.id}'.`);
            }
        }
        const activeDimensions = active.dimensions;
        if (activeDimensions !== DEFAULT_MEMORY_EMBEDDING_DIMS) {
            warnings.push(`Active embedding provider '${active.id}' advertises ${active.dimensions} dimensions; vectors are normalized to ${DEFAULT_MEMORY_EMBEDDING_DIMS} dimensions for sqlite-vec storage.`);
        }
        return {
            activeProviderId: this.activeProviderId,
            providers,
            asyncProviders,
            syncProviders,
            warnings,
        };
    }
}
function persistDefaultEmbeddingProviderId(configManager, providerId) {
    try {
        configManager.set('provider.embeddingProvider', providerId, { bypassManagedLock: true });
    }
    catch {
        // Persistence is best-effort; runtime selection still takes effect immediately.
    }
}
export const HASHED_MEMORY_EMBEDDING_PROVIDER = {
    id: 'hashed-local',
    label: 'Hashed Local Embeddings',
    dimensions: DEFAULT_MEMORY_EMBEDDING_DIMS,
    deterministic: true,
    local: true,
    embedSync(request) {
        return {
            vector: embedMemoryText(request.text, request.dimensions),
            dimensions: request.dimensions,
            modelId: 'goodvibes-hashed-local',
            metadata: {
                usage: request.usage,
                deterministic: true,
            },
        };
    },
};
function normalizeVector(vector) {
    let sum = 0;
    for (const value of vector) {
        sum += value * value;
    }
    const norm = Math.sqrt(sum);
    if (norm === 0)
        return;
    for (let i = 0; i < vector.length; i++) {
        vector[i] = vector[i] / norm;
    }
}
function addHashedFeature(vector, feature, weight) {
    const hash = fnv1a(feature);
    const index = hash % vector.length;
    const sign = (hash & 0x80000000) === 0 ? 1 : -1;
    vector[index] += sign * weight;
}
function fnv1a(value) {
    let hash = 0x811c9dc5;
    for (let i = 0; i < value.length; i++) {
        hash ^= value.charCodeAt(i);
        hash = Math.imul(hash, 0x01000193);
    }
    return hash >>> 0;
}
