/*
 * Copyright 2026, Jaime Terrats.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { existsSync, readdirSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
import { resolveRuntimeCapabilities } from '../profiles/index.js';
import { getDefaultConfig, preprocessContext } from './context-preprocessor.js';
import { PROFILE_DOC_SOURCES, syncProfileDocs } from './docs-sync.js';
import { listRecords } from './orchestration-records.js';
import { PHASE_TO_PROFILE, resolvePhaseInstructions } from './workflow-instructions.js';
// ─── Phase deliverable expectations ─────────────────────────────────────────
const PHASE_DELIVERABLES = {
    pm: 'Sprint plan or prioritized backlog with acceptance criteria',
    ba: 'Refined user stories with acceptance criteria and edge cases',
    architect: 'Architecture Decision Record (ADR) with sizing and trade-offs',
    ux: 'Wireframes or interaction specs with accessibility annotations',
    developer: 'Working implementation with passing tests',
    'security-review': 'Security findings report with severity classification',
    qa: 'Test plan execution results and coverage report',
    release: 'Release checklist verification and deploy evidence',
};
// ─── Phase constraints ──────────────────────────────────────────────────────
const OOTB_FIRST_CONSTRAINT = 'OOTB-first: before proposing custom Apex, LWC, Flows, or custom objects, verify the functionality is not already available declaratively (Approval Process, Entitlements, Milestones, OmniChannel, Escalation Rules, Assignment Rules, Agentforce, Lead Nurturing, Knowledge, etc.). Only propose custom code when OOTB does not cover the requirement.';
const NO_DUPLICATE_OBJECTS_CONSTRAINT = 'No duplicate objects: do not propose custom objects that replicate standard object functionality. Use standard objects (Case, Entitlement, Territory2, WorkOrder, Asset, etc.) with custom fields when possible.';
const PHASE_CONSTRAINTS = {
    pm: ['Do not implement code', 'Do not make architecture decisions', 'Focus on what and why, not how'],
    ba: [
        'Do not implement code',
        'Do not make architecture decisions',
        'Refine requirements only',
        'Identify which requirements map to OOTB features vs custom development',
    ],
    architect: [
        'Do not implement code',
        'Design only — produce ADR',
        'Include sizing decision',
        OOTB_FIRST_CONSTRAINT,
        NO_DUPLICATE_OBJECTS_CONSTRAINT,
    ],
    ux: ['Do not implement code', 'Design only — produce specs/wireframes'],
    developer: [
        'Consider architecture decisions from prior phases; if you identify issues, raise a clarification instead of silently diverging',
        'Do not skip tests',
        OOTB_FIRST_CONSTRAINT,
        NO_DUPLICATE_OBJECTS_CONSTRAINT,
    ],
    'security-review': ['Do not fix issues — only report findings', 'Classify severity for each finding'],
    qa: ['Do not fix bugs — only report failures', 'Cover golden path and edge cases'],
    release: ['Do not deploy without explicit approval', 'Verify all gates passed'],
};
const DEFAULT_MAX_CONTEXT_CHARS = 4000;
const DEFAULT_MAX_PLAYBOOK_CHARS = 6000;
const DEFAULT_MAX_RULES_CHARS = 3000;
const DEFAULT_TOTAL_BUDGET_CHARS = 32_000;
export async function buildDelegationPacket(options) {
    const { cwd, taskId, runId, phase, goal, priorPhases = [], runtimeTarget = 'generic' } = options;
    const maxContextChars = options.maxContextChars ?? DEFAULT_MAX_CONTEXT_CHARS;
    const maxPlaybookChars = options.maxPlaybookChars ?? DEFAULT_MAX_PLAYBOOK_CHARS;
    const totalBudget = options.totalBudgetChars ?? DEFAULT_TOTAL_BUDGET_CHARS;
    const profile = PHASE_TO_PROFILE[phase];
    const rawProfileRules = truncate(loadProfileRules(cwd, profile, runtimeTarget), DEFAULT_MAX_RULES_CHARS);
    const rawPlaybooks = loadPlaybooks(cwd, phase, maxPlaybookChars);
    const references = await resolveReferences(cwd, phase, profile, taskId);
    const context = buildContext(cwd, taskId, priorPhases, goal, maxContextChars);
    // Pre-process inlined content to reduce large files before budget trimming
    const ppConfig = options.preProcessorConfig ?? getDefaultConfig();
    const filesToProcess = [
        ...(rawProfileRules ? [{ path: `rules/${profile}`, content: rawProfileRules }] : []),
        ...rawPlaybooks.map((pb, i) => ({ path: `playbook/${phase}/${i}`, content: pb })),
    ];
    const processed = preprocessContext(filesToProcess, ppConfig);
    const processedRules = rawProfileRules ? processed.find((p) => p.path.startsWith('rules/'))?.content ?? null : null;
    const processedPlaybooks = processed.filter((p) => p.path.startsWith('playbook/')).map((p) => p.content);
    const packet = {
        taskId,
        runId,
        phase,
        profile,
        profileRules: processedRules,
        playbooks: processedPlaybooks,
        references,
        context,
        expectedDeliverable: PHASE_DELIVERABLES[phase],
        constraints: PHASE_CONSTRAINTS[phase],
    };
    return applyTotalBudget(packet, totalBudget, { includeTrimReport: options.includeTrimReport });
}
// ─── Total budget trimming ──────────────────────────────────────────────────
export function estimatePacketChars(packet) {
    let chars = 0;
    chars += packet.context.goal.length;
    chars += packet.expectedDeliverable.length;
    for (const c of packet.constraints)
        chars += c.length + 4;
    for (const d of packet.context.decisions)
        chars += d.summary.length + (d.rationale?.length ?? 0) + 40;
    for (const e of packet.context.evidence)
        chars += e.summary.length + 40;
    for (const cl of packet.context.clarifications)
        chars += cl.question.length + cl.answer.length + 40;
    for (const ac of packet.context.acceptanceCriteria ?? [])
        chars += ac.length + 10;
    chars += packet.context.priorPhases.length * 40;
    for (const pb of packet.playbooks)
        chars += pb.length;
    chars += packet.profileRules?.length ?? 0;
    for (const ref of packet.references)
        chars += ref.path.length + ref.title.length + 20;
    return chars;
}
function trackTrim(trimmedSections, section, charsReclaimed, removedItems) {
    const existing = trimmedSections.find((s) => s.section === section);
    if (existing) {
        existing.removedItems += removedItems;
        existing.charsReclaimed += charsReclaimed;
    }
    else {
        trimmedSections.push({ section, removedItems, charsReclaimed });
    }
}
function trimArraySection(result, budget, array, section, trimmedSections, minKeep = 0) {
    while (estimatePacketChars(result) > budget && array.length > minKeep) {
        const before = estimatePacketChars(result);
        array.shift();
        trackTrim(trimmedSections, section, before - estimatePacketChars(result), 1);
    }
}
function trimPlaybooks(result, budget, trimmedSections) {
    while (estimatePacketChars(result) > budget && result.playbooks.length > 0) {
        const lastIdx = result.playbooks.length - 1;
        const lastPlaybook = result.playbooks[lastIdx];
        const before = estimatePacketChars(result);
        if (lastPlaybook.length > 200) {
            result.playbooks[lastIdx] = lastPlaybook.slice(0, Math.floor(lastPlaybook.length / 2)) + '\n[...truncated]';
            trackTrim(trimmedSections, 'playbooks', before - estimatePacketChars(result), 0);
        }
        else {
            result.playbooks.pop();
            trackTrim(trimmedSections, 'playbooks', before - estimatePacketChars(result), 1);
        }
    }
}
function trimProfileRules(result, budget, trimmedSections) {
    if (estimatePacketChars(result) <= budget || !result.profileRules)
        return;
    const before = estimatePacketChars(result);
    const excess = before - budget;
    const newLen = Math.max(0, result.profileRules.length - excess);
    result.profileRules = newLen === 0 ? null : result.profileRules.slice(0, newLen) + '\n[...truncated]';
    trimmedSections.push({
        section: 'profileRules',
        removedItems: newLen === 0 ? 1 : 0,
        charsReclaimed: before - estimatePacketChars(result),
    });
}
export function applyTotalBudget(packet, budget, options) {
    const originalChars = estimatePacketChars(packet);
    if (originalChars <= budget) {
        if (options?.includeTrimReport) {
            return { ...packet, trimReport: { originalChars, finalChars: originalChars, trimmedSections: [] } };
        }
        return packet;
    }
    // Deep clone mutable sections to maintain purity
    const result = {
        ...packet,
        playbooks: [...packet.playbooks],
        context: {
            ...packet.context,
            evidence: [...packet.context.evidence],
            priorPhases: [...packet.context.priorPhases],
            decisions: [...packet.context.decisions],
            clarifications: [...packet.context.clarifications],
            acceptanceCriteria: [...(packet.context.acceptanceCriteria ?? [])],
        },
        profileRules: packet.profileRules,
    };
    const trimmedSections = [];
    // Priority 1: evidence — drop oldest first
    trimArraySection(result, budget, result.context.evidence, 'evidence', trimmedSections);
    // Priority 2: priorPhases — drop oldest first
    trimArraySection(result, budget, result.context.priorPhases, 'priorPhases', trimmedSections);
    // Priority 3: decisions — drop oldest first, keep at least 1
    trimArraySection(result, budget, result.context.decisions, 'decisions', trimmedSections, 1);
    // Priority 4: playbooks — truncate last, then drop from end
    trimPlaybooks(result, budget, trimmedSections);
    // Priority 5: profileRules — truncate (last resort)
    trimProfileRules(result, budget, trimmedSections);
    if (options?.includeTrimReport) {
        result.trimReport = { originalChars, finalChars: estimatePacketChars(result), trimmedSections };
    }
    return result;
}
// ─── Render packet as prompt ────────────────────────────────────────────────
export function renderPacketAsPrompt(packet) {
    const sections = [];
    sections.push(`# Phase: ${packet.phase} (${packet.profile})`);
    sections.push(`Task: ${packet.taskId} | Run: ${packet.runId}`);
    sections.push('');
    sections.push('## Goal');
    sections.push(packet.context.goal);
    sections.push('');
    sections.push('## Expected Deliverable');
    sections.push(packet.expectedDeliverable);
    sections.push('');
    if (packet.constraints.length > 0) {
        sections.push('## Constraints');
        for (const c of packet.constraints)
            sections.push(`- ${c}`);
        sections.push('');
    }
    if (packet.context.decisions.length > 0) {
        sections.push('## Prior Decisions');
        for (const d of packet.context.decisions) {
            sections.push(`- [${d.owner}] ${d.summary}${d.rationale ? ` — ${d.rationale}` : ''}`);
        }
        sections.push('');
    }
    if (packet.context.evidence.length > 0) {
        sections.push('## Prior Evidence');
        for (const e of packet.context.evidence) {
            sections.push(`- [${e.owner}] ${e.summary} (${e.kind})`);
        }
        sections.push('');
    }
    if (packet.context.acceptanceCriteria?.length > 0) {
        sections.push('## Acceptance Criteria');
        for (const ac of packet.context.acceptanceCriteria)
            sections.push(`- ${ac}`);
        sections.push('');
    }
    if (packet.context.clarifications.length > 0) {
        sections.push('## Answered Clarifications');
        for (const c of packet.context.clarifications) {
            sections.push(`- Q (${c.fromRole}→${c.toRole}): ${c.question}`);
            sections.push(`  A: ${c.answer}`);
        }
        sections.push('');
    }
    if (packet.context.priorPhases.length > 0) {
        sections.push('## Prior Phases');
        for (const p of packet.context.priorPhases) {
            sections.push(`- ${p.phase} (${p.profile}): ${p.status}`);
        }
        sections.push('');
    }
    if (packet.playbooks.length > 0) {
        sections.push('## Playbooks');
        for (const pb of packet.playbooks) {
            sections.push('```');
            sections.push(pb);
            sections.push('```');
            sections.push('');
        }
    }
    if (packet.references.length > 0) {
        sections.push('## Available Documentation');
        sections.push('Read these files as needed using the Read tool:');
        for (const ref of packet.references) {
            sections.push(`- \`${ref.path}\` — ${ref.title} (${ref.source})`);
        }
        sections.push('');
    }
    sections.push('## Response Protocol');
    sections.push('When you finish, output your response in one of these formats:');
    sections.push('');
    sections.push('If delivering results:');
    sections.push('```json');
    sections.push('{ "type": "deliverable", "content": "<your deliverable>", "artifacts": ["file1.ts"] }');
    sections.push('```');
    sections.push('');
    sections.push('If you need clarification before proceeding:');
    sections.push('```json');
    sections.push('{ "type": "clarification", "question": "<your question>", "options": ["A", "B"] }');
    sections.push('```');
    sections.push('');
    sections.push('If blocked and cannot proceed:');
    sections.push('```json');
    sections.push('{ "type": "blocked", "reason": "<why you cannot proceed>" }');
    sections.push('```');
    return sections.join('\n');
}
// ─── Response parser ────────────────────────────────────────────────────────
export function parseSubAgentResponse(stdout, exitCode) {
    const fenceStart = stdout.lastIndexOf('```json');
    if (fenceStart !== -1) {
        const contentStart = stdout.indexOf('\n', fenceStart) + 1;
        const fenceEnd = stdout.indexOf('```', contentStart);
        if (fenceEnd !== -1) {
            const block = stdout.slice(contentStart, fenceEnd).trim();
            const parsed = tryParseResponse(block);
            if (parsed)
                return parsed;
        }
    }
    const lastBrace = stdout.lastIndexOf('}');
    if (lastBrace !== -1) {
        const typeIdx = stdout.lastIndexOf('"type"');
        if (typeIdx !== -1) {
            const objStart = stdout.lastIndexOf('{', typeIdx);
            if (objStart !== -1) {
                const candidate = stdout.slice(objStart, lastBrace + 1);
                const parsed = tryParseResponse(candidate);
                if (parsed)
                    return parsed;
            }
        }
    }
    if (exitCode === undefined || exitCode === 0) {
        if (stdout.trim().length > 50) {
            return { type: 'deliverable', content: stdout.trim() };
        }
    }
    return null;
}
function tryParseResponse(text) {
    try {
        const parsed = JSON.parse(text);
        if (parsed.type && ['deliverable', 'clarification', 'blocked'].includes(parsed.type)) {
            return parsed;
        }
        return null;
    }
    catch {
        return null;
    }
}
const TOKEN_PATTERNS = [
    /input[:\s]+(\d[\d,]+)\s*tokens?/i,
    /output[:\s]+(\d[\d,]+)\s*tokens?/i,
    /cost[:\s]+\$?([\d.]+)/i,
    /model[:\s]+(claude[\w.-]+)/i,
];
export function parseTokenUsage(stdout) {
    const tail = stdout.slice(-2000);
    const inputMatch = TOKEN_PATTERNS[0].exec(tail);
    const outputMatch = TOKEN_PATTERNS[1].exec(tail);
    if (!inputMatch && !outputMatch)
        return null;
    const inputTokens = inputMatch ? Number(inputMatch[1].replace(/,/g, '')) : 0;
    const outputTokens = outputMatch ? Number(outputMatch[1].replace(/,/g, '')) : 0;
    if (inputTokens === 0 && outputTokens === 0)
        return null;
    const costMatch = TOKEN_PATTERNS[2].exec(tail);
    const modelMatch = TOKEN_PATTERNS[3].exec(tail);
    return {
        inputTokens,
        outputTokens,
        ...(costMatch ? { costUsd: Number(costMatch[1]) } : {}),
        ...(modelMatch ? { model: modelMatch[1] } : {}),
    };
}
// ─── Internals ──────────────────────────────────────────────────────────────
function loadProfileRules(cwd, profile, runtimeTarget = 'generic') {
    const runtimePaths = {
        claude: [join(cwd, '.claude', 'rules', `${profile}.md`)],
        cursor: [join(cwd, '.cursor', 'rules', `${profile}.mdc`)],
        codex: [join(cwd, '.codex', `${profile}.md`)],
        generic: [
            join(cwd, '.claude', 'rules', `${profile}.md`),
            join(cwd, '.cursor', 'rules', `${profile}.mdc`),
            join(cwd, '.codex', `${profile}.md`),
        ],
    };
    const paths = runtimePaths[runtimeTarget] ?? runtimePaths['generic'];
    for (const p of paths) {
        if (existsSync(p))
            return readFileSync(p, 'utf8');
    }
    return null;
}
async function resolveReferences(cwd, phase, profile, taskId) {
    const refs = [];
    const seen = new Set();
    const instructions = resolvePhaseInstructions(cwd, phase);
    for (const pb of instructions.playbooks) {
        if (pb.exists && !seen.has(pb.path)) {
            seen.add(pb.path);
            refs.push({ path: pb.path, title: pb.filename.replace(/\.md$/, ''), source: 'playbook' });
        }
    }
    if (phase === 'security-review') {
        const envAuditPath = join(cwd, '.setup-agents', 'env-audit.md');
        if (existsSync(envAuditPath) && !seen.has(envAuditPath)) {
            seen.add(envAuditPath);
            refs.push({ path: envAuditPath, title: 'Environment Variable Audit', source: 'doc' });
        }
    }
    const capabilities = resolveRuntimeCapabilities({ profileIds: [profile], signals: [taskId, phase] });
    for (const cap of capabilities) {
        for (const skill of cap.skills) {
            if (!seen.has(skill.path) && existsSync(skill.path)) {
                seen.add(skill.path);
                refs.push({ path: skill.path, title: skill.title, source: 'skill' });
            }
        }
    }
    const docsDir = join(cwd, '.setup-agents', 'docs', profile);
    // Lazy fetch: if profile has doc sources but no local docs, sync on demand
    if (!existsSync(docsDir) || readdirSync(docsDir).filter((f) => f.endsWith('.md')).length === 0) {
        if (PROFILE_DOC_SOURCES[profile]) {
            try {
                await syncProfileDocs(cwd, profile, { maxPagesPerSource: 5 });
            }
            catch {
                // Network unavailable — continue without docs
            }
        }
    }
    if (existsSync(docsDir)) {
        const files = readdirSync(docsDir).filter((f) => f.endsWith('.md') && !f.endsWith('.index.md'));
        for (const file of files) {
            const docPath = join(docsDir, file);
            const indexPath = docPath.replace(/\.md$/, '.index.md');
            if (!seen.has(docPath)) {
                seen.add(docPath);
                if (existsSync(indexPath)) {
                    refs.push({
                        path: indexPath,
                        title: `${file.replace(/\.md$/, '')} (index — read sections with offset/limit)`,
                        source: 'doc',
                    });
                    refs.push({
                        path: docPath,
                        title: `${file.replace(/\.md$/, '')} (full doc — read by section)`,
                        source: 'doc',
                    });
                }
                else {
                    refs.push({ path: docPath, title: file.replace(/\.md$/, ''), source: 'doc' });
                }
            }
        }
    }
    return refs;
}
function loadPlaybooks(cwd, phase, maxChars) {
    const instructions = resolvePhaseInstructions(cwd, phase);
    const contents = [];
    let totalChars = 0;
    for (const pb of instructions.playbooks) {
        if (!pb.exists)
            continue;
        const content = readFileSync(pb.path, 'utf8');
        if (totalChars + content.length > maxChars) {
            const remaining = maxChars - totalChars;
            if (remaining > 200)
                contents.push(content.slice(0, remaining) + '\n[...truncated]');
            break;
        }
        contents.push(content);
        totalChars += content.length;
    }
    return contents;
}
function buildContext(cwd, taskId, priorPhases, goal, maxChars) {
    const allDecisions = listRecords(cwd, 'decision');
    const allEvidence = listRecords(cwd, 'evidence');
    const allHandoffs = listRecords(cwd, 'handoff');
    const decisions = allDecisions
        .filter((d) => d.taskIds.includes(taskId))
        .map((d) => ({
        id: d.id,
        owner: d.owner?.replace('setup-agents:', '') ?? 'unknown',
        summary: d.summary,
        rationale: d['rationale'],
    }));
    const evidence = allEvidence
        .filter((e) => e.taskIds.includes(taskId))
        .map((e) => ({
        id: e.id,
        owner: e['owner']
            ? String(e['owner']).replace('setup-agents:', '')
            : 'unknown',
        summary: e.summary,
        kind: e['evidenceKind'],
    }));
    const acceptanceCriteria = allHandoffs
        .filter((h) => h.taskIds.includes(taskId))
        .flatMap((h) => h['acceptanceCriteria'] ?? []);
    // Budget enforcement — drop oldest items first if context exceeds limit
    const ctx = { goal, decisions, evidence, clarifications: [], priorPhases, acceptanceCriteria };
    let size = estimateContextChars(ctx);
    while (size > maxChars && ctx.evidence.length > 0) {
        ctx.evidence.shift();
        size = estimateContextChars(ctx);
    }
    while (size > maxChars && ctx.decisions.length > 1) {
        ctx.decisions.shift();
        size = estimateContextChars(ctx);
    }
    return ctx;
}
function estimateContextChars(ctx) {
    let chars = ctx.goal.length;
    for (const d of ctx.decisions)
        chars += d.summary.length + (d.rationale?.length ?? 0) + 40;
    for (const e of ctx.evidence)
        chars += e.summary.length + 40;
    for (const c of ctx.clarifications)
        chars += c.question.length + c.answer.length + 40;
    for (const ac of ctx.acceptanceCriteria ?? [])
        chars += ac.length + 10;
    chars += ctx.priorPhases.length * 30;
    return chars;
}
function truncate(value, maxChars) {
    if (!value || value.length <= maxChars)
        return value;
    return value.slice(0, maxChars) + '\n[...truncated]';
}
//# sourceMappingURL=workflow-delegation.js.map