import { parseError } from '../parsing/error-parser.js';
import { generateFingerprint } from '../matching/fingerprint.js';
import { matchError } from '../matching/error-matcher.js';
import { getEventBus } from '../utils/events.js';
import { getLogger } from '../utils/logger.js';
export class ErrorService {
    errorRepo;
    projectRepo;
    synapseManager;
    logger = getLogger();
    eventBus = getEventBus();
    matchingConfig = null;
    embeddingEngine = null;
    autoResolution = null;
    constructor(errorRepo, projectRepo, synapseManager, matchingConfig) {
        this.errorRepo = errorRepo;
        this.projectRepo = projectRepo;
        this.synapseManager = synapseManager;
        this.matchingConfig = matchingConfig ?? null;
    }
    setEmbeddingEngine(engine) {
        this.embeddingEngine = engine;
    }
    setAutoResolution(service) {
        this.autoResolution = service;
    }
    report(input) {
        // 1. Ensure project exists
        let project = this.projectRepo.findByName(input.project);
        if (!project) {
            const id = this.projectRepo.create({ name: input.project, path: null, language: null, framework: null });
            project = this.projectRepo.getById(id);
        }
        // 2. Build context from available information
        const context = this.buildContext(input);
        // 3. Parse the error
        const parsed = parseError(input.errorOutput);
        if (!parsed) {
            this.logger.warn('Could not parse error output');
            const errorId = this.errorRepo.create({
                project_id: project.id,
                terminal_id: input.terminalId ?? null,
                fingerprint: '',
                type: 'UnknownError',
                message: input.errorOutput.split('\n')[0] ?? input.errorOutput,
                raw_output: input.errorOutput,
                context,
                file_path: input.filePath ?? null,
                line_number: null,
                column_number: null,
            });
            return { errorId, isNew: true, matches: [] };
        }
        // 4. Generate fingerprint
        const fingerprint = generateFingerprint(parsed.errorType, parsed.message, parsed.frames);
        // 5. Check for existing error with same fingerprint
        const existing = this.errorRepo.findByFingerprint(fingerprint);
        if (existing.length > 0) {
            const err = existing[0];
            this.errorRepo.incrementOccurrence(err.id);
            // Update context if previously null
            if (!err.context && context) {
                this.errorRepo.update(err.id, { context });
            }
            this.logger.info(`Known error (id=${err.id}), occurrence incremented`);
            // Strengthen synapse
            this.synapseManager.strengthen({ type: 'error', id: err.id }, { type: 'project', id: project.id }, 'co_occurs');
            return { errorId: err.id, isNew: false, matches: [] };
        }
        // 6. Create new error record
        const errorId = this.errorRepo.create({
            project_id: project.id,
            terminal_id: input.terminalId ?? null,
            fingerprint,
            type: parsed.errorType,
            message: parsed.message,
            raw_output: input.errorOutput,
            context,
            file_path: parsed.sourceFile ?? input.filePath ?? null,
            line_number: parsed.sourceLine ?? null,
            column_number: null,
        });
        // 7. Create synapse: error ↔ project
        this.synapseManager.strengthen({ type: 'error', id: errorId }, { type: 'project', id: project.id }, 'co_occurs');
        // 8. Find similar errors (with vector scores if embedding engine is available)
        const candidates = this.errorRepo.findByProject(project.id)
            .filter(e => e.id !== errorId);
        const newError = this.errorRepo.getById(errorId);
        const vectorScores = this.embeddingEngine?.isReady()
            ? this.embeddingEngine.computeErrorVectorScores(errorId, project.id)
            : undefined;
        const matches = matchError(newError, candidates, vectorScores);
        // 9. Create similarity synapses for strong matches
        for (const match of matches.filter(m => m.isStrong)) {
            this.synapseManager.strengthen({ type: 'error', id: errorId }, { type: 'error', id: match.errorId }, 'similar_to');
        }
        // 10. Error Chain Detection: link to recent unresolved errors in same project
        this.detectErrorChain(errorId, project.id);
        // 11. Cross-Project Transfer Learning
        let crossProjectMatches = [];
        if (this.matchingConfig?.crossProjectMatching) {
            crossProjectMatches = this.findCrossProjectMatches(newError, project.id);
            for (const match of crossProjectMatches.filter(m => m.isStrong)) {
                this.synapseManager.strengthen({ type: 'error', id: errorId }, { type: 'error', id: match.errorId }, 'cross_project');
            }
        }
        // 12. Auto-Resolution: find solutions for matched errors
        let suggestions;
        if (this.autoResolution && (matches.length > 0 || crossProjectMatches.length > 0)) {
            suggestions = this.autoResolution.getSuggestions(errorId, matches, crossProjectMatches);
        }
        this.eventBus.emit('error:reported', { errorId, projectId: project.id, fingerprint });
        this.logger.info(`New error reported (id=${errorId}, type=${parsed.errorType})`);
        return { errorId, isNew: true, matches, crossProjectMatches, suggestions };
    }
    query(input) {
        if (input.search && input.search.trim()) {
            return this.errorRepo.search(input.search.trim());
        }
        if (input.resolved === false) {
            return this.errorRepo.findUnresolved(input.projectId);
        }
        if (input.projectId) {
            return this.errorRepo.findByProject(input.projectId);
        }
        // Default: return recent errors (most recent first)
        return this.errorRepo.findAll(input.limit ?? 100);
    }
    matchSimilar(errorId) {
        const error = this.errorRepo.getById(errorId);
        if (!error)
            return [];
        const candidates = this.errorRepo.findByProject(error.project_id)
            .filter(e => e.id !== errorId);
        // Hybrid search: include vector scores if embedding engine is available
        const vectorScores = this.embeddingEngine?.isReady()
            ? this.embeddingEngine.computeErrorVectorScores(errorId, error.project_id)
            : undefined;
        return matchError(error, candidates, vectorScores);
    }
    resolve(errorId, solutionId) {
        this.errorRepo.update(errorId, {
            resolved: 1,
            resolved_at: new Date().toISOString(),
        });
        if (solutionId) {
            this.eventBus.emit('error:resolved', { errorId, solutionId });
        }
    }
    getById(id) {
        return this.errorRepo.getById(id);
    }
    countSince(since, projectId) {
        return this.errorRepo.countSince(since, projectId);
    }
    getErrorChain(errorId) {
        return {
            parents: this.errorRepo.findChainParents(errorId),
            children: this.errorRepo.findChainChildren(errorId),
        };
    }
    findCrossProjectMatches(error, currentProjectId) {
        const allProjects = this.projectRepo.getAll();
        const otherProjects = allProjects.filter(p => p.id !== currentProjectId);
        const weight = this.matchingConfig?.crossProjectWeight ?? 0.7;
        const allMatches = [];
        for (const project of otherProjects) {
            const candidates = this.errorRepo.findByProject(project.id)
                .filter(e => e.resolved === 1); // Only look at resolved errors from other projects
            const matches = matchError(error, candidates);
            // Apply cross-project weight discount
            for (const match of matches) {
                match.score *= weight;
                match.isStrong = match.score >= 0.90;
                allMatches.push(match);
            }
        }
        return allMatches.sort((a, b) => b.score - a.score).slice(0, 5);
    }
    detectErrorChain(newErrorId, projectId) {
        // Look for recent unresolved errors in the same project (last 10 minutes)
        const tenMinutesAgo = new Date(Date.now() - 10 * 60 * 1000).toISOString();
        const recentErrors = this.errorRepo.findRecentByProject(projectId, tenMinutesAgo, 5);
        for (const recent of recentErrors) {
            if (recent.id === newErrorId)
                continue;
            if (recent.resolved === 0) {
                // This new error appeared while trying to fix a recent error → chain
                this.errorRepo.createChain(recent.id, newErrorId, 'caused_by_fix');
                this.synapseManager.strengthen({ type: 'error', id: recent.id }, { type: 'error', id: newErrorId }, 'causes');
                this.logger.info(`Error chain: #${recent.id} → #${newErrorId} (caused_by_fix)`);
                break; // Link to most recent parent only
            }
        }
    }
    buildContext(input) {
        const parts = [];
        if (input.taskContext)
            parts.push(`task: ${input.taskContext}`);
        if (input.command)
            parts.push(`command: ${input.command}`);
        if (input.workingDirectory)
            parts.push(`cwd: ${input.workingDirectory}`);
        if (input.filePath)
            parts.push(`file: ${input.filePath}`);
        return parts.length > 0 ? parts.join(' | ') : null;
    }
}
//# sourceMappingURL=error.service.js.map