import { nexusEventBus } from '../../engines/event-bus.js';
import { getDefaultFederation } from '../../engines/federation.js';
import { podNetwork } from '../../engines/pod-network.js';
import { getSpecialist } from '../../engines/specialist-roster.js';
import { estimateSpecialistSortieCost } from '../../engines/specialist-cost-estimator.js';
import { scheduleFirstSortie } from '../../synapse/bootstrap.js';
import { DarwinLoop } from '../../engines/darwin-loop.js';
import { DarwinJournal } from '../../engines/darwin-journal.js';
import { materializeWorkforceAgent, runDashboardAgentControl } from './workforce.js';
import path from 'node:path';
// Surface "Pillar 2 disconnected" with a tagged empty array. The marker keeps
// existing `.map()`/`.length` iteration code working while letting the frontend
// detect the not-initialized state and render the reason. CLAUDE.md §3.
function notInitializedList(pillar) {
    const arr = [];
    Object.defineProperty(arr, 'notReady', { value: true, enumerable: true });
    Object.defineProperty(arr, 'reason', {
        value: `${pillar} runtime is not attached to this dashboard process — see CLAUDE.md §3.`,
        enumerable: true,
    });
    Object.defineProperty(arr, 'pillar', { value: pillar, enumerable: true });
    return arr;
}
function notInitializedObject(pillar) {
    return {
        notReady: true,
        pillar,
        reason: `${pillar} runtime is not attached to this dashboard process — see CLAUDE.md §3.`,
        items: [],
    };
}
function getSynapseForMutation(ctx) {
    let synapse = ctx.getSynapse();
    if (synapse)
        return synapse;
    const reinit = ctx.getReinitSynapse?.();
    if (reinit) {
        try {
            reinit();
            synapse = ctx.getSynapse();
        }
        catch {
            synapse = undefined;
        }
    }
    return synapse;
}
export const handleGovernanceRoutes = async (ctx, req, res, url) => {
    if (req.method === 'GET' && url.pathname === '/api/synapse/teams') {
        const synapse = ctx.getSynapse();
        ctx.respondJson(res, synapse?.getStrikeTeamStatus?.() ?? notInitializedList('Synapse'));
        return true;
    }
    if (req.method === 'GET' && url.pathname === '/api/synapse/health') {
        const synapse = ctx.getSynapse();
        ctx.respondJson(res, synapse?.getOperativeHealth?.() ?? notInitializedList('Synapse'));
        return true;
    }
    if (req.method === 'GET' && url.pathname === '/api/synapse/approvals') {
        const synapse = ctx.getSynapse();
        ctx.respondJson(res, synapse?.getPendingApprovals?.() ?? notInitializedList('Synapse'));
        return true;
    }
    if (req.method === 'POST' && url.pathname === '/api/synapse/hire') {
        const synapse = getSynapseForMutation(ctx);
        if (!synapse) {
            const initErr = ctx.getSynapseInitError();
            ctx.respondJson(res, {
                error: initErr?.message ?? 'synapse-unavailable',
                code: 'synapse-init-failed',
                hint: 'Synapse could not warm for this hire request. Run `nexus-prime doctor`, then retry from the dashboard.',
                at: initErr?.at ?? null,
            }, 503);
            return true;
        }
        const body = await ctx.readJsonBody(req);
        const name = typeof body.name === 'string' ? body.name : undefined;
        const specialistId = typeof body.specialistId === 'string'
            ? body.specialistId
            : (typeof body.specialist === 'string' ? body.specialist : undefined);
        const requestedGoal = typeof body.goal === 'string' && body.goal.trim()
            ? body.goal.trim()
            : null;
        if (!name && !specialistId) {
            ctx.respondJson(res, { error: 'name-or-specialistId-required' }, 400);
            return true;
        }
        try {
            const result = synapse.hireOperative({
                name,
                roleTitle: typeof body.roleTitle === 'string' ? body.roleTitle : null,
                reportsToOperativeId: typeof body.reportsToOperativeId === 'string' ? body.reportsToOperativeId : null,
                skillId: typeof body.skillId === 'string' ? body.skillId : null,
                specialistId: specialistId ?? null,
                strikeTeamId: typeof body.strikeTeamId === 'string' ? body.strikeTeamId : null,
                budgetCapUsd: typeof body.budgetCapUsd === 'number' ? body.budgetCapUsd : undefined,
                sortieIntervalMs: typeof body.sortieIntervalMs === 'number' ? body.sortieIntervalMs : undefined,
                requestApproval: body.requestApproval === true,
                requestedByOperativeId: typeof body.requestedByOperativeId === 'string' ? body.requestedByOperativeId : 'dashboard-operator',
            });
            nexusEventBus.emit('dashboard.action', {
                action: 'synapse.hire',
                status: 'ok',
                target: name ?? specialistId ?? 'operative',
            });
            // Enrich response with per-sortie cost estimate and dashboard deep-link.
            const specialistProfile = specialistId ? getSpecialist(specialistId) : undefined;
            const pricing = specialistProfile ? estimateSpecialistSortieCost(specialistProfile) : null;
            const operativeId = result?.id ?? result?.operativeId ?? null;
            const baseUrl = ctx.getAddress() ?? 'http://localhost:3377';
            const workforceAgent = operativeId ? materializeWorkforceAgent(ctx, {
                operativeId,
                name: result?.name ?? name ?? specialistProfile?.name ?? specialistId ?? 'Dashboard agent',
                role: result?.roleTitle ?? specialistProfile?.name ?? null,
                specialistId: result?.specialistId ?? specialistId ?? null,
                budgetCapUsd: result?.budgetCapUsd ?? body.budgetCapUsd ?? null,
            }) : null;
            let firstDispatch = null;
            // Optionally fire a warm-up first sortie (body.fireFirstSortie === true).
            // Non-blocking: the dashboard gets an immediate hire response while the
            // Workforce job and run lifecycle continue through SSE and kanban state.
            if (body.fireFirstSortie === true && operativeId) {
                const displayName = result?.name ?? specialistProfile?.name ?? name ?? specialistId ?? operativeId;
                const repoName = path.basename(ctx.repoRoot ?? process.cwd());
                const firstGoal = requestedGoal
                    ?? `Use ${displayName} to inspect ${repoName}, report what is working, and name the next useful task.`;
                firstDispatch = {
                    queued: true,
                    operativeId,
                    mode: 'pending',
                    goal: firstGoal,
                    expectedBehavior: 'Hire creates an operative, assigns this goal as a Workforce job, and moves the job through dispatch or orchestrator fallback.',
                };
                runDashboardAgentControl(ctx, new URL(`${baseUrl}/api/workforce/agents/${encodeURIComponent(operativeId)}/dispatch`), {
                    operativeId,
                    specialistId: result?.specialistId ?? specialistId ?? null,
                    budgetCapUsd: result?.budgetCapUsd ?? 0.50,
                    title: requestedGoal ? `Dashboard goal: ${firstGoal.slice(0, 64)}` : `First sortie: ${displayName}`,
                    goal: firstGoal,
                }).then(r => {
                    nexusEventBus.emit('dashboard.action', {
                        action: 'synapse.first-sortie.dispatched',
                        status: 'ok',
                        target: r.runId ?? r.job?.id ?? operativeId,
                    });
                }).catch((error) => {
                    nexusEventBus.emit('dashboard.action', {
                        action: 'synapse.first-sortie.failed',
                        status: 'fail',
                        target: error?.message ?? String(error),
                    });
                    if (result?.specialistId) {
                        const repoRoot = ctx.repoRoot ?? process.cwd();
                        scheduleFirstSortie({
                            operative: result,
                            repoRoot,
                            repoName: path.basename(repoRoot),
                            budgetCapUsd: result?.budgetCapUsd ?? 0.50,
                        }).catch(() => { });
                    }
                });
            }
            ctx.respondJson(res, {
                operative: result,
                agent: workforceAgent,
                pricing,
                firstSortieEstimate: pricing,
                firstDispatch,
                dashboardUrl: operativeId ? `${baseUrl}/#workforce/${operativeId}` : `${baseUrl}/#workforce`,
            }, 201);
        }
        catch (error) {
            ctx.respondJson(res, { error: error?.message || String(error) }, 500);
        }
        return true;
    }
    if (req.method === 'POST' && url.pathname === '/api/synapse/deploy-crew') {
        // Ensure Architects is live before mandate so coordination bridge is wired.
        // getSynapse() triggers ensureSynapseInit(); getArchitects() triggers ensureArchitectsInit()
        // + reconnectSynapseCoordination() so worklistId is non-null in the pipeline.
        ctx.getArchitects();
        const synapse = getSynapseForMutation(ctx);
        if (!synapse) {
            const initErr = ctx.getSynapseInitError();
            ctx.respondJson(res, {
                error: initErr?.message ?? 'synapse-unavailable',
                code: 'synapse-init-failed',
                hint: 'Check stderr for `synapse-init-failed reason=...`. Common causes: better-sqlite3 native binding mismatch, corrupt state dir, missing coordination bridge.',
                at: initErr?.at ?? null,
            }, 503);
            return true;
        }
        const body = await ctx.readJsonBody(req);
        const mandate = typeof body.mandate === 'string' && body.mandate.trim().length > 0
            ? body.mandate.trim()
            : (typeof body.crew === 'string' ? `Deploy strike team: ${body.crew}` : null);
        if (!mandate) {
            ctx.respondJson(res, { error: 'mandate-or-crew-required' }, 400);
            return true;
        }
        try {
            const team = await synapse.executeMandatePipeline(mandate, {
                budgetUsd: typeof body.budgetUsd === 'number' ? body.budgetUsd : undefined,
                maxOperatives: typeof body.maxOperatives === 'number' ? body.maxOperatives : undefined,
            });
            nexusEventBus.emit('dashboard.action', {
                action: 'synapse.deploy-crew',
                status: 'ok',
                target: body.crew ?? team?.id ?? 'crew',
            });
            ctx.respondJson(res, team, 201);
        }
        catch (error) {
            ctx.respondJson(res, { error: error?.message || String(error) }, 500);
        }
        return true;
    }
    if (req.method === 'GET' && url.pathname === '/api/architects/dispatch') {
        const architects = ctx.getArchitects();
        ctx.respondJson(res, architects?.getDispatchStatus?.() ?? notInitializedObject('Architects'));
        return true;
    }
    if (req.method === 'GET' && url.pathname === '/api/architects/escalations') {
        const architects = ctx.getArchitects();
        ctx.respondJson(res, architects?.getWardEscalations?.() ?? notInitializedList('Architects'));
        return true;
    }
    if (req.method === 'GET' && url.pathname === '/api/architects/worklist') {
        ctx.respondJson(res, ctx.getPreferredArchitectsWorklist(url, url.searchParams.get('worklistId'), url.searchParams.get('strikeTeamId')));
        return true;
    }
    if (req.method === 'GET' && url.pathname === '/api/pod') {
        const limit = parseInt(url.searchParams.get('limit') || '40', 10);
        ctx.respondJson(res, podNetwork.getDashboardSnapshot(limit));
        return true;
    }
    if (req.method === 'GET' && url.pathname.startsWith('/api/pod/')) {
        const workerId = decodeURIComponent(url.pathname.replace('/api/pod/', ''));
        const limit = parseInt(url.searchParams.get('limit') || '20', 10);
        ctx.respondJson(res, podNetwork.getWorker(workerId, limit));
        return true;
    }
    if (req.method === 'GET' && url.pathname === '/api/clients') {
        await ctx.respondCachedJson(res, `clients:${url.search}`, 2_000, async () => {
            const snapshot = ctx.resolveRuntimeSnapshot(url);
            return snapshot?.clients?.detected?.length
                ? snapshot.clients.detected
                : ctx.getClientRegistry()?.listClients(ctx.getAdapters()) ?? [];
        });
        return true;
    }
    if (req.method === 'GET' && url.pathname === '/api/clients/primary') {
        await ctx.respondCachedJson(res, `clients:primary:${url.search}`, 2_000, async () => {
            const snapshot = ctx.resolveRuntimeSnapshot(url);
            return snapshot?.clients?.primary ?? ctx.getClientRegistry()?.getPrimaryClient(ctx.getAdapters()) ?? {};
        });
        return true;
    }
    if (req.method === 'POST' && url.pathname.startsWith('/api/clients/') && url.pathname.endsWith('/reconnect')) {
        const clientId = decodeURIComponent(url.pathname.replace('/api/clients/', '').replace('/reconnect', '').replace(/\/$/, ''));
        const registry = ctx.getClientRegistry();
        if (!registry) {
            ctx.respondJson(res, { error: 'client-registry-unavailable' }, 503);
            return true;
        }
        const client = registry.reconnect(clientId);
        nexusEventBus.emit('dashboard.action', {
            action: 'client.reconnect',
            status: 'ok',
            target: clientId,
        });
        ctx.respondJson(res, client);
        return true;
    }
    if (req.method === 'POST' && url.pathname.startsWith('/api/clients/') && url.pathname.endsWith('/clear')) {
        const clientId = decodeURIComponent(url.pathname.replace('/api/clients/', '').replace('/clear', '').replace(/\/$/, ''));
        const registry = ctx.getClientRegistry();
        if (!registry) {
            ctx.respondJson(res, { error: 'client-registry-unavailable' }, 503);
            return true;
        }
        registry.clear(clientId);
        nexusEventBus.emit('dashboard.action', {
            action: 'client.clear',
            status: 'ok',
            target: clientId,
        });
        ctx.respondJson(res, { ok: true, clientId });
        return true;
    }
    if (req.method === 'GET' && url.pathname === '/api/federation') {
        const runtimeStatus = ctx.getRuntime()?.getNetworkStatus?.();
        const snapshot = runtimeStatus && Object.keys(runtimeStatus).length > 0
            ? runtimeStatus
            : getDefaultFederation().getSnapshot();
        ctx.respondJson(res, {
            relayMode: snapshot?.relay?.connected ? 'active' : 'local-only',
            peers: snapshot?.knownPeers ?? [],
            ...snapshot,
        });
        return true;
    }
    if (req.method === 'POST' && url.pathname === '/api/synapse/reinit') {
        const reinit = ctx.getReinitSynapse?.();
        if (!reinit) {
            ctx.respondJson(res, { ok: false, error: 'reinit-not-available', hint: 'Dashboard server was not started with a reinit provider.' }, 503);
            return true;
        }
        try {
            reinit();
        }
        catch (err) {
            ctx.respondJson(res, { ok: false, error: err?.message ?? String(err) }, 500);
            return true;
        }
        const synapse = ctx.getSynapse();
        if (synapse) {
            nexusEventBus.emit('dashboard.action', { action: 'synapse.reinit', status: 'ok' });
            ctx.respondJson(res, { ok: true });
        }
        else {
            const initErr = ctx.getSynapseInitError();
            ctx.respondJson(res, {
                ok: false,
                error: initErr?.message ?? 'reinit-failed',
                code: 'synapse-init-failed',
                hint: 'Synapse re-initialisation failed. Check stderr for `synapse-init-failed reason=...`.',
                at: initErr?.at ?? null,
            }, 503);
        }
        return true;
    }
    // ── Synapse sortie history (kanban diff + parallel timeline) ──────────────
    if (req.method === 'GET' && url.pathname === '/api/synapse/sorties') {
        const synapse = ctx.getSynapse();
        if (!synapse) {
            ctx.respondJson(res, { sorties: [], error: 'synapse-unavailable' }, 503);
            return true;
        }
        const rawLimit = url.searchParams.get('limit');
        const limit = Math.min(Number.isFinite(Number(rawLimit)) && Number(rawLimit) > 0 ? Math.floor(Number(rawLimit)) : 30, 100);
        const operativeId = url.searchParams.get('operativeId') ?? null;
        try {
            const rows = operativeId
                ? synapse.db.prepare(`SELECT s.id, s.operative_id, s.mission_id, s.status, s.tokens_used, s.cost_usd,
                    s.started_at, s.completed_at,
                    fr.files_changed, fr.findings
             FROM synapse_sorties s
             LEFT JOIN synapse_field_reports fr ON fr.sortie_id = s.id
             WHERE s.operative_id = ?
             ORDER BY s.started_at DESC LIMIT ?`).all(operativeId, limit)
                : synapse.db.prepare(`SELECT s.id, s.operative_id, s.mission_id, s.status, s.tokens_used, s.cost_usd,
                    s.started_at, s.completed_at,
                    fr.files_changed, fr.findings
             FROM synapse_sorties s
             LEFT JOIN synapse_field_reports fr ON fr.sortie_id = s.id
             ORDER BY s.started_at DESC LIMIT ?`).all(limit);
            const sorties = rows.map(r => ({
                id: r.id,
                operativeId: r.operative_id,
                missionId: r.mission_id,
                status: r.status,
                tokensUsed: r.tokens_used ?? 0,
                costUsd: r.cost_usd ?? 0,
                startedAt: r.started_at,
                completedAt: r.completed_at,
                duration: r.completed_at && r.started_at ? r.completed_at - r.started_at : null,
                filesChanged: (() => { try {
                    return JSON.parse(r.files_changed ?? '[]');
                }
                catch {
                    return [];
                } })(),
                findings: (() => { try {
                    return JSON.parse(r.findings ?? '[]');
                }
                catch {
                    return [];
                } })(),
            }));
            ctx.respondJson(res, { sorties });
        }
        catch (err) {
            ctx.respondJson(res, { sorties: [], error: err?.message ?? String(err) });
        }
        return true;
    }
    // ── Darwin auto-propose review gate ────────────────────────────────────────
    if (req.method === 'GET' && url.pathname === '/api/governance/darwin') {
        try {
            const journal = new DarwinJournal();
            const all = journal.loadCycles();
            ctx.respondJson(res, { cycles: all, pending: all.filter(c => c.outcome === 'pending') });
        }
        catch (err) {
            ctx.respondJson(res, { cycles: [], pending: [], error: err?.message ?? String(err) });
        }
        return true;
    }
    if (req.method === 'POST' && /^\/api\/governance\/darwin\/[^/]+\/review$/.test(url.pathname)) {
        const cycleId = url.pathname.split('/')[4] ?? '';
        if (!cycleId) {
            ctx.respondJson(res, { error: 'cycle-id-required' }, 400);
            return true;
        }
        const body = await ctx.readJsonBody(req);
        const decision = typeof body.decision === 'string' ? body.decision : '';
        if (!['approve', 'reject', 'defer'].includes(decision)) {
            ctx.respondJson(res, { error: 'decision must be approve|reject|defer' }, 400);
            return true;
        }
        const reason = typeof body.reason === 'string' ? body.reason.trim() : undefined;
        const learnings = reason ? [reason] : [];
        const action = decision === 'approve' ? 'apply' : decision;
        try {
            const loop = new DarwinLoop();
            const updated = await loop.review(cycleId, action, learnings);
            nexusEventBus.emit('darwin.cycle.complete', {
                id: updated.id,
                outcome: updated.outcome,
                targetFile: updated.targetFile,
            });
            nexusEventBus.emit('dashboard.action', {
                action: `darwin.review.${decision}`,
                status: updated.outcome,
                target: cycleId,
            });
            ctx.respondJson(res, { ok: true, cycle: updated });
        }
        catch (err) {
            ctx.respondJson(res, { ok: false, error: err?.message ?? String(err) }, 500);
        }
        return true;
    }
    return false;
};
