import { authMiddleware } from '../auth/middleware.js';
import { supabase } from '../utils/supabase.js';
import { createChildLogger } from '../utils/logger.js';
import { spawnSubagent, listSubagents, getSubagent, cancelSubagent, getActiveCount, } from './runner.js';
import { subagentEvents } from './events.js';
const log = createChildLogger('subagents:routes');
async function resolveTenantId(req) {
    const userId = req.user?.id;
    if (!userId)
        return null;
    const { data } = await supabase.from('tenants').select('id').eq('owner_id', userId).maybeSingle();
    return data?.id ?? null;
}
// (2026-05-31) Bridge real autonomy work into the Office. The autonomy crons
// (skill-discovery, research, reflection, goal-pursuit, proactive-actions) do
// genuine departmental work but ran as journal entries, not subagent rows — so
// the Office showed empty departments ("agents just chilling"). This maps recent
// journal activity into run-shaped objects whose `task` text carries the right
// department keyword (deptFor in office.js routes by keyword), so each department
// shows what it actually did. Honest: status reflects real recency, not fakery.
// Kind → department-routed Arabic task text (keyword must match office.js DEPARTMENTS).
const AUTONOMY_JOURNAL_MAP = {
    skill_discovery: (r) => `تطوير وتعلّم: ${r.target || 'اكتشاف وتركيب مهارات جديدة'}`,
    skill_installed: (r) => `تطوير: تركيب مهارة ${r.target || ''}`.trim(),
    reflection: () => 'تحليل الأداء والتعلّم من التجارب',
    research: (r) => `بحث السوق: ${r.target || r.action || ''}`.trim(),
    research_cycle: (r) => `بحث وتحليل: ${r.target || r.action || ''}`.trim(),
    insight: (r) => `بحث وتحليل: ${r.action || ''}`.trim(),
    goal_progress: (r) => `تشغيل هدف العمل: ${r.target || r.action || ''}`.trim(),
    goal_created: (r) => `هدف جديد للعمليات: ${r.target || r.action || ''}`.trim(),
    proactive: (r) => `مبيعات ومتابعة العملاء: ${r.action || ''}`.trim(),
    proactive_action: (r) => `مبيعات: متابعة العملاء — ${r.action || ''}`.trim(),
    scorecard: () => 'الميزانية والأداء المالي',
};
async function autonomyActivityRuns(tid) {
    try {
        const sinceMs = Date.now() - 6 * 3600_000; // last 6h
        const { data } = await supabase
            .from('journal_entries')
            .select('id, kind, action, target, created_at')
            .eq('tenant_id', tid)
            .order('created_at', { ascending: false })
            .limit(60);
        const rows = (data || []).filter(r => AUTONOMY_JOURNAL_MAP[r.kind] && Date.parse(r.created_at) >= sinceMs);
        const seenKinds = new Set();
        const runs = [];
        for (const r of rows) {
            // One visible worker per kind per refresh keeps the floor readable.
            if (seenKinds.has(r.kind))
                continue;
            seenKinds.add(r.kind);
            const ageMs = Date.now() - Date.parse(r.created_at);
            runs.push({
                id: `auto-${r.id}`,
                task: AUTONOMY_JOURNAL_MAP[r.kind](r),
                status: ageMs < 5 * 60_000 ? 'running' : 'completed',
                last_step_tool: r.kind,
                started_at: r.created_at,
                source: 'autonomy',
            });
        }
        return runs;
    }
    catch (err) {
        log.debug({ err: err.message }, 'autonomy activity merge skipped');
        return [];
    }
}
export async function registerSubagentRoutes(app) {
    // Live AI-Council meeting state for the Office meeting-room visual.
    app.get('/api/council/active', { preHandler: [authMiddleware] }, async (req, reply) => {
        const tid = await resolveTenantId(req);
        if (!tid)
            return reply.status(401).send({ error: 'no tenant' });
        try {
            const { getActiveMeeting } = await import('../agent/council.js');
            reply.send({ success: true, data: getActiveMeeting(tid) });
        }
        catch (err) {
            reply.send({ success: true, data: null });
        }
    });
    app.get('/api/subagents', { preHandler: [authMiddleware] }, async (req, reply) => {
        const tid = await resolveTenantId(req);
        if (!tid)
            return reply.status(401).send({ error: 'no tenant' });
        const realRuns = await listSubagents(tid, 30);
        // Merge real autonomy work so the Office departments reflect what the
        // agent actually does on its own (not just user-spawned subagents).
        const autoRuns = await autonomyActivityRuns(tid);
        const runs = [...realRuns, ...autoRuns];
        reply.send({
            success: true,
            data: {
                runs,
                active_count: runs.filter((r) => r.status === 'running' || r.status === 'queued').length,
                in_process_active: getActiveCount(),
            },
        });
    });
    app.get('/api/subagents/:id', { preHandler: [authMiddleware] }, async (req, reply) => {
        const tid = await resolveTenantId(req);
        if (!tid)
            return reply.status(401).send({ error: 'no tenant' });
        const run = await getSubagent(tid, req.params.id);
        if (!run)
            return reply.status(404).send({ error: 'not found' });
        reply.send({ success: true, data: run });
    });
    app.get('/api/subagents/:id/events', { preHandler: [authMiddleware] }, async (req, reply) => {
        const tid = await resolveTenantId(req);
        if (!tid)
            return reply.status(401).send({ error: 'no tenant' });
        const run = await getSubagent(tid, req.params.id);
        if (!run)
            return reply.status(404).send({ error: 'not found' });
        reply.send({ success: true, data: subagentEvents.history(req.params.id) });
    });
    // SSE stream — the dashboard subscribes to this per active subagent to
    // see the progress timeline tick in real time.
    app.get('/api/subagents/:id/stream', { preHandler: [authMiddleware] }, async (req, reply) => {
        const tid = await resolveTenantId(req);
        if (!tid)
            return reply.status(401).send({ error: 'no tenant' });
        const run = await getSubagent(tid, req.params.id);
        if (!run)
            return reply.status(404).send({ error: 'not found' });
        reply.raw.writeHead(200, {
            'Content-Type': 'text/event-stream',
            'Cache-Control': 'no-cache, no-transform',
            Connection: 'keep-alive',
            'X-Accel-Buffering': 'no',
        });
        let gone = false;
        const send = (ev) => {
            if (gone)
                return;
            try {
                reply.raw.write(`event: ${ev.type}\n`);
                reply.raw.write(`data: ${JSON.stringify(ev)}\n\n`);
            }
            catch {
                gone = true;
            }
        };
        // Replay history first so a late-connecting client sees what already happened
        for (const ev of subagentEvents.history(req.params.id))
            send(ev);
        const off = subagentEvents.on(req.params.id, send);
        const keepAlive = setInterval(() => {
            if (gone)
                return;
            try {
                reply.raw.write(': keep-alive\n\n');
            }
            catch {
                gone = true;
            }
        }, 15_000);
        reply.raw.on('close', () => { gone = true; off(); clearInterval(keepAlive); });
    });
    // Spawn from explicit request — bypasses the chat-NL parser
    app.post('/api/subagents', { preHandler: [authMiddleware] }, async (req, reply) => {
        const tid = await resolveTenantId(req);
        if (!tid)
            return reply.status(401).send({ error: 'no tenant' });
        const task = (req.body?.task ?? '').trim();
        if (task.length < 10)
            return reply.status(400).send({ error: 'task too short — be specific about what to research + what format to return' });
        try {
            const run = await spawnSubagent({
                tenantId: tid,
                task,
                timeoutSeconds: req.body?.timeout_seconds ?? 600,
                metadata: req.body?.label ? { label: req.body.label } : undefined,
            });
            reply.send({ success: true, data: run });
        }
        catch (err) {
            reply.status(500).send({ success: false, error: err.message });
        }
    });
    // Cancel a running subagent
    app.post('/api/subagents/:id/cancel', { preHandler: [authMiddleware] }, async (req, reply) => {
        const tid = await resolveTenantId(req);
        if (!tid)
            return reply.status(401).send({ error: 'no tenant' });
        const cancelled = await cancelSubagent(tid, req.params.id);
        if (!cancelled)
            return reply.status(404).send({ error: 'not found or already finished' });
        subagentEvents.emit(req.params.id, 'cancelled', 'تم الإلغاء بواسطة المالك');
        reply.send({ success: true, data: { id: req.params.id, status: 'cancelled' } });
    });
    // NL parser — accepts natural language and decides whether the user
    // wants to spawn a subagent. Handles slang + typos.
    //   "spawn an agent to dig into our top 10 competitors" ✅
    //   "وكيل ابحث لي عن منافسين في مدينة جدة" ✅
    //   "agent تابع البريد الإلكتروني خلال الساعة الجاي" ✅ (but suggest cron)
    //   "ابعت ايميل لاحمد" ❌ (not a subagent task — direct action)
    app.post('/api/subagents/parse', { preHandler: [authMiddleware] }, async (req, reply) => {
        const tid = await resolveTenantId(req);
        if (!tid)
            return reply.status(401).send({ error: 'no tenant' });
        const text = (req.body?.text ?? '').trim();
        const parsed = parseSubagentIntent(text);
        if (!parsed.isSubagentRequest) {
            return reply.send({ success: true, data: { detected: false, reason: parsed.reason } });
        }
        if (req.body?.auto_spawn === false) {
            return reply.send({ success: true, data: { detected: true, task: parsed.task, confidence: parsed.confidence } });
        }
        try {
            const run = await spawnSubagent({ tenantId: tid, task: parsed.task, timeoutSeconds: 600 });
            reply.send({ success: true, data: { detected: true, spawned: true, run, confidence: parsed.confidence } });
        }
        catch (err) {
            reply.status(500).send({ success: false, error: err.message });
        }
    });
    log.info('Subagent routes registered ✅');
}
/**
 * Heuristic NL parser. Detects:
 *   - explicit verbs: "spawn / launch / run / fire / start" + "agent / subagent / وكيل / مساعد فرعي"
 *   - delegated research intent: "research / dig into / investigate / audit / analyze ابحث / حلّل / دقّق / راجع"
 *   - long-running cues: "take your time / over the next / خلال الساعة / في الخلفية"
 * Returns the cleaned task (trimmed of the trigger verb).
 */
