import { ValidationError } from '../errors.js';
import { uniqueStrings } from '../util/text.js';
const TOP_ROLLUP_LIMIT = 12;
const TOP_CLUSTER_LIMIT = 20;
const SEVERITY_ORDER = ['debug', 'info', 'notice', 'warning', 'error', 'critical', 'alert', 'emergency'];
export class ErrorLogInsightService {
    errorLogs;
    reflection;
    constructor(errorLogs, reflection) {
        this.errorLogs = errorLogs;
        this.reflection = reflection;
    }
    async collect(options) {
        const { logs, totalMatched } = await this.errorLogs.collectLogs(options);
        const summaries = logs.map(summarizeLog);
        const clusters = buildClusters(logs);
        return {
            project: options.project,
            generatedAt: new Date().toISOString(),
            totalMatched,
            returned: summaries.length,
            nextOffset: options.offset + summaries.length < totalMatched ? options.offset + summaries.length : undefined,
            filters: options,
            rollups: buildRollups(logs),
            clusters,
            logs: summaries,
            agentBrief: renderAgentBrief(summaries, clusters, totalMatched, options),
        };
    }
    async createReflectionDraft(input) {
        if (!this.reflection) {
            throw new ValidationError('Reflection draft creation requires configured reflection storage.');
        }
        const logs = await this.readSelectedLogs(input.errorLogIds);
        const project = resolveDraftProject(logs, input.project);
        const draftInput = buildReflectionDraftInput(logs, project, input);
        const draft = await this.reflection.createDraft(draftInput);
        const linkedErrorLogIds = [];
        if (input.linkLogs !== false) {
            for (const log of logs) {
                const updated = await this.errorLogs.updateLog(log.id, { reflectionDraftId: draft.id });
                if (updated?.reflectionDraftId === draft.id) {
                    linkedErrorLogIds.push(log.id);
                }
            }
        }
        return { draft, linkedErrorLogIds };
    }
    async resolve(input) {
        const existing = await this.errorLogs.getLog(input.id);
        if (!existing) {
            return undefined;
        }
        const status = input.status ?? 'fixed';
        const log = await this.errorLogs.updateLog(input.id, {
            status,
            reflectionDraftId: input.reflectionDraftId,
            notes: buildResolutionNotes(input),
            metadata: {
                ...input.metadata,
                resolution: {
                    rootCause: input.rootCause,
                    summary: input.resolutionSummary,
                    changedFiles: input.changedFiles ?? [],
                    verificationCommands: input.verificationCommands ?? [],
                    reflectionDraftId: input.reflectionDraftId,
                    resolvedBy: 'tuberosa_resolve_error_log',
                    resolvedAt: new Date().toISOString(),
                },
            },
        });
        if (!log) {
            return undefined;
        }
        return {
            log,
            instruction: log.reflectionDraftId
                ? 'Error log resolved and linked to a reflection draft.'
                : 'Error log resolved. Create or link a reviewed reflection draft if this fix created a durable lesson.',
        };
    }
    async readSelectedLogs(ids) {
        const uniqueIds = uniqueStrings(ids);
        const logs = await Promise.all(uniqueIds.map((id) => this.errorLogs.getLog(id)));
        const missing = uniqueIds.filter((id, index) => !logs[index]);
        if (missing.length > 0) {
            throw new ValidationError(`Error logs not found: ${missing.join(', ')}`);
        }
        return logs.filter((log) => Boolean(log));
    }
}
function buildResolutionNotes(input) {
    return [
        input.notes,
        `Root cause: ${input.rootCause}`,
        `Resolution: ${input.resolutionSummary}`,
        input.changedFiles?.length ? `Changed files: ${input.changedFiles.join(', ')}` : undefined,
        input.verificationCommands?.length ? `Verification: ${input.verificationCommands.join(' | ')}` : undefined,
    ].filter((line) => Boolean(line)).join('\n');
}
function summarizeLog(log) {
    return {
        id: log.id,
        project: log.project,
        category: log.category,
        severity: log.severity,
        status: log.status,
        title: log.title,
        summary: log.summary,
        occurrenceCount: log.occurrenceCount,
        firstSeenAt: log.firstSeenAt,
        lastSeenAt: log.lastSeenAt,
        files: log.files,
        symbols: log.symbols,
        errors: log.errors,
        tags: log.tags,
        fingerprint: log.fingerprint,
        reflectionDraftId: log.reflectionDraftId,
        references: log.references,
    };
}
function buildClusters(logs) {
    const byFingerprint = new Map();
    for (const log of logs) {
        const group = byFingerprint.get(log.fingerprint) ?? [];
        group.push(log);
        byFingerprint.set(log.fingerprint, group);
    }
    return [...byFingerprint.entries()]
        .map(([fingerprint, group]) => {
        const sorted = [...group].sort((left, right) => right.lastSeenAt.localeCompare(left.lastSeenAt));
        const latest = sorted[0];
        return {
            fingerprint,
            title: latest.title,
            count: group.length,
            occurrenceCount: sum(group.map((log) => log.occurrenceCount)),
            severity: mostSevere(group.map((log) => log.severity)),
            statuses: uniqueStrings(group.map((log) => log.status)),
            categories: uniqueStrings(group.map((log) => log.category)),
            firstSeenAt: minString(group.map((log) => log.firstSeenAt)),
            lastSeenAt: maxString(group.map((log) => log.lastSeenAt)),
            logIds: sorted.map((log) => log.id),
            files: topValues(group.flatMap((log) => log.files), 8).map((entry) => entry.value),
            symbols: topValues(group.flatMap((log) => log.symbols), 8).map((entry) => entry.value),
            errors: topValues(group.flatMap((log) => log.errors), 8).map((entry) => entry.value),
            tags: topValues(group.flatMap((log) => log.tags), 8).map((entry) => entry.value),
        };
    })
        .sort((left, right) => (right.occurrenceCount - left.occurrenceCount
        || severityRank(right.severity) - severityRank(left.severity)
        || right.lastSeenAt.localeCompare(left.lastSeenAt)))
        .slice(0, TOP_CLUSTER_LIMIT);
}
function buildRollups(logs) {
    return {
        categories: topValues(logs.map((log) => log.category), TOP_ROLLUP_LIMIT),
        severities: topValues(logs.map((log) => log.severity), TOP_ROLLUP_LIMIT),
        statuses: topValues(logs.map((log) => log.status), TOP_ROLLUP_LIMIT),
        files: topValues(logs.flatMap((log) => log.files), TOP_ROLLUP_LIMIT),
        symbols: topValues(logs.flatMap((log) => log.symbols), TOP_ROLLUP_LIMIT),
        errors: topValues(logs.flatMap((log) => log.errors), TOP_ROLLUP_LIMIT),
        tags: topValues(logs.flatMap((log) => log.tags), TOP_ROLLUP_LIMIT),
    };
}
function renderAgentBrief(logs, clusters, totalMatched, options) {
    const lines = [
        '# Error Log Brief',
        '',
        `Matched ${totalMatched} incident${totalMatched === 1 ? '' : 's'}; returned ${logs.length}.`,
    ];
    if (options.project) {
        lines.push(`Project: ${options.project}.`);
    }
    if (clusters.length > 0) {
        lines.push('', '## Recurring Patterns');
        for (const cluster of clusters.slice(0, 5)) {
            lines.push(`- ${cluster.title} (${cluster.occurrenceCount} occurrence${cluster.occurrenceCount === 1 ? '' : 's'}, ${cluster.severity}, ${cluster.statuses.join('/')})`);
        }
    }
    if (logs.length > 0) {
        lines.push('', '## Recent Incidents');
        for (const log of logs.slice(0, 8)) {
            lines.push(`- ${log.title} [${log.severity}/${log.status}] last seen ${log.lastSeenAt}; id ${log.id}`);
        }
    }
    lines.push('', 'Use tuberosa_get_error_log only for incidents that need raw debugging context. Create a reviewed reflection draft for durable lessons before they become searchable memory.');
    return `${lines.join('\n')}\n`;
}
function buildReflectionDraftInput(logs, project, input) {
    const clusters = buildClusters(logs);
    const title = input.title ?? defaultDraftTitle(logs, clusters);
    const summary = input.summary ?? defaultDraftSummary(logs, clusters);
    const content = input.content ?? defaultDraftContent(logs, clusters);
    const references = buildDraftReferences(logs);
    return {
        project,
        title,
        summary,
        content,
        itemType: 'bugfix',
        triggerType: 'error_recovery',
        labels: buildDraftLabels(logs, project),
        references,
        metadata: {
            ...(input.metadata ?? {}),
            taxonomy: 'incident_lesson',
            source: 'error-log-insights',
            errorLogIds: logs.map((log) => log.id),
            fingerprints: uniqueStrings(logs.map((log) => log.fingerprint)),
            categories: uniqueStrings(logs.map((log) => log.category)),
            severities: uniqueStrings(logs.map((log) => log.severity)),
            statuses: uniqueStrings(logs.map((log) => log.status)),
            tags: uniqueStrings(logs.flatMap((log) => log.tags)),
            references,
        },
    };
}
function resolveDraftProject(logs, project) {
    if (project) {
        return project;
    }
    const projects = uniqueStrings(logs.map((log) => log.project ?? 'unprojected'));
    if (projects.length > 1) {
        throw new ValidationError('errorLogIds span multiple projects; provide project to create a cross-project reflection draft.');
    }
    return projects[0] === 'unprojected' ? undefined : projects[0];
}
function defaultDraftTitle(logs, clusters) {
    const title = clusters[0]?.title ?? logs[0]?.title ?? 'Error log incident lesson';
    return `Incident lesson: ${title}`.slice(0, 160);
}
function defaultDraftSummary(logs, clusters) {
    const top = clusters[0];
    if (top) {
        return `A recurring ${top.severity} incident appeared ${top.occurrenceCount} time(s) across ${logs.length} log record(s).`;
    }
    return `A selected error-log incident needs a durable reviewed lesson for future agents.`;
}
function defaultDraftContent(logs, clusters) {
    const lines = [
        'Durable lesson candidate from selected Tuberosa error logs.',
        '',
        '## Incident Pattern',
    ];
    for (const cluster of clusters.slice(0, 5)) {
        lines.push(`- ${cluster.title}: ${cluster.occurrenceCount} occurrence(s), severity ${cluster.severity}, status ${cluster.statuses.join('/')}.`);
    }
    lines.push('', '## Evidence');
    for (const log of logs.slice(0, 10)) {
        lines.push(`- ${log.title}: ${log.summary} (${log.category}, ${log.severity}, ${log.status}, id ${log.id}).`);
    }
    lines.push('', 'Review this draft after the fix is known. Keep only the durable cause, working path, and prevention rule before approval.');
    return lines.join('\n');
}
function buildDraftLabels(logs, project) {
    const labels = [];
    if (project) {
        labels.push({ type: 'project', value: project, weight: 1 });
    }
    labels.push({ type: 'task_type', value: 'debugging', weight: 0.9 });
    for (const severity of uniqueStrings(logs.map((log) => log.severity))) {
        labels.push({ type: 'severity', value: severity, weight: 0.8 });
    }
    for (const file of topValues(logs.flatMap((log) => log.files), 12)) {
        labels.push({ type: 'file', value: file.value, weight: 0.85 });
    }
    for (const symbol of topValues(logs.flatMap((log) => log.symbols), 12)) {
        labels.push({ type: 'symbol', value: symbol.value, weight: 0.8 });
    }
    for (const error of topValues(logs.flatMap((log) => log.errors), 12)) {
        labels.push({ type: 'error', value: error.value, weight: 0.9 });
    }
    return labels;
}
function buildDraftReferences(logs) {
    const references = [];
    for (const log of logs) {
        references.push({
            type: 'external',
            uri: `tuberosa://error-logs/${log.id}`,
            metadata: { category: log.category, severity: log.severity, status: log.status },
        });
        references.push(...log.references.filter((reference) => reference.type === 'file'));
    }
    return uniqueReferences(references).slice(0, 40);
}
function uniqueReferences(references) {
    const seen = new Set();
    return references.filter((reference) => {
        const key = `${reference.type}:${reference.uri}:${reference.lineStart ?? ''}:${reference.lineEnd ?? ''}`;
        if (seen.has(key)) {
            return false;
        }
        seen.add(key);
        return true;
    });
}
function topValues(values, limit) {
    const counts = new Map();
    for (const value of values) {
        const trimmed = value.trim();
        if (!trimmed) {
            continue;
        }
        const key = trimmed.toLowerCase();
        const existing = counts.get(key);
        counts.set(key, { value: existing?.value ?? trimmed, count: (existing?.count ?? 0) + 1 });
    }
    return [...counts.values()]
        .sort((left, right) => right.count - left.count || left.value.localeCompare(right.value))
        .slice(0, limit);
}
function mostSevere(values) {
    return values.reduce((current, candidate) => (severityRank(candidate) > severityRank(current) ? candidate : current), 'debug');
}
function severityRank(value) {
    return SEVERITY_ORDER.indexOf(value);
}
function sum(values) {
    return values.reduce((total, value) => total + value, 0);
}
function minString(values) {
    return values.reduce((min, value) => value < min ? value : min, values[0] ?? '');
}
function maxString(values) {
    return values.reduce((max, value) => value > max ? value : max, values[0] ?? '');
}
