/**
 * src/autonomy/initiative.ts
 *
 * The GENERATIVE initiative engine — the leap from "5 hardcoded scanners" to
 * "an employee that thinks FOR the business and acts." Where proactive-actions.ts
 * runs a fixed checklist, this engine:
 *
 *   1. OBSERVE     — snapshot the REAL business state (identity, goals, customers,
 *                    recent journal, pending escalations).
 *   2. HYPOTHESISE — one planning-tier (Opus) call: "you are the owner's right-hand
 *                    employee; given this state, what beneficial things should you
 *                    do RIGHT NOW?" → strict JSON proposals.
 *   3. DECIDE+ACT  — route every proposal through the SAME policy control plane
 *                    (policies.evaluate + HARD_GATED_KEYS):
 *                      • auto  → batched into one agent kickoff (act-then-tell;
 *                                the scheduler runs it, output lands in owner chat).
 *                      • ask   → escalation card with the draft attached.
 *                      • never → skipped + journalled.
 *
 * Safety posture (locked with the owner 2026-05-25, "safe Ultron"):
 *   • Reversible work (internal analysis, warm follow-ups to EXISTING contacts) → auto.
 *   • Cold first-touch to a freshly-discovered lead → ask (PDPL: protect the number).
 *   • Public content publishing → ask by default (owner can flip to auto).
 *   • Money-out / contracts / data-deletion / mass-blast → HARD_GATED (always ask).
 *
 * GROUNDING GATE (the honest catch): on a near-empty account, "always suggest"
 * degrades into generic fluff that HURTS the human feel. So the engine stays
 * SILENT unless there is real signal to act on. Quality over noise.
 */
