import { z } from 'zod';
import { authMiddleware } from '../auth/middleware.js';
import { supabase } from '../utils/supabase.js';
/**
 * Resolve the tenant for an authenticated request.
 *
 * In device mode the dashboard owns one tenant (the device's), so we look it
 * up by `owner_id`. In control-plane mode a tenant ID would normally come from
 * the URL or a header — autonomy is currently device-mode-first, so we keep
 * the lookup pattern simple here.
 */
async function resolveTenantId(req) {
    if (req.tenant?.id)
        return req.tenant.id;
    const userId = req.user?.id;
    if (!userId)
        return null;
    const { data } = await supabase
        .from('tenants')
        .select('id')
        .eq('owner_id', userId)
        .order('created_at', { ascending: false })
        .limit(1);
    const row = data?.[0];
    return row?.id ?? null;
}
import { createGoal, listActiveGoals, incrementGoalProgress, archiveGoal, summarizeAgenda, getGoal, } from './agenda.js';
import { list as listJournal, todayRollup, weekRollup, } from './journal.js';
import { upsertPolicy, listPolicies, } from './policies.js';
import { upsertFact, listFacts, deleteFact, } from './identity.js';
import { listPending as listPendingEscalations, resolve as resolveEscalation, } from './escalation.js';
import { listAllowed, addAllowed, removeAllowed, } from './allowlist.js';
const createGoalBodySchema = z.object({
    title: z.string().min(1).max(200),
    description: z.string().optional(),
    category: z.enum(['sales', 'leads', 'finance', 'compliance', 'marketing', 'operations', 'customer-success', 'general']).optional(),
    target_value: z.number().optional(),
    unit: z.string().optional(),
    deadline: z.string().optional(),
    priority: z.number().int().min(1).max(5).optional(),
    /** When true, the autonomy loop picks the goal up on its next tick and
     * starts working on it without waiting for explicit chat kickoff.
     * (v3.0.5) Wired in from the dashboard's "Add Goal" modal so the
     * "aggressive goal pursuit" task #42 can route off this signal. */
    auto_execute: z.boolean().optional(),
});
const upsertPolicyBodySchema = z.object({
    policy_key: z.string().min(1),
    description: z.string().optional(),
    mode: z.enum(['auto', 'ask', 'never']).optional(),
    threshold_amount: z.number().optional(),
    threshold_currency: z.string().optional(),
    enabled: z.boolean().optional(),
});
const upsertFactBodySchema = z.object({
    fact_key: z.string().min(1),
    fact_value: z.string().min(1),
    category: z.enum(['business', 'owner', 'team', 'product', 'customer', 'preference']).optional(),
});
const resolveBodySchema = z.object({
    resolution: z.enum(['approved', 'denied', 'modified']),
    note: z.string().optional(),
});
export async function registerAutonomyRoutes(app) {
    // ─── Summary (the dashboard hero card) ─────────────────────────────
    app.get('/api/autonomy/summary', { preHandler: [authMiddleware] }, async (req, reply) => {
        const tenantId = await resolveTenantId(req);
        if (!tenantId)
            return reply.status(401).send({ error: 'No tenant for this user' });
        const [agenda, today, week, escalations] = await Promise.all([
            summarizeAgenda(tenantId),
            todayRollup(tenantId),
            weekRollup(tenantId),
            listPendingEscalations(tenantId),
        ]);
        reply.send({
            agenda,
            today,
            week,
            escalations: escalations.slice(0, 10),
            escalations_total: escalations.length,
        });
    });
    // ─── Agenda ────────────────────────────────────────────────────────
    app.get('/api/autonomy/agenda', { preHandler: [authMiddleware] }, async (req, reply) => {
        const tenantId = await resolveTenantId(req);
        if (!tenantId)
            return reply.status(401).send({ error: 'No tenant for this user' });
        reply.send({ goals: await listActiveGoals(tenantId) });
    });
    app.post('/api/autonomy/agenda', { preHandler: [authMiddleware] }, async (req, reply) => {
        const tenantId = await resolveTenantId(req);
        if (!tenantId)
            return reply.status(401).send({ error: 'No tenant for this user' });
        const parsed = createGoalBodySchema.safeParse(req.body);
        if (!parsed.success)
            return reply.status(400).send({ error: parsed.error.flatten() });
        const goal = await createGoal(tenantId, parsed.data);
        reply.send({ goal });
    });
    app.post('/api/autonomy/agenda/:id/progress', { preHandler: [authMiddleware] }, async (req, reply) => {
        const tenantId = await resolveTenantId(req);
        if (!tenantId)
            return reply.status(401).send({ error: 'No tenant for this user' });
        const { id } = req.params;
        const { delta } = (req.body ?? {});
        if (typeof delta !== 'number')
            return reply.status(400).send({ error: 'delta must be a number' });
        const goal = await incrementGoalProgress(tenantId, id, delta);
        if (!goal)
            return reply.status(404).send({ error: 'Goal not found' });
        reply.send({ goal });
    });
    app.post('/api/autonomy/agenda/:id/archive', { preHandler: [authMiddleware] }, async (req, reply) => {
        const tenantId = await resolveTenantId(req);
        if (!tenantId)
            return reply.status(401).send({ error: 'No tenant for this user' });
        const { id } = req.params;
        await archiveGoal(tenantId, id);
        reply.send({ ok: true });
    });
    // On-demand goal pursuit — owner clicks "اعمل عليه الآن". Runs the agent on
    // this specific goal NOW (instead of waiting for the 30-min cron), logs each
    // step against the goal, records an honest outcome, and surfaces the result
    // in the owner's chat so they can watch it work.
    app.post('/api/autonomy/agenda/:id/pursue', { preHandler: [authMiddleware] }, async (req, reply) => {
        const tenantId = await resolveTenantId(req);
        if (!tenantId)
            return reply.status(401).send({ error: 'No tenant for this user' });
        const { id } = req.params;
        const { buildGoalKickoff, recordPursuitSteps, recordPursuitOutcome } = await import('./goal-pursuit.js');
        const built = await buildGoalKickoff(tenantId, id);
        if (!built)
            return reply.status(404).send({ error: 'Goal not found' });
        const { processMessage } = await import('../agent/brain.js');
        const result = await processMessage(tenantId, `system-goal-${id}`, built.kickoff, { recentMessages: [], additionalInstructions: 'Owner triggered this goal pursuit on demand from the dashboard. Take a real, concrete step using tools.', recursionDepth: 1 }, 
        // Owner clicked this and is watching → use the top model (planning).
        { budgetAware: true, enableTools: true, maxToolLoops: 4, purpose: 'planning' });
        const tools = result.toolsExecuted ?? [];
        const succeeded = tools.filter((t) => t.success).length;
        await recordPursuitSteps(tenantId, id, tools);
        await recordPursuitOutcome(tenantId, id, succeeded > 0 ? 'success' : 'failure', succeeded > 0 ? undefined : 'no tools executed on demand');
        // Surface in the owner's chat thread so the work is visible there too.
        try {
            const { resolveOwnerPrimaryCustomerId } = await import('../web-chat/owner-thread.js');
            const customerId = await resolveOwnerPrimaryCustomerId(tenantId);
            if (customerId) {
                const { randomUUID } = await import('node:crypto');
                await supabase.from('messages').insert({
                    id: randomUUID(), tenant_id: tenantId, customer_id: customerId,
                    session_id: `goal:${id}`, direction: 'outbound', type: 'text',
                    content: `🎯 **${built.goal.title}** — ${succeeded} إجراء فعلي\n\n${result.response}`,
                    delivery_status: 'sent', created_at: new Date().toISOString(),
                });
            }
        }
        catch { /* best-effort */ }
        reply.send({ ok: true, toolsSucceeded: succeeded, response: result.response });
    });
    // Per-goal activity timeline — the actual steps the agent took on this goal
    // (journal entries linked via related_goal_id) plus its pursuit status.
    // This is what makes a goal show its PROCESS instead of just a bar.
    app.get('/api/autonomy/agenda/:id/activity', { preHandler: [authMiddleware] }, async (req, reply) => {
        const tenantId = await resolveTenantId(req);
        if (!tenantId)
            return reply.status(401).send({ error: 'No tenant for this user' });
        const { id } = req.params;
        const goal = await getGoal(tenantId, id);
        if (!goal)
            return reply.status(404).send({ error: 'Goal not found' });
        const { data } = await supabase
            .from('journal_entries')
            .select('kind, actor, action, target, outcome, reason, created_at')
            .eq('tenant_id', tenantId)
            .eq('related_goal_id', id)
            .order('created_at', { ascending: false })
            .limit(50);
        const meta = (goal.metadata ?? {});
        reply.send({
            goal: {
                id: goal.id, title: goal.title, description: goal.description,
                status: goal.status, current_value: goal.current_value, target_value: goal.target_value,
                unit: goal.unit, priority: goal.priority,
            },
            pursuit: {
                last_pursued_at: meta.last_pursued_at ?? null,
                last_outcome: meta.last_pursuit_outcome ?? null,
                consecutive_fails: Number(meta.consecutive_pursuit_fails ?? 0),
                failure_detail: meta.last_pursuit_failure_detail ?? null,
                auto_execute: meta.auto_execute !== false,
                paused: meta.paused === true,
            },
            activity: data ?? [],
        });
    });
    // ─── Journal ───────────────────────────────────────────────────────
    app.get('/api/autonomy/journal', { preHandler: [authMiddleware] }, async (req, reply) => {
        const tenantId = await resolveTenantId(req);
        if (!tenantId)
            return reply.status(401).send({ error: 'No tenant for this user' });
        const { kind, since, limit } = req.query;
        const entries = await listJournal({
            tenantId,
            kind,
            since,
            limit: limit ? Number.parseInt(limit, 10) : 50,
        });
        reply.send({ entries });
    });
    app.get('/api/autonomy/journal/today', { preHandler: [authMiddleware] }, async (req, reply) => {
        const tenantId = await resolveTenantId(req);
        if (!tenantId)
            return reply.status(401).send({ error: 'No tenant for this user' });
        reply.send({ rollup: await todayRollup(tenantId) });
    });
    app.get('/api/autonomy/journal/week', { preHandler: [authMiddleware] }, async (req, reply) => {
        const tenantId = await resolveTenantId(req);
        if (!tenantId)
            return reply.status(401).send({ error: 'No tenant for this user' });
        reply.send({ rollup: await weekRollup(tenantId) });
    });
    // ─── Policies ──────────────────────────────────────────────────────
    app.get('/api/autonomy/policies', { preHandler: [authMiddleware] }, async (req, reply) => {
        const tenantId = await resolveTenantId(req);
        if (!tenantId)
            return reply.status(401).send({ error: 'No tenant for this user' });
        reply.send({ policies: await listPolicies(tenantId) });
    });
    app.post('/api/autonomy/policies', { preHandler: [authMiddleware] }, async (req, reply) => {
        const tenantId = await resolveTenantId(req);
        if (!tenantId)
            return reply.status(401).send({ error: 'No tenant for this user' });
        const parsed = upsertPolicyBodySchema.safeParse(req.body);
        if (!parsed.success)
            return reply.status(400).send({ error: parsed.error.flatten() });
        const policy = await upsertPolicy(tenantId, {
            ...parsed.data,
            mode: parsed.data.mode,
        });
        reply.send({ policy });
    });
    // ─── Identity ──────────────────────────────────────────────────────
    app.get('/api/autonomy/identity', { preHandler: [authMiddleware] }, async (req, reply) => {
        const tenantId = await resolveTenantId(req);
        if (!tenantId)
            return reply.status(401).send({ error: 'No tenant for this user' });
        const { category } = req.query;
        const facts = await listFacts(tenantId, category);
        reply.send({ facts });
    });
    app.post('/api/autonomy/identity', { preHandler: [authMiddleware] }, async (req, reply) => {
        const tenantId = await resolveTenantId(req);
        if (!tenantId)
            return reply.status(401).send({ error: 'No tenant for this user' });
        const parsed = upsertFactBodySchema.safeParse(req.body);
        if (!parsed.success)
            return reply.status(400).send({ error: parsed.error.flatten() });
        const fact = await upsertFact(tenantId, parsed.data.fact_key, parsed.data.fact_value, parsed.data.category, 'owner');
        reply.send({ fact });
    });
    app.delete('/api/autonomy/identity/:key', { preHandler: [authMiddleware] }, async (req, reply) => {
        const tenantId = await resolveTenantId(req);
        if (!tenantId)
            return reply.status(401).send({ error: 'No tenant for this user' });
        const { key } = req.params;
        await deleteFact(tenantId, key);
        reply.send({ ok: true });
    });
    // ─── Escalations ───────────────────────────────────────────────────
    app.get('/api/autonomy/escalations', { preHandler: [authMiddleware] }, async (req, reply) => {
        const tenantId = await resolveTenantId(req);
        if (!tenantId)
            return reply.status(401).send({ error: 'No tenant for this user' });
        reply.send({ escalations: await listPendingEscalations(tenantId) });
    });
    app.post('/api/autonomy/escalations/:id/resolve', { preHandler: [authMiddleware] }, async (req, reply) => {
        const tenantId = await resolveTenantId(req);
        if (!tenantId)
            return reply.status(401).send({ error: 'No tenant for this user' });
        const { id } = req.params;
        const parsed = resolveBodySchema.safeParse(req.body);
        if (!parsed.success)
            return reply.status(400).send({ error: parsed.error.flatten() });
        const escalation = await resolveEscalation(tenantId, id, parsed.data.resolution, parsed.data.note);
        if (!escalation)
            return reply.status(404).send({ error: 'Escalation not found' });
        reply.send({ escalation });
    });
    // ─── Allowlist ─────────────────────────────────────────────────────
    app.get('/api/autonomy/allowlist', { preHandler: [authMiddleware] }, async (req, reply) => {
        const tenantId = await resolveTenantId(req);
        if (!tenantId)
            return reply.status(401).send({ error: 'No tenant for this user' });
        const { channel } = req.query;
        const contacts = await listAllowed(tenantId, channel);
        reply.send({ contacts });
    });
    app.post('/api/autonomy/allowlist', { preHandler: [authMiddleware] }, async (req, reply) => {
        const tenantId = await resolveTenantId(req);
        if (!tenantId)
            return reply.status(401).send({ error: 'No tenant for this user' });
        const body = (req.body ?? {});
        if (!body.channel || !body.identifier) {
            return reply.status(400).send({ error: 'channel and identifier required' });
        }
        const contact = await addAllowed(tenantId, {
            channel: body.channel,
            identifier: body.identifier,
            display_name: body.display_name,
            role: body.role ?? 'contact',
            added_by: 'owner',
            reason: body.reason,
        });
        reply.send({ contact });
    });
    app.delete('/api/autonomy/allowlist/:channel/:identifier', { preHandler: [authMiddleware] }, async (req, reply) => {
        const tenantId = await resolveTenantId(req);
        if (!tenantId)
            return reply.status(401).send({ error: 'No tenant for this user' });
        const { channel, identifier } = req.params;
        await removeAllowed(tenantId, channel, decodeURIComponent(identifier));
        reply.send({ ok: true });
    });
}
//# sourceMappingURL=routes.js.map