/**
 * Reranker Factory
 *
 * Factory for creating reranker instances with configuration.
 * Follows the BaseFactory pattern for consistent lifecycle management.
 */
import { BaseFactory } from "../../core/infrastructure/index.js";
import { logger } from "../../utils/logger.js";
import { RAGErrorCodes, RerankerError } from "../errors/RAGError.js";
/**
 * Default reranker metadata entries
 */
const DEFAULT_RERANKER_METADATA = {
    llm: {
        description: "LLM-powered semantic reranking with multi-factor scoring",
        defaultConfig: {
            topK: 3,
            weights: { semantic: 0.4, vector: 0.4, position: 0.2 },
        },
        supportedOptions: ["model", "provider", "topK", "weights"],
        useCases: [
            "High-quality semantic reranking",
            "Complex query understanding",
            "Context-aware scoring",
        ],
        aliases: ["semantic", "ai", "model-based"],
        requiresModel: true,
        requiresExternalAPI: false,
    },
    "cross-encoder": {
        description: "Cross-encoder model for query-document relevance scoring",
        defaultConfig: {
            topK: 3,
            model: "ms-marco-MiniLM-L-6-v2",
        },
        supportedOptions: ["model", "topK"],
        useCases: [
            "High-precision reranking",
            "Search result refinement",
            "Academic/research applications",
        ],
        aliases: ["cross", "encoder", "bi-encoder"],
        requiresModel: true,
        requiresExternalAPI: false,
    },
    cohere: {
        description: "Cohere Rerank API for production-grade relevance scoring",
        defaultConfig: {
            topK: 3,
            model: "rerank-v3.5",
        },
        supportedOptions: ["model", "topK", "apiKey"],
        useCases: [
            "Production search systems",
            "Enterprise applications",
            "High-volume reranking",
        ],
        aliases: ["cohere-rerank", "cohere-api"],
        requiresModel: false,
        requiresExternalAPI: true,
    },
    simple: {
        description: "Position and vector score-based reranking (no LLM required)",
        defaultConfig: {
            topK: 3,
            weights: { vector: 0.8, position: 0.2 },
        },
        supportedOptions: ["topK", "weights"],
        useCases: [
            "Fast reranking",
            "Low-latency requirements",
            "When LLM is unavailable",
        ],
        aliases: ["fast", "basic", "position-based"],
        requiresModel: false,
        requiresExternalAPI: false,
    },
    batch: {
        description: "Batch LLM reranking for efficient multi-document scoring",
        defaultConfig: {
            topK: 3,
            weights: { semantic: 0.4, vector: 0.4, position: 0.2 },
        },
        supportedOptions: ["model", "provider", "topK", "weights"],
        useCases: [
            "Large result sets",
            "Cost-efficient LLM usage",
            "Batch processing pipelines",
        ],
        aliases: ["batch-llm", "efficient", "bulk"],
        requiresModel: true,
        requiresExternalAPI: false,
    },
};
/**
 * Reranker Factory
 *
 * Creates reranker instances based on type with configuration support.
 * Uses lazy loading via dynamic imports to avoid circular dependencies.
 */
export class RerankerFactory extends BaseFactory {
    static instance = null;
    metadataMap = new Map();
    modelProvider = null;
    constructor() {
        super();
    }
    /**
     * Get singleton instance
     */
    static getInstance() {
        if (!RerankerFactory.instance) {
            RerankerFactory.instance = new RerankerFactory();
        }
        return RerankerFactory.instance;
    }
    /**
     * Reset singleton (for testing)
     */
    static resetInstance() {
        if (RerankerFactory.instance) {
            RerankerFactory.instance.clear();
            RerankerFactory.instance = null;
        }
    }
    /**
     * Set the AI provider for LLM-based rerankers
     */
    setModelProvider(provider) {
        this.modelProvider = provider;
    }
    /**
     * Register all default rerankers
     */
    async registerAll() {
        // Register LLM reranker
        this.registerReranker("llm", async (config) => {
            const { rerank } = await import("./reranker.js");
            return this.createLLMReranker(rerank, config);
        }, DEFAULT_RERANKER_METADATA.llm);
        // Register cross-encoder reranker
        this.registerReranker("cross-encoder", async (config) => {
            const { CrossEncoderReranker } = await import("./reranker.js");
            return this.createCrossEncoderReranker(CrossEncoderReranker, config);
        }, DEFAULT_RERANKER_METADATA["cross-encoder"]);
        // Register Cohere reranker
        this.registerReranker("cohere", async (config) => {
            const { CohereRelevanceScorer } = await import("./reranker.js");
            return this.createCohereReranker(CohereRelevanceScorer, config);
        }, DEFAULT_RERANKER_METADATA.cohere);
        // Register simple reranker
        this.registerReranker("simple", async (config) => {
            const { simpleRerank } = await import("./reranker.js");
            return this.createSimpleReranker(simpleRerank, config);
        }, DEFAULT_RERANKER_METADATA.simple);
        // Register batch reranker
        this.registerReranker("batch", async (config) => {
            const { batchRerank } = await import("./reranker.js");
            return this.createBatchReranker(batchRerank, config);
        }, DEFAULT_RERANKER_METADATA.batch);
        logger.debug(`[RerankerFactory] Registered ${this.items.size} reranker types`);
    }
    /**
     * Create LLM-based reranker wrapper
     */
    createLLMReranker(rerankFn, config) {
        const factory = this;
        return {
            type: "llm",
            async rerank(results, query, options) {
                if (!factory.modelProvider) {
                    throw new RerankerError("LLM reranker requires a model provider. Call setModelProvider() first.", {
                        rerankerType: "llm",
                    });
                }
                return rerankFn(results, query, factory.modelProvider, {
                    ...options,
                    topK: config?.topK ?? options?.topK,
                    weights: config?.weights ?? options?.weights,
                });
            },
        };
    }
    /**
     * Create cross-encoder reranker wrapper
     */
    createCrossEncoderReranker(CrossEncoderClass, config) {
        const encoder = new CrossEncoderClass(typeof config?.model === "string" ? config.model : undefined);
        return {
            type: "cross-encoder",
            async rerank(results, query, options) {
                const documents = results.map((r) => r.text || r.metadata?.text || "");
                const scores = await encoder.rerank(query, documents);
                const topK = config?.topK ?? options?.topK ?? 3;
                return scores
                    .map((s) => ({
                    result: results[s.index],
                    score: s.score,
                    details: {
                        semantic: s.score,
                        vector: results[s.index].score ?? 0,
                        position: 1 - s.index / results.length,
                    },
                }))
                    .sort((a, b) => b.score - a.score)
                    .slice(0, topK);
            },
        };
    }
    /**
     * Create Cohere reranker wrapper
     */
    createCohereReranker(CohereClass, config) {
        const scorer = new CohereClass(typeof config?.model === "string" ? config.model : undefined);
        return {
            type: "cohere",
            async rerank(results, query, options) {
                const documents = results.map((r) => r.text || r.metadata?.text || "");
                const scores = await scorer.score(query, documents);
                const topK = config?.topK ?? options?.topK ?? 3;
                return scores
                    .map((s) => ({
                    result: results[s.index],
                    score: s.score,
                    details: {
                        semantic: s.score,
                        vector: results[s.index].score ?? 0,
                        position: 1 - s.index / results.length,
                    },
                }))
                    .sort((a, b) => b.score - a.score)
                    .slice(0, topK);
            },
        };
    }
    /**
     * Create simple reranker wrapper
     */
    createSimpleReranker(simpleRerankFn, config) {
        return {
            type: "simple",
            async rerank(results, _query, options) {
                return simpleRerankFn(results, {
                    topK: config?.topK ?? options?.topK,
                    vectorWeight: config?.weights?.vector,
                    positionWeight: config?.weights?.position,
                });
            },
        };
    }
    /**
     * Create batch reranker wrapper
     */
    createBatchReranker(batchRerankFn, config) {
        const factory = this;
        return {
            type: "batch",
            async rerank(results, query, options) {
                if (!factory.modelProvider) {
                    throw new RerankerError("Batch reranker requires a model provider. Call setModelProvider() first.", {
                        rerankerType: "batch",
                    });
                }
                return batchRerankFn(results, query, factory.modelProvider, {
                    ...options,
                    topK: config?.topK ?? options?.topK,
                    weights: config?.weights ?? options?.weights,
                });
            },
        };
    }
    /**
     * Register a reranker with metadata and aliases
     */
    registerReranker(type, factory, metadata) {
        // Store metadata
        this.metadataMap.set(type, metadata);
        // Register with aliases
        this.register(type, factory, metadata.aliases, { metadata });
        logger.debug(`[RerankerFactory] Registered reranker '${type}' with aliases: ${metadata.aliases.join(", ")}`);
    }
    /**
     * Create a reranker by type or alias
     */
    async createReranker(typeOrAlias, config) {
        await this.ensureInitialized();
        const resolvedName = this.resolveName(typeOrAlias);
        if (!this.has(resolvedName)) {
            const available = this.getAvailable();
            throw new RerankerError(`Unknown reranker type: '${typeOrAlias}'. Available types: ${available.join(", ")}`, {
                code: RAGErrorCodes.RERANKER_NOT_FOUND,
                rerankerType: typeOrAlias,
                details: {
                    requestedType: typeOrAlias,
                    availableTypes: available,
                },
            });
        }
        try {
            const reranker = await this.create(resolvedName, config);
            logger.debug(`[RerankerFactory] Created reranker '${resolvedName}' with config:`, config);
            return reranker;
        }
        catch (error) {
            // Re-throw if already a RerankerError
            if (error instanceof RerankerError) {
                throw error;
            }
            throw new RerankerError(`Failed to create reranker '${resolvedName}': ${error instanceof Error ? error.message : String(error)}`, {
                rerankerType: resolvedName,
                cause: error instanceof Error ? error : undefined,
                details: { type: resolvedName, config },
            });
        }
    }
    /**
     * Get metadata for a reranker
     */
    getRerankerMetadata(typeOrAlias) {
        const resolvedName = this.resolveName(typeOrAlias);
        return this.metadataMap.get(resolvedName);
    }
    /**
     * Get default configuration for a reranker
     */
    getDefaultConfig(typeOrAlias) {
        const metadata = this.getRerankerMetadata(typeOrAlias);
        return metadata?.defaultConfig;
    }
    /**
     * Get available reranker types (not including aliases)
     */
    async getAvailableTypes() {
        await this.ensureInitialized();
        return this.getAvailable();
    }
    /**
     * Get all aliases mapped to their types
     */
    getTypeAliases() {
        return this.getAliases();
    }
    /**
     * Check if a type exists
     */
    hasType(typeOrAlias) {
        const resolved = this.resolveName(typeOrAlias);
        return this.has(resolved);
    }
    /**
     * Get rerankers suitable for a use case
     */
    getRerankersForUseCase(useCase) {
        const matches = [];
        const useCaseLower = useCase.toLowerCase();
        for (const [type, metadata] of this.metadataMap) {
            const hasMatch = metadata.useCases.some((uc) => uc.toLowerCase().includes(useCaseLower));
            if (hasMatch) {
                matches.push(type);
            }
        }
        return matches;
    }
    /**
     * Get rerankers that don't require external APIs
     */
    getLocalRerankers() {
        const matches = [];
        for (const [type, metadata] of this.metadataMap) {
            if (!metadata.requiresExternalAPI) {
                matches.push(type);
            }
        }
        return matches;
    }
    /**
     * Get rerankers that don't require AI models
     */
    getModelFreeRerankers() {
        const matches = [];
        for (const [type, metadata] of this.metadataMap) {
            if (!metadata.requiresModel) {
                matches.push(type);
            }
        }
        return matches;
    }
    /**
     * Get all reranker metadata
     */
    getAllMetadata() {
        return new Map(this.metadataMap);
    }
    /**
     * Clear factory and metadata
     */
    clear() {
        super.clear();
        this.metadataMap.clear();
        this.modelProvider = null;
    }
}
/**
 * Global reranker factory singleton
 */
export const rerankerFactory = RerankerFactory.getInstance();
/**
 * Convenience function to create a reranker
 */
export async function createReranker(typeOrAlias, config) {
    return rerankerFactory.createReranker(typeOrAlias, config);
}
/**
 * Convenience function to get available reranker types
 */
export async function getAvailableRerankerTypes() {
    return rerankerFactory.getAvailableTypes();
}
/**
 * Convenience function to get reranker metadata
 */
export function getRerankerMetadata(typeOrAlias) {
    return rerankerFactory.getRerankerMetadata(typeOrAlias);
}
/**
 * Convenience function to get default config
 */
export function getRerankerDefaultConfig(typeOrAlias) {
    return rerankerFactory.getDefaultConfig(typeOrAlias);
}