import { z } from 'zod';
import { createChildLogger } from '../utils/logger.js';
import { callWithFailover } from '../models/failover.js';
import { pickModelOverride } from '../models/router.js';
import { supabase } from '../utils/supabase.js';
import { listActiveGoals } from './agenda.js';
import { listFacts } from './identity.js';
import { list as listJournal, record as journalRecord } from './journal.js';
import { upsertPolicy, evaluate as evaluatePolicy, HARD_GATED_KEYS } from './policies.js';
import { escalate } from './escalation.js';
const log = createChildLogger('autonomy:initiative');
const days = (n) => new Date(Date.now() - n * 24 * 60 * 60 * 1000).toISOString();
const ACTION_POLICY_KEY = {
    internal: 'initiative_internal_action',
    warm_outreach: 'initiative_warm_outreach',
    cold_outreach: 'initiative_cold_outreach',
    content_publish: 'initiative_content_publish',
    money: 'transfer_funds',
    contract: 'sign_contract',
    data_delete: 'delete_data',
};
/** Default modes reflect the owner's locked posture. Owner can flip from dashboard. */
const INITIATIVE_POLICY_DEFAULTS = [
    { key: 'initiative_internal_action', mode: 'auto', description: 'Run internal research/drafting/analysis on its own (no external send)' },
    { key: 'initiative_warm_outreach', mode: 'auto', description: 'Message an existing/known contact on its own, then inform the owner' },
    { key: 'initiative_cold_outreach', mode: 'ask', description: 'First-touch to a newly-discovered lead — queue for owner approval (PDPL)' },
    { key: 'initiative_content_publish', mode: 'ask', description: 'Publish public content (social/site) — owner approves first by default' },
];
const proposalSchema = z.object({
    kind: z.string().min(1).max(60),
    title: z.string().min(1).max(160),
    action_type: z.enum(['internal', 'warm_outreach', 'cold_outreach', 'content_publish', 'money', 'contract', 'data_delete']),
    target: z.string().max(200).optional().default(''),
    rationale: z.string().min(1).max(600),
    draft: z.string().max(1500).optional().default(''),
    risk: z.enum(['low', 'medium', 'high']).optional().default('medium'),
    expected_benefit: z.string().max(300).optional().default(''),
});
const proposalEnvelopeSchema = z.object({
    proposals: z.array(proposalSchema).default([]),
});
/** Idempotently seed initiative policies with the owner-locked defaults. */
export async function seedInitiativePolicies(tenantId) {
    for (const p of INITIATIVE_POLICY_DEFAULTS) {
        try {
            await upsertPolicy(tenantId, { policy_key: p.key, description: p.description, mode: p.mode });
        }
        catch (err) {
            log.debug({ key: p.key, err: err.message }, 'initiative policy seed skipped');
        }
    }
}
async function fetchCustomers(tenantId) {
    try {
        const { data } = await supabase
            .from('customers')
            .select('id, name, status, last_contact, metadata')
            .eq('tenant_id', tenantId)
            .order('last_contact', { ascending: false })
            .limit(25);
        return (data ?? []);
    }
    catch {
        return [];
    }
}
async function countPendingEscalations(tenantId) {
    try {
        const { count } = await supabase
            .from('escalation_queue')
            .select('id', { count: 'exact', head: true })
            .eq('tenant_id', tenantId)
            .eq('status', 'pending');
        return count ?? 0;
    }
    catch {
        return 0;
    }
}
async function buildSnapshot(tenantId) {
    const [facts, goals, customersRaw, journal, pendingEscalations] = await Promise.all([
        listFacts(tenantId).catch(() => []),
        listActiveGoals(tenantId).catch(() => []),
        fetchCustomers(tenantId),
        listJournal({ tenantId, since: days(3), limit: 30 }).catch(() => []),
        countPendingEscalations(tenantId),
    ]);
    const customers = customersRaw.map((c) => ({
        id: c.id,
        name: c.name,
        status: c.status,
        daysSinceContact: c.last_contact
            ? Math.floor((Date.now() - new Date(c.last_contact).getTime()) / (24 * 60 * 60 * 1000))
            : null,
        tags: (c.metadata?.tags ?? []),
    }));
    const recentJournal = journal.map((e) => ({
        kind: e.kind,
        action: e.action,
        outcome: String(e.outcome ?? ''),
        at: e.created_at,
    }));
    // Signal score: real things worth acting on. The gate below keeps the engine
    // quiet on empty accounts so it never emits generic fluff.
    const signal = goals.length + customers.length + (recentJournal.length >= 3 ? 1 : 0) + pendingEscalations;
    return { facts: facts.map((f) => ({ key: f.fact_key, value: f.fact_value })), goals, customers, recentJournal, pendingEscalations, signal };
}
function renderSnapshot(s) {
    const lines = [];
    lines.push('## Business identity');
    if (s.facts.length === 0)
        lines.push('(no facts recorded yet)');
    for (const f of s.facts.slice(0, 30))
        lines.push(`- ${f.key}: ${f.value}`);
    lines.push('', '## Active goals');
    if (s.goals.length === 0)
        lines.push('(none)');
    for (const g of s.goals.slice(0, 10)) {
        const pct = g.target_value && g.target_value > 0 ? ` — ${Math.round((g.current_value / g.target_value) * 100)}%` : '';
        lines.push(`- [${g.category}] ${g.title}${g.deadline ? ` (due ${g.deadline})` : ''}${pct}`);
    }
    lines.push('', '## Customers / leads (most recent contact first)');
    if (s.customers.length === 0)
        lines.push('(none)');
    for (const c of s.customers.slice(0, 20)) {
        const silence = c.daysSinceContact === null ? 'never contacted' : `${c.daysSinceContact}d since contact`;
        const tags = c.tags.length ? ` [${c.tags.join(', ')}]` : '';
        lines.push(`- id=${c.id} "${c.name}" status=${c.status} ${silence}${tags}`);
    }
    lines.push('', '## Recent autonomous activity (last 3 days)');
    if (s.recentJournal.length === 0)
        lines.push('(none)');
    for (const e of s.recentJournal.slice(0, 15)) {
        lines.push(`- ${e.kind}: ${e.action} → ${e.outcome}`);
    }
    lines.push('', `Pending owner approvals waiting: ${s.pendingEscalations}`);
    return lines.join('\n');
}
// ─── 2. HYPOTHESISE ───
const SYSTEM_PROMPT = `أنت "المؤسس" — الذراع اليمنى الذكية لصاحب العمل. صاحب العمل *دفع* مقابلك، ووظيفتك الأولى أن تجعل هذا الاستثمار يستحق: تزيد دخله، توفّر تكاليفه، أو توفّر وقته — كل يوم. فكّر *نيابةً عن العمل* كموظف خبير طموح يبادر دون انتظار الأوامر، ويطارد كل فرصة تعود بعائد ملموس.

البوصلة في كل اقتراح: "كيف يجني صاحب العمل مالاً أو يوفّره من هذا؟" رتّب اقتراحاتك بأعلى عائد متوقّع أولاً (إيراد محتمل > احتفاظ بعميل > توفير وقت/تكلفة > تحسين تشغيلي). اربط الفائدة بالمال أو الوقت بصراحة في expected_benefit (مثلاً "متابعة ٣ عملاء ساكنين قد تُنقذ صفقات بقيمة ~X" أو "توفير ساعتين أسبوعياً").

قواعد صارمة:
1. اقترح فقط ما تدعمه البيانات المعطاة. لا تختلق عملاء أو أرقام أو أحداث. إن لم يكن هناك ما يستحق، أعد قائمة فارغة.
2. كل اقتراح يجب أن يكون قابلاً للتنفيذ الآن بأدوات الوكيل (بحث، صياغة رسالة، جدولة، تحديث بيانات، إنشاء ملف).
3. لا تكرر إجراءً ظهر مؤخراً في سجل النشاط.
4. غطِّ كل مجالات العمل لا مجالاً واحداً: مبيعات، تسويق، احتفاظ بالعملاء، تحصيل مستحقات، كفاءة تشغيلية، التزام. الأفضل موظف يرى الصورة كاملة.
5. صنّف كل اقتراح بـ action_type بدقة:
   - internal: بحث/تحليل/صياغة/تنظيم — بلا إرسال خارجي.
   - warm_outreach: رسالة لعميل/جهة *موجودة ومعروفة* (سبق التواصل).
   - cold_outreach: أول تواصل مع عميل محتمل جديد لم يسبق التعامل معه.
   - content_publish: نشر محتوى عام (سوشيال/موقع).
   - money / contract / data_delete: أي حركة مالية أو توقيع التزام أو حذف بيانات.
6. الأولوية للأعلى عائداً ثم الأقل مخاطرة. حد أقصى 6 اقتراحات، الأعلى عائداً أولاً.

أعد JSON فقط بالشكل:
{"proposals":[{"kind":"معرّف_قصير","title":"عنوان سطر واحد","action_type":"internal","target":"id عميل أو وصف أو owner","rationale":"السبب مستنداً للبيانات","draft":"مسودة الرسالة/المحتوى إن وُجد","risk":"low|medium|high","expected_benefit":"الفائدة الملموسة"}]}`;
async function generateProposals(tenantId, snapshot) {
    const messages = [
        { role: 'system', content: SYSTEM_PROMPT },
        { role: 'user', content: `حالة العمل الحالية:\n\n${renderSnapshot(snapshot)}\n\nما الإجراءات المفيدة التي يجب أن تبادر بها الآن؟ أعد JSON فقط.` },
    ];
    let raw;
    try {
        const res = await callWithFailover('reasoning', messages, { responseFormat: { type: 'json_object' }, maxTokens: 1800, timeoutMs: 60_000 }, tenantId, pickModelOverride('planning'));
        raw = res.content ?? '';
    }
    catch (err) {
        log.warn({ tenantId, err: err.message }, 'initiative generation LLM call failed');
        return [];
    }
    try {
        const json = JSON.parse(extractJson(raw));
        const parsed = proposalEnvelopeSchema.parse(json);
        return parsed.proposals.slice(0, 6);
    }
    catch (err) {
        log.warn({ tenantId, err: err.message, sample: raw.slice(0, 200) }, 'initiative proposal parse failed');
        return [];
    }
}
/** Tolerate models that wrap JSON in prose or ```json fences. */
function extractJson(raw) {
    const fenced = raw.match(/```(?:json)?\s*([\s\S]*?)```/);
    if (fenced?.[1])
        return fenced[1].trim();
    const start = raw.indexOf('{');
    const end = raw.lastIndexOf('}');
    if (start >= 0 && end > start)
        return raw.slice(start, end + 1);
    return raw.trim();
}
// ─── 3. DECIDE + ACT ───
export async function runInitiativeCycle(tenantId) {
    const result = { proposed: 0, autoQueued: 0, escalated: 0, skipped: 0, byKind: {} };
    await seedInitiativePolicies(tenantId).catch(() => undefined);
    const snapshot = await buildSnapshot(tenantId);
    // GROUNDING GATE — stay silent on a thin account rather than emit fluff.
    if (snapshot.signal < 2) {
        result.reason = 'insufficient real context — staying quiet to avoid generic noise';
        await journalRecord({
            tenantId,
            kind: 'initiative_skipped',
            actor: 'initiative',
            action: 'thin_context',
            outcome: 'success',
            reason: result.reason,
            metadata: { signal: snapshot.signal },
        }).catch(() => undefined);
        return result;
    }
    const proposals = await generateProposals(tenantId, snapshot);
    result.proposed = proposals.length;
    if (proposals.length === 0) {
        result.reason = 'no grounded initiative this cycle';
        return result;
    }
    const autoActions = [];
    for (const p of proposals) {
        result.byKind[p.kind] = (result.byKind[p.kind] ?? 0) + 1;
        const policyKey = ACTION_POLICY_KEY[p.action_type] ?? 'initiative_internal_action';
        let mode = 'ask';
        try {
            const decision = await evaluatePolicy({ tenantId, policyKey });
            if (decision.mode === 'auto' || decision.mode === 'ask' || decision.mode === 'never')
                mode = decision.mode;
        }
        catch { /* default ask — fail safe */ }
        if (mode === 'never') {
            result.skipped++;
            await journalRecord({
                tenantId, kind: 'initiative', actor: 'initiative', action: p.kind, target: p.target || null,
                outcome: 'declined', reason: `policy '${policyKey}' = never`,
                metadata: { action_type: p.action_type, title: p.title },
            }).catch(() => undefined);
            continue;
        }
        if (mode === 'auto') {
            autoActions.push(p);
            continue;
        }
        // ASK path → escalation card with the draft attached.
        const isHardGated = HARD_GATED_KEYS.has(policyKey);
        try {
            await escalate({
                tenantId,
                title: p.title,
                context: `${p.rationale}${p.expected_benefit ? `\n\nالفائدة المتوقعة: ${p.expected_benefit}` : ''}${p.draft ? `\n\nالمسودة:\n${p.draft}` : ''}`,
                recommendation: isHardGated
                    ? 'هذا إجراء لا يمكن التراجع عنه — يحتاج موافقتك الصريحة قبل أي تنفيذ.'
                    : 'أقترح تنفيذ هذا الإجراء. وافق لأبدأ.',
                counterargument: p.action_type === 'cold_outreach'
                    ? 'تواصل بارد مع جهة جديدة — قد يُعتبر إزعاجاً أو يخالف PDPL إن لم يكن هناك أساس نظامي.'
                    : 'قد يكون التوقيت غير مناسب أو الأولوية أقل من غيرها.',
                alternatives: ['نفّذ كما هو', 'عدّل المسودة أولاً', 'تجاهل هذه المرة'],
                riskLevel: isHardGated ? 'critical' : p.risk === 'high' ? 'high' : p.risk === 'low' ? 'low' : 'medium',
                expiresInMinutes: 24 * 60,
            });
            result.escalated++;
        }
        catch (err) {
            log.warn({ tenantId, kind: p.kind, err: err.message }, 'initiative escalation failed');
        }
    }
    // Batch all auto actions into ONE agent kickoff. The scheduler runs it through
    // handleAgentTurn (act-then-tell): the agent executes with its tools and the
    // result surfaces in the owner's chat — that IS the "inform me" guarantee.
    if (autoActions.length > 0) {
        result.autoQueued = autoActions.length;
        result.kickoffMessage = composeInitiativeKickoff(autoActions);
    }
    log.info({ tenantId, ...result, kickoffMessage: undefined, hasKickoff: Boolean(result.kickoffMessage) }, 'initiative cycle complete');
    return result;
}
function composeInitiativeKickoff(actions) {
    const lines = [];
    lines.push('[نظام · مبادرة تلقائية]');
    lines.push('راجعت حالة العمل وقررت تنفيذ الإجراءات التالية الآن (ضمن صلاحياتك المعتمدة). نفّذها فعلياً بأدواتك، ثم لخّص لي ما أنجزته:');
    lines.push('');
    actions.forEach((a, i) => {
        lines.push(`${i + 1}. ${a.title}`);
        lines.push(`   السبب: ${a.rationale}`);
        if (a.target)
            lines.push(`   الهدف: ${a.target}`);
        if (a.draft)
            lines.push(`   المسودة: ${a.draft.slice(0, 400)}`);
    });
    lines.push('');
    lines.push('قواعد: نفّذ الإجراءات القابلة للتنفيذ بأدواتك (web_search, create_excel, send_whatsapp للجهات المعروفة, schedule_once, إلخ). لا تنتظر إذني — اعمل ثم احكِ لي. إن تعذّر إجراء، اشرح السبب باختصار. سجّل كل خطوة.');
    return lines.join('\n');
}
//# sourceMappingURL=initiative.js.map