import fs from 'fs/promises';
import path from 'path';
import { resolveWorkspaceContext } from '../../engines/workspace-resolver.js';
function safeWorkspacePath(value, allowedRoots) {
    if (!value)
        return undefined;
    const abs = path.resolve(String(value));
    return allowedRoots.some((root) => abs === root || abs.startsWith(root + path.sep)) ? abs : undefined;
}
async function isDirectory(value) {
    try {
        return (await fs.stat(value)).isDirectory();
    }
    catch {
        return false;
    }
}
async function normalizeRagInputs(body, allowedRoots) {
    const rawInputs = Array.isArray(body?.inputs) ? body.inputs : [body ?? {}];
    const normalized = await Promise.all(rawInputs
        .map(async (entry) => {
        let filePath = safeWorkspacePath(entry?.filePath, allowedRoots);
        let folderPath = safeWorkspacePath(entry?.folderPath, allowedRoots);
        if (filePath && !folderPath && await isDirectory(filePath)) {
            folderPath = filePath;
            filePath = undefined;
        }
        if (filePath && folderPath && filePath === folderPath) {
            if (await isDirectory(filePath)) {
                filePath = undefined;
            }
            else {
                folderPath = undefined;
            }
        }
        return {
            filePath,
            folderPath,
            folderGlob: entry?.folderGlob ? String(entry.folderGlob) : undefined,
            url: entry?.url ? String(entry.url) : undefined,
            text: entry?.text ? String(entry.text) : undefined,
            label: entry?.label ? String(entry.label) : undefined,
            tags: Array.isArray(entry?.tags) ? entry.tags.map(String) : [],
        };
    }));
    return normalized.filter((entry) => entry.filePath || entry.folderPath || entry.url || entry.text);
}
export const handleMemoryRoutes = async (ctx, req, res, url) => {
    if (req.method === 'GET' && url.pathname === '/api/knowledge-fabric/session') {
        const snapshot = ctx.resolveRuntimeSnapshot(url);
        ctx.respondJson(res, snapshot?.knowledgeFabric ?? ctx.getOrchestrator()?.getKnowledgeFabricSnapshot?.() ?? {});
        return true;
    }
    if (req.method === 'GET' && url.pathname === '/api/knowledge-fabric/provenance') {
        const snapshot = ctx.resolveRuntimeSnapshot(url);
        ctx.respondJson(res, snapshot?.knowledgeFabric?.provenance ?? ctx.getOrchestrator()?.getKnowledgeFabricProvenance?.() ?? { entries: [] });
        return true;
    }
    if (req.method === 'GET' && url.pathname === '/api/models/tiers') {
        const snapshot = ctx.resolveRuntimeSnapshot(url);
        const orchestrator = ctx.getOrchestrator();
        ctx.respondJson(res, snapshot?.knowledgeFabric
            ? {
                policy: snapshot.knowledgeFabric.modelTierPolicy,
                trace: snapshot.knowledgeFabric.modelTierTrace,
            }
            : orchestrator?.getModelTierTrace?.() ?? { trace: [] });
        return true;
    }
    if (req.method === 'GET' && url.pathname === '/api/rag/collections') {
        ctx.respondJson(res, ctx.getOrchestrator()?.listRagCollections?.() ?? []);
        return true;
    }
    if (req.method === 'GET' && url.pathname.startsWith('/api/rag/collections/')) {
        const collectionId = decodeURIComponent(url.pathname.replace('/api/rag/collections/', ''));
        const collection = ctx.getOrchestrator()?.getRagCollection?.(collectionId);
        ctx.respondJson(res, collection ?? { error: 'rag-collection-not-found', collectionId }, collection ? 200 : 404);
        return true;
    }
    if (req.method === 'GET' && url.pathname === '/api/patterns/search') {
        const query = url.searchParams.get('q') ?? '';
        const limit = parseInt(url.searchParams.get('limit') || '8', 10);
        const orchestrator = ctx.getOrchestrator();
        const results = query.trim()
            ? orchestrator?.searchPatterns?.(query, limit) ?? []
            : (orchestrator?.listPatterns?.() ?? []).slice(0, Math.max(1, limit));
        ctx.respondJson(res, results);
        return true;
    }
    if (req.method === 'GET' && url.pathname === '/api/memory') {
        const limit = parseInt(url.searchParams.get('limit') || '40', 10);
        const tier = url.searchParams.get('tier') ?? undefined;
        const tag = url.searchParams.get('tag') ?? undefined;
        const linkedType = url.searchParams.get('linkedType') ?? undefined;
        const recencyMs = url.searchParams.get('recencyMs');
        const showPhantom = url.searchParams.get('showPhantom') === 'true';
        const lane = url.searchParams.get('lane') ?? undefined;
        const repoId = url.searchParams.get('repoId') ?? undefined;
        const workspaceId = url.searchParams.get('workspaceId') ?? undefined;
        const projectId = url.searchParams.get('projectId') ?? undefined;
        const includeHidden = url.searchParams.get('includeHidden') === 'true';
        const includeInactive = url.searchParams.get('includeInactive') === 'true';
        // ?repo=<name> scopes results to memories tagged repo:<name> (written by auto-memory).
        // Defaults to the current workspace repo name when not supplied by the frontend.
        const repoNameRaw = url.searchParams.get('repo');
        const repoName = repoNameRaw && repoNameRaw.trim().length > 0 ? repoNameRaw.trim() : undefined;
        ctx.respondJson(res, ctx.listDashboardMemories({
            limit,
            tier,
            tag,
            linkedType,
            recencyMs: recencyMs ? parseInt(recencyMs, 10) : undefined,
            showPhantom,
            lane,
            repoId,
            workspaceId,
            projectId,
            includeHidden,
            includeInactive,
            repoName,
        }));
        return true;
    }
    if (req.method === 'GET' && url.pathname === '/api/memory/cross-project') {
        const scopeRaw = String(url.searchParams.get('scope') || 'shared').toLowerCase();
        if (!['shared', 'promoted'].includes(scopeRaw)) {
            ctx.respondJson(res, {
                error: 'invalid-scope',
                message: 'scope must be shared or promoted',
                allowedScopes: ['shared', 'promoted'],
            }, 400);
            return true;
        }
        const tags = String(url.searchParams.get('tags') || '')
            .split(',')
            .map((tag) => tag.trim())
            .filter(Boolean)
            .map((tag) => tag.startsWith('#') ? tag : `#${tag}`);
        const q = String(url.searchParams.get('q') || '').trim();
        const fuzzy = String(url.searchParams.get('fuzzy') || '').toLowerCase() === 'true';
        const repoId = String(url.searchParams.get('repoId') || '').trim() || undefined;
        const limit = Math.max(1, Math.min(parseInt(url.searchParams.get('limit') || '20', 10), 100));
        try {
            ctx.respondJson(res, ctx.listCrossProjectMemories({
                scope: scopeRaw,
                tags,
                q,
                fuzzy,
                repoId,
                limit,
            }));
        }
        catch (error) {
            ctx.respondJson(res, {
                error: 'cross-project-query-failed',
                message: error?.message ?? String(error),
                retry: 'Retry with a narrower query or lower limit.',
            }, 503);
        }
        return true;
    }
    if (req.method === 'GET' && url.pathname === '/api/memory/projects') {
        const limit = Math.max(1, Math.min(parseInt(url.searchParams.get('limit') || '60', 10), 200));
        ctx.respondJson(res, ctx.listCrossProjectProjects(limit));
        return true;
    }
    if (req.method === 'GET' && url.pathname === '/api/memory/audit') {
        const limit = parseInt(url.searchParams.get('limit') || '80', 10);
        ctx.respondJson(res, ctx.getRuntime()?.auditMemory?.(limit) ?? { scanned: 0, quarantined: [], findings: [] });
        return true;
    }
    if (req.method === 'GET' && url.pathname === '/api/memory/samples') {
        const tier = url.searchParams.get('tier') ?? 'prefrontal';
        const limit = Math.max(1, Math.min(parseInt(url.searchParams.get('limit') || '3', 10), 10));
        const memory = ctx.getMemory();
        if (!memory) {
            ctx.respondJson(res, { tier, samples: [] });
            return true;
        }
        const samples = ctx.listDashboardMemories({ limit, tier, includeHidden: false });
        ctx.respondJson(res, { tier, samples });
        return true;
    }
    if (req.method === 'GET' && url.pathname === '/api/memory/health') {
        ctx.respondJson(res, ctx.getRuntime()?.getMemoryHealth?.() ?? {
            generatedAt: Date.now(),
            schemaVersion: 0,
            total: 0,
            active: 0,
            quarantined: 0,
            scrap: 0,
            expired: 0,
            promoted: 0,
            shared: 0,
            purgeEligible: 0,
            quarantineBacklog: 0,
            lastHygieneAt: null,
            topTags: [],
        });
        return true;
    }
    if (req.method === 'GET' && url.pathname === '/api/memory/shared') {
        const snapshot = ctx.resolveRuntimeSnapshot(url);
        const runtime = ctx.getRuntime();
        const shared = snapshot?.runtimeId && runtime?.getRuntimeId?.() === snapshot.runtimeId
            ? runtime.getSharedMemorySnapshot?.(12)
            : [];
        ctx.respondJson(res, shared ?? []);
        return true;
    }
    if (req.method === 'GET' && url.pathname === '/api/memory/trace') {
        const id = url.searchParams.get('id') ?? '';
        const snapshot = ctx.resolveRuntimeSnapshot(url);
        const runtime = ctx.getRuntime();
        const traced = snapshot?.runtimeId && runtime?.getRuntimeId?.() === snapshot.runtimeId
            ? runtime.traceMemory?.(id)
            : ctx.getMemory()?.trace?.(id);
        ctx.respondJson(res, traced ?? { error: 'memory-trace-not-found', id }, traced ? 200 : 404);
        return true;
    }
    if (req.method === 'GET' && url.pathname === '/api/memory/quarantine') {
        const limit = parseInt(url.searchParams.get('limit') || '40', 10);
        ctx.respondJson(res, ctx.getRuntime()?.listMemoryQuarantine?.(limit) ?? []);
        return true;
    }
    if (req.method === 'GET' && url.pathname.startsWith('/api/memory/') && url.pathname.endsWith('/network')) {
        const id = decodeURIComponent(url.pathname.replace('/api/memory/', '').replace('/network', '').replace(/\/$/, ''));
        const depth = parseInt(url.searchParams.get('depth') || '2', 10);
        const limit = parseInt(url.searchParams.get('limit') || '40', 10);
        const includeQuarantined = ['1', 'true', 'yes'].includes(String(url.searchParams.get('includeQuarantined') || '').toLowerCase());
        ctx.respondJson(res, ctx.getMemory()?.getNetworkSnapshot?.(id, depth, limit, { includeQuarantined }) ?? { focusId: id, nodes: [], links: [] });
        return true;
    }
    if (req.method === 'GET' && url.pathname.startsWith('/api/memory/')) {
        const id = decodeURIComponent(url.pathname.replace('/api/memory/', ''));
        const memory = ctx.getMemory();
        const detail = memory?.trace?.(id) ?? memory?.getDetail?.(id);
        ctx.respondJson(res, detail ?? { error: 'memory-not-found', id }, detail ? 200 : 404);
        return true;
    }
    if (req.method === 'POST' && url.pathname === '/api/rag/collections') {
        const body = await ctx.readJsonBody(req);
        const orchestrator = ctx.getOrchestrator();
        if (!orchestrator) {
            ctx.respondJson(res, { error: 'orchestrator-unavailable' }, 503);
            return true;
        }
        if (!String(body.name ?? '').trim()) {
            ctx.respondJson(res, { error: 'name-required' }, 400);
            return true;
        }
        ctx.respondJson(res, orchestrator.createRagCollection({
            name: String(body.name),
            description: body.description ? String(body.description) : undefined,
            tags: Array.isArray(body.tags) ? body.tags.map(String) : [],
            scope: body.scope === 'project' ? 'project' : 'session',
        }), 201);
        return true;
    }
    if (req.method === 'POST' && /\/api\/rag\/collections\/[^/]+\/ingest$/.test(url.pathname)) {
        const collectionId = decodeURIComponent(url.pathname.split('/')[4] || '');
        const body = await ctx.readJsonBody(req);
        const orchestrator = ctx.getOrchestrator();
        const workspace = ctx.workspaceContextProvider?.() ?? resolveWorkspaceContext({ workspaceRoot: ctx.repoRoot });
        const allowedRoots = [workspace.workspaceRoot, workspace.repoRoot].map((root) => path.resolve(root));
        if (!orchestrator) {
            ctx.respondJson(res, { error: 'orchestrator-unavailable' }, 503);
            return true;
        }
        const inputs = await normalizeRagInputs(body, allowedRoots);
        ctx.respondJson(res, await orchestrator.ingestRagCollection(collectionId, inputs));
        return true;
    }
    if (req.method === 'POST' && url.pathname === '/api/rag/ingest') {
        const body = await ctx.readJsonBody(req);
        const collectionId = String(body?.collectionId ?? body?.id ?? '').trim();
        const orchestrator = ctx.getOrchestrator();
        const workspace = ctx.workspaceContextProvider?.() ?? resolveWorkspaceContext({ workspaceRoot: ctx.repoRoot });
        const allowedRoots = [workspace.workspaceRoot, workspace.repoRoot].map((root) => path.resolve(root));
        if (!orchestrator) {
            ctx.respondJson(res, { error: 'orchestrator-unavailable' }, 503);
            return true;
        }
        if (!collectionId) {
            ctx.respondJson(res, { error: 'collectionId-required' }, 400);
            return true;
        }
        let inputs = await normalizeRagInputs(body, allowedRoots);
        if (!inputs.length && !body?.inputs && !body?.filePath && !body?.folderPath && !body?.url && !body?.text) {
            inputs = [{
                    folderPath: workspace.repoRoot,
                    label: workspace.repoName || path.basename(workspace.repoRoot),
                    tags: ['repo', 'dashboard'],
                }];
        }
        ctx.respondJson(res, await orchestrator.ingestRagCollection(collectionId, inputs));
        return true;
    }
    if (req.method === 'POST' && /\/api\/rag\/collections\/[^/]+\/attach$/.test(url.pathname)) {
        const collectionId = decodeURIComponent(url.pathname.split('/')[4] || '');
        const orchestrator = ctx.getOrchestrator();
        if (!orchestrator) {
            ctx.respondJson(res, { error: 'orchestrator-unavailable' }, 503);
            return true;
        }
        ctx.respondJson(res, orchestrator.attachRagCollection(collectionId));
        return true;
    }
    if (req.method === 'POST' && /\/api\/rag\/collections\/[^/]+\/detach$/.test(url.pathname)) {
        const collectionId = decodeURIComponent(url.pathname.split('/')[4] || '');
        const orchestrator = ctx.getOrchestrator();
        if (!orchestrator) {
            ctx.respondJson(res, { error: 'orchestrator-unavailable' }, 503);
            return true;
        }
        ctx.respondJson(res, orchestrator.detachRagCollection(collectionId));
        return true;
    }
    if (req.method === 'DELETE' && url.pathname.startsWith('/api/rag/collections/')) {
        const collectionId = decodeURIComponent(url.pathname.replace('/api/rag/collections/', ''));
        const orchestrator = ctx.getOrchestrator();
        if (!orchestrator) {
            ctx.respondJson(res, { error: 'orchestrator-unavailable' }, 503);
            return true;
        }
        const removed = orchestrator.deleteRagCollection(collectionId);
        ctx.respondJson(res, removed ? { ok: true, collectionId } : { error: 'rag-collection-not-found', collectionId }, removed ? 200 : 404);
        return true;
    }
    return false;
};