function parseSubagentIntent(text) {
    if (!text || text.length < 8)
        return { isSubagentRequest: false, task: '', confidence: 0, reason: 'too short' };
    const lower = text.toLowerCase();
    // Explicit spawn triggers — typo-tolerant
    const explicit = /\b(spawn|spwan|launch|lanch|fire\s+up|run|kick\s+off|delegate)\s+(?:a\s+|an\s+)?(sub-?agent|background\s+agent|research\s+agent|deep\s+research|task)\b/i.test(text)
        || /\b(وكيل|مساعد\s*فرعي|وكيل\s+بحث|وكلاء)\s+(?:يبحث|يدقّق|يحلّل|يراجع|يجمع|يدوّر|ابحث|دقّق|راجع|حلّل)/u.test(text)
        || /\b(?:طلّع|ابعت|أرسل)\s+(?:لي\s+)?(?:وكيل|مساعد\s+فرعي)/u.test(text);
    // Research / audit intent — strong signal for subagent. Arabic verbs in
    // BOTH imperative (ابحث) and present (يبحث / يدقّق) forms; cover dialect
    // variants and common typos. English keeps the original list + a few
    // common misspellings observed in user transcripts.
    const researchIntent = /\b(research|reasearch|investigate|audit|analy[zs]e|dig\s+into|deep\s+dive|find\s+out|look\s+into|compile|summari[zs]e|benchmark|figure\s+out|scout)\b/i.test(text)
        || /(?:ابحث|يبحث|تبحث|بحث\s+عميق|دقّق|دقق|يدقّق|راجع|يراجع|اطّلع|اطلع|اكتشف|قارن|قارني|قارنّا|حلّل|حلل|يحلّل|يحلل|اعمل\s+تقرير|تقرير\s+عن|سوّي\s+بحث|دوّر|يدوّر)/u.test(text);
    // Long-running cues — bumps confidence
    const longCue = /\b(take\s+your\s+time|over\s+the\s+next|background|while\s+i|behind\s+the\s+scenes)\b/i.test(text)
        || /\b(خلال\s+(?:الساعة|اليوم|الأسبوع)|في\s+الخلفية|على\s+مهلك|أمامك\s+وقت)\b/u.test(text);
    // Strong NEGATIVE signals — direct action, not delegation
    const directAction = /\b(send|email|message|إرسال|أرسل|ابعت|ابعتلي|ادفع|سدد|احجز|اشتري)\b/i.test(lower)
        && !researchIntent;
    if (directAction && !explicit) {
        return { isSubagentRequest: false, task: '', confidence: 0.85, reason: 'direct-action verb without research intent' };
    }
    if (!explicit && !researchIntent) {
        return { isSubagentRequest: false, task: '', confidence: 0.9, reason: 'no spawn or research verb' };
    }
    // Strip the trigger verbs to leave the actual task
    let task = text
        .replace(/^\s*(?:please|من\s*فضلك|لطفاً)\s*/iu, '')
        .replace(/\b(spawn|launch|run|fire\s+up|kick\s+off|delegate)\s+(?:a\s+|an\s+)?(sub-?agent|background\s+agent|research\s+agent|deep\s+research|task)\s+(?:to\s+)?/iu, '')
        .replace(/\b(?:طلّع|ابعت|أرسل)\s+(?:لي\s+)?(?:وكيل|مساعد\s+فرعي)\s+(?:يبحث|يدقّق|يحلّل|يراجع|يجمع|يدوّر)?\s*/u, '')
        .trim();
    if (task.length < 8)
        task = text; // bail to original if we over-trimmed
    const confidence = (explicit ? 0.5 : 0) + (researchIntent ? 0.3 : 0) + (longCue ? 0.2 : 0);
    return { isSubagentRequest: true, task, confidence: Math.min(confidence, 0.95) };
}
//# sourceMappingURL=routes.js.map