/**
 * Dashboard routes for the unified workforce module.
 *
 * GET /api/workforce/kanban    — full kanban board (all lanes)
 * GET /api/workforce/workers   — worker list
 * GET /api/workforce/jobs      — job list with optional ?status= and ?client= filters
 */
import { getWorkforce } from '../../workforce/index.js';
import { getSpans, listRecentRuns } from '../../engines/orchestrator/store.js';
import { pushDispatch } from '../../engines/dispatch/push-dispatch.js';
import { nexusEventBus } from '../../engines/event-bus.js';
import { resolveWorkspaceContext } from '../../engines/workspace-resolver.js';
import path from 'path';
const VALID_WORKER_STATUSES = new Set(['idle', 'active', 'stale', 'retired']);
const VALID_JOB_STATUSES = new Set(['backlog', 'ready', 'claimed', 'wip', 'blocked', 'review', 'done', 'failed', 'cancelled']);
function safeLimit(raw, def) {
    const n = Number(raw);
    return Number.isFinite(n) && n > 0 ? Math.floor(n) : def;
}
function trimString(value) {
    return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null;
}
function getRepoIdentity(ctx, url) {
    const selected = url ? ctx.getSelectedRepoIdentity(url) : null;
    const repoRoot = trimString(selected?.repoRoot) ?? ctx.repoRoot ?? process.cwd();
    try {
        const workspace = resolveWorkspaceContext({ workspaceRoot: repoRoot });
        return {
            repoRoot: workspace.repoRoot,
            repoName: trimString(selected?.repoName) ?? workspace.repoName ?? path.basename(workspace.repoRoot),
        };
    }
    catch {
        return { repoRoot, repoName: trimString(selected?.repoName) ?? (path.basename(repoRoot) || 'workspace') };
    }
}
function getOperativeName(ctx, operativeId) {
    const operatives = ctx.getSynapse()?.getOperativeHealth?.() ?? [];
    const found = Array.isArray(operatives)
        ? operatives.find((op) => op?.id === operativeId || op?.operativeId === operativeId)
        : null;
    return trimString(found?.name) ?? trimString(found?.roleTitle) ?? trimString(found?.role) ?? `Agent ${operativeId.slice(0, 8)}`;
}
export function materializeWorkforceAgent(ctx, input) {
    const wf = getWorkforce(ctx.repoRoot ?? process.cwd());
    const worker = wf.registerWorker({
        id: input.operativeId,
        name: input.name ?? getOperativeName(ctx, input.operativeId),
        role: input.role ?? input.specialistId ?? 'synapse-operative',
        client: 'synapse',
        budgetCapUsd: Number(input.budgetCapUsd ?? 2),
    });
    nexusEventBus.emit('dashboard.action', {
        action: 'workforce.agent.materialized',
        status: 'ok',
        target: worker.id,
    });
    return worker;
}
export async function runDashboardAgentControl(ctx, url, input) {
    const operativeId = input.operativeId;
    const goal = input.goal.trim();
    const wf = getWorkforce(ctx.repoRoot ?? process.cwd());
    const worker = wf.getWorker(operativeId) ?? materializeWorkforceAgent(ctx, {
        operativeId,
        specialistId: input.specialistId ?? null,
        budgetCapUsd: input.budgetCapUsd ?? 2,
    });
    const job = wf.enqueueJob({
        title: input.title ?? (goal.slice(0, 80) || 'Agent command'),
        tier: 'tactical',
        client: 'dashboard-agent',
        priority: 8,
        budgetCapUsd: input.budgetCapUsd ?? 2,
        payload: {
            goal,
            operativeId,
            specialistId: input.specialistId ?? worker.role,
            source: 'dashboard-agent-control',
            expectedBehavior: 'A real Workforce job is owned by this operative, routed through dispatch when available, then moved to done or failed with proof.',
            actualStatus: 'Job enqueued and claimed; waiting for dispatch adapter.',
            lifecycle: ['worker-materialized', 'job-enqueued', 'claim-acquired'],
            mode: input.preferredRuntime ? `preferred:${input.preferredRuntime}` : 'dispatch',
            assignedAt: Date.now(),
        },
    });
    const claim = wf.claimJob(job.id, worker.id, 15 * 60_000);
    if (claim)
        wf.advanceToWip(job.id, worker.id);
    const { repoRoot, repoName } = getRepoIdentity(ctx, url);
    const orchestrator = ctx.getOrchestrator();
    const memory = ctx.getMemory();
    const memories = await memory?.recall?.(goal, 6).then((items) => (items.map((content, index) => ({ id: `recall-${index + 1}`, content, tier: 'recall', priority: 0.6 })))).catch(() => []) ?? [];
    const files = await orchestrator?.listTopologyFiles?.(20).catch(() => []) ?? [];
    const graphLines = files.slice(0, 12).map((file) => path.relative(repoRoot, file) || file);
    const crGraphContext = graphLines.length
        ? `Repo graph shortlist for this command:\n${graphLines.map((file) => `- ${file}`).join('\n')}`
        : null;
    try {
        const dispatch = await pushDispatch({
            goal,
            specialistId: input.specialistId ?? worker.role ?? 'engineering.rapid-prototyper',
            operativeId,
            budgetCapUsd: Number(input.budgetCapUsd ?? worker.budgetCapUsd ?? 2),
            workingDir: repoRoot,
            repoName,
            preferredRuntime: input.preferredRuntime ?? undefined,
            memories,
            files,
            crGraphContext,
            storeMemory: async (content, priority, tags) => { memory?.store?.(content, priority, tags); },
        });
        wf.patchJobPayload(job.id, {
            runId: dispatch.runId,
            invoker: dispatch.invoker,
            mode: 'dispatch',
            actualStatus: 'Dispatch accepted; waiting for runtime completion.',
            lifecycle: ['worker-materialized', 'job-enqueued', 'claim-acquired', 'dispatch-accepted'],
        });
        const cleanup = [
            nexusEventBus.on('dispatch.complete', (payload) => {
                if (payload.runId !== dispatch.runId)
                    return;
                wf.recordTokens(job.id, worker.id, Number(payload.tokensUsed ?? 0));
                const ok = payload.success !== false;
                wf.patchJobPayload(job.id, {
                    actualStatus: ok ? 'Completed through dispatch runtime.' : 'Dispatch runtime reported failure.',
                    completedRunId: dispatch.runId,
                    tokensUsed: Number(payload.tokensUsed ?? 0),
                    filesChanged: Array.isArray(payload.filesChanged) ? payload.filesChanged : [],
                    lifecycle: ok
                        ? ['worker-materialized', 'job-enqueued', 'claim-acquired', 'dispatch-accepted', 'dispatch-complete']
                        : ['worker-materialized', 'job-enqueued', 'claim-acquired', 'dispatch-accepted', 'dispatch-failed'],
                });
                if (!ok)
                    wf.failJob(job.id, worker.id);
                else
                    wf.completeJob(job.id, worker.id);
                cleanup.forEach((unsubscribe) => unsubscribe());
            }),
            nexusEventBus.on('dispatch.failed', (payload) => {
                if (payload.runId !== dispatch.runId)
                    return;
                wf.patchJobPayload(job.id, {
                    actualStatus: 'Dispatch failed before completion.',
                    runId: dispatch.runId,
                    failureReason: payload.error ?? 'dispatch.failed',
                    lifecycle: ['worker-materialized', 'job-enqueued', 'claim-acquired', 'dispatch-accepted', 'dispatch-failed'],
                });
                wf.failJob(job.id, worker.id);
                cleanup.forEach((unsubscribe) => unsubscribe());
            }),
        ];
        return { ok: true, mode: 'dispatch', worker, job: wf.getJob(job.id) ?? job, runId: dispatch.runId, invoker: dispatch.invoker };
    }
    catch (dispatchError) {
        if (!orchestrator) {
            wf.patchJobPayload(job.id, {
                actualStatus: 'Dispatch failed and no local orchestrator fallback was available.',
                failureReason: dispatchError?.message ?? String(dispatchError),
                mode: 'dispatch-unavailable',
                lifecycle: ['worker-materialized', 'job-enqueued', 'claim-acquired', 'dispatch-failed'],
            });
            wf.failJob(job.id, worker.id);
            throw dispatchError;
        }
        const run = await orchestrator.orchestrate(goal, {
            source: 'dashboard-agent-control',
            workers: 1,
            files,
            specialistId: input.specialistId ?? worker.role,
            operativeId,
        });
        wf.patchJobPayload(job.id, {
            runId: run.runId,
            mode: 'orchestrator-fallback',
            invoker: 'nexus-orchestrator',
            fallbackReason: dispatchError?.message ?? String(dispatchError),
            actualStatus: run.state === 'failed'
                ? 'Local orchestrator fallback failed.'
                : 'Completed through local orchestrator fallback.',
            lifecycle: run.state === 'failed'
                ? ['worker-materialized', 'job-enqueued', 'claim-acquired', 'dispatch-failed', 'orchestrator-fallback-failed']
                : ['worker-materialized', 'job-enqueued', 'claim-acquired', 'dispatch-failed', 'orchestrator-fallback-complete'],
        });
        if (run.state === 'failed')
            wf.failJob(job.id, worker.id);
        else
            wf.completeJob(job.id, worker.id);
        return {
            ok: true,
            mode: 'orchestrator-fallback',
            worker,
            job: wf.getJob(job.id) ?? job,
            runId: run.runId,
            state: run.state,
            message: dispatchError?.message ? `Push dispatch unavailable; ran through Nexus orchestrator instead. ${dispatchError.message}` : 'Ran through Nexus orchestrator.',
        };
    }
}
export const handleWorkforceRoutes = async (ctx, req, res, url) => {
    if (!url.pathname.startsWith('/api/workforce/'))
        return false;
    const wf = getWorkforce(ctx.repoRoot ?? process.cwd());
    const companyId = req.headers['x-nexus-company']?.trim() || url.searchParams.get('company') || undefined;
    if (url.pathname === '/api/workforce/kanban') {
        const client = url.searchParams.get('client') ?? undefined;
        const board = wf.getKanbanBoard(client);
        ctx.respondJson(res, board);
        return true;
    }
    if (url.pathname === '/api/workforce/companies') {
        const companies = wf.listCompanies();
        ctx.respondJson(res, { companies });
        return true;
    }
    if (url.pathname === '/api/workforce/workers') {
        const statusRaw = url.searchParams.get('status');
        const statusParam = statusRaw && VALID_WORKER_STATUSES.has(statusRaw)
            ? statusRaw
            : undefined;
        const workers = wf.listWorkers(statusParam, companyId);
        ctx.respondJson(res, { workers });
        return true;
    }
    if (url.pathname === '/api/workforce/jobs') {
        const statusRaw = url.searchParams.get('status');
        const statusParam = statusRaw && VALID_JOB_STATUSES.has(statusRaw)
            ? statusRaw
            : undefined;
        const limit = safeLimit(url.searchParams.get('limit'), 100);
        const jobs = wf.listJobs(statusParam, limit, companyId);
        ctx.respondJson(res, { jobs });
        return true;
    }
    const agentControlMatch = req.method === 'POST'
        ? /^\/api\/workforce\/agents\/([^/]+)\/(?:chat|dispatch)$/.exec(url.pathname)
        : null;
    if (agentControlMatch) {
        const operativeId = decodeURIComponent(agentControlMatch[1]);
        const body = await ctx.readJsonBody(req);
        const goal = trimString(body.goal) ?? trimString(body.message) ?? trimString(body.prompt);
        if (!goal) {
            ctx.respondJson(res, { error: 'goal-required' }, 400);
            return true;
        }
        try {
            const result = await runDashboardAgentControl(ctx, url, {
                operativeId,
                goal,
                specialistId: trimString(body.specialistId),
                budgetCapUsd: typeof body.budgetCapUsd === 'number' ? body.budgetCapUsd : undefined,
                preferredRuntime: trimString(body.preferredRuntime),
                title: trimString(body.title),
            });
            ctx.respondJson(res, result, 202);
        }
        catch (error) {
            ctx.respondJson(res, {
                ok: false,
                error: error?.message ?? String(error),
                code: 'agent-control-failed',
                hint: 'No headless runtime or local orchestrator could accept this agent command.',
            }, 503);
        }
        return true;
    }
    if (url.pathname === '/api/workforce/runs') {
        const limit = safeLimit(url.searchParams.get('limit'), 20);
        try {
            const runs = listRecentRuns(limit);
            ctx.respondJson(res, { runs });
        }
        catch {
            ctx.respondJson(res, { runs: [], error: 'store-unavailable' });
        }
        return true;
    }
    const spansMatch = url.pathname.match(/^\/api\/workforce\/runs\/([^/]+)\/spans$/);
    if (spansMatch) {
        const runId = decodeURIComponent(spansMatch[1]);
        try {
            const spans = getSpans(runId);
            ctx.respondJson(res, { runId, spans });
        }
        catch {
            ctx.respondJson(res, { runId, spans: [], error: 'store-unavailable' });
        }
        return true;
    }
    return false;
};
