#!/usr/bin/env node
// Copyright (c) 2026 Hellmai Ltd
// SPDX-License-Identifier: AGPL-3.0-only
/**
 * Delegation List Command (WU-1950, WU-1674)
 *
 * Displays delegation trees for WUs or initiatives.
 * WU-1604: Includes explicit delegation lineage records from wu:delegate.
 * WU-2618: Initiative mode also surfaces orchestration launch receipts and
 * reconciled live WU state so operators can distinguish planned handoffs from
 * active or returned-but-incomplete work.
 */
import { createWUParser, WU_OPTIONS } from '@lumenflow/core/arg-parser';
import path from 'node:path';
import { DelegationRegistryStore } from '@lumenflow/core';
import { die } from '@lumenflow/core/error-handler';
import { PATTERNS, LUMENFLOW_PATHS } from '@lumenflow/core/wu-constants';
import { buildDelegationTree, formatDelegationTree, getDelegationsByWU, treeToJSON, STATUS_INDICATORS, } from '@lumenflow/core/delegation-tree';
import { DelegationStatus, DELEGATION_SCHEMA_VERSION_V2, } from '@lumenflow/core/delegation-registry-schema';
import { loadInitiativeWUs } from '@lumenflow/initiatives';
import { runCLI } from './cli-entry-point.js';
import { formatExecutionStateLabel, loadInitiativeExecutionSummary, } from './orchestrate-monitor.js';
import { collectSessionCrossLinkIndex, resolveSessionForDelegation, } from './session-cross-link.js';
const EMOJI = {
    WARNING: '⚠️',
};
const DELEGATION_LIST_OPTIONS = {
    initiative: {
        name: 'initiative',
        flags: '--initiative <initId>',
        description: 'Initiative ID to show all delegations for (e.g., INIT-001)',
    },
    json: {
        name: 'json',
        flags: '--json',
        description: 'Output as JSON',
        default: false,
    },
};
const LOG_PREFIX = '[delegation:list]';
const INIT_PATTERN = /^INIT-\d+$/;
const DEFAULT_PATHS = Object.freeze({
    REGISTRY_DIR: LUMENFLOW_PATHS.STATE_DIR,
    SESSIONS_DIR: LUMENFLOW_PATHS.SESSIONS,
});
// WU-2755: session cross-link helpers moved to ./session-cross-link.ts so
// mem:context --include-roster can reuse them without a circular import.
// Re-exported for any downstream caller that imported from delegation-list.
export { collectSessionCrossLinkIndex, resolveSessionForDelegation } from './session-cross-link.js';
export { collectActiveSessionRecords, resolveActiveSessionTarget } from './session-cross-link.js';
/**
 * Render session axes as a compact "lifecycle/specialty" string for table
 * output. Returns the unset marker when neither axis is populated.
 */
function formatSessionAxes(session) {
    if (!session) {
        return UNSET_ROLE_MARKER;
    }
    const lifecycle = session.lifecycle_role ?? UNSET_ROLE_MARKER;
    const specialty = session.specialty_profile ?? UNSET_ROLE_MARKER;
    if (lifecycle === UNSET_ROLE_MARKER && specialty === UNSET_ROLE_MARKER) {
        return UNSET_ROLE_MARKER;
    }
    return truncate(`${lifecycle}/${specialty}`, ROLE_DISPLAY_MAX_LENGTH);
}
function printLegend() {
    console.log('\nLegend:');
    console.log(`  ${STATUS_INDICATORS[DelegationStatus.PENDING]} pending`);
    console.log(`  ${STATUS_INDICATORS[DelegationStatus.COMPLETED]} completed`);
    console.log(`  ${STATUS_INDICATORS[DelegationStatus.TIMEOUT]} timeout`);
    console.log(`  ${STATUS_INDICATORS[DelegationStatus.CRASHED]} crashed`);
}
/**
 * Maximum rendered length for role/scope fields in table output (WU-2753).
 * Long ADR-014 role descriptors get truncated with an ellipsis so the table
 * stays readable on narrow terminals; --json always returns full values.
 */
const ROLE_DISPLAY_MAX_LENGTH = 48;
const UNSET_ROLE_MARKER = '-';
/**
 * Render a DelegationRoleDescriptor as a compact "lifecycle/specialty"
 * string for table output. Long role strings are truncated with an ellipsis.
 */
function formatRoleDescriptor(role) {
    if (!role) {
        return UNSET_ROLE_MARKER;
    }
    const base = `${role.lifecycle_role}/${role.specialty_profile}`;
    const alias = role.role_alias ? ` (${role.role_alias})` : '';
    return truncate(`${base}${alias}`, ROLE_DISPLAY_MAX_LENGTH);
}
/**
 * Render capabilities list as a comma-joined, truncated string.
 */
function formatCapabilities(capabilities) {
    if (!capabilities || capabilities.length === 0) {
        return UNSET_ROLE_MARKER;
    }
    return truncate(capabilities.join(','), ROLE_DISPLAY_MAX_LENGTH);
}
/**
 * Render ownership_scope as a compact description; the delegation-list table
 * shows the most load-bearing fields (initiative, WU ids, responsibility).
 */
function formatOwnershipScope(scope) {
    if (!scope) {
        return UNSET_ROLE_MARKER;
    }
    const parts = [];
    if (scope.initiative_id) {
        parts.push(scope.initiative_id);
    }
    if (scope.wu_ids && scope.wu_ids.length > 0) {
        parts.push(`wu:${scope.wu_ids.join(',')}`);
    }
    if (scope.lane) {
        parts.push(`lane:${scope.lane}`);
    }
    if (scope.responsibility_kinds.length > 0) {
        parts.push(`kind:${scope.responsibility_kinds.join(',')}`);
    }
    const rendered = parts.length === 0 ? UNSET_ROLE_MARKER : parts.join(' | ');
    return truncate(rendered, ROLE_DISPLAY_MAX_LENGTH);
}
function truncate(value, limit) {
    if (value.length <= limit) {
        return value;
    }
    return `${value.slice(0, limit - 1)}…`;
}
/**
 * Builds the per-delegation block that exposes the four ADR-014 axes
 * (requested_role, actual_role, capabilities, ownership_scope) beneath the
 * existing summary line. Omitted for v1-compat records without role data.
 *
 * WU-2754: When a matching session record is supplied, its lifecycle_role +
 * specialty_profile are rendered alongside the delegation axes so operators
 * can spot role mismatches between launch intent and live execution at a
 * glance.
 */
function buildRoleAxesBlock(delegation, indent, session) {
    const hasAnyDelegationRoleField = delegation.schemaVersion === DELEGATION_SCHEMA_VERSION_V2 ||
        delegation.requested_role !== undefined ||
        delegation.actual_role !== undefined ||
        (Array.isArray(delegation.capabilities) && delegation.capabilities.length > 0) ||
        delegation.ownership_scope !== undefined;
    const hasAnySessionRoleField = session !== undefined && (session.lifecycle_role || session.specialty_profile);
    if (!hasAnyDelegationRoleField && !hasAnySessionRoleField) {
        return [];
    }
    const lines = [
        `${indent}requested_role:  ${formatRoleDescriptor(delegation.requested_role)}`,
        `${indent}actual_role:     ${formatRoleDescriptor(delegation.actual_role)}`,
        `${indent}capabilities:    ${formatCapabilities(delegation.capabilities)}`,
        `${indent}ownership_scope: ${formatOwnershipScope(delegation.ownership_scope)}`,
    ];
    if (hasAnySessionRoleField) {
        lines.push(`${indent}session_axes:    ${formatSessionAxes(session)}`);
        if (session.display_name) {
            lines.push(`${indent}display_name:    ${truncate(session.display_name, ROLE_DISPLAY_MAX_LENGTH)}`);
        }
    }
    return lines;
}
async function loadInitiativeDelegations(baseDir, wuIds) {
    const store = new DelegationRegistryStore(path.join(baseDir, DEFAULT_PATHS.REGISTRY_DIR));
    try {
        await store.load();
    }
    catch {
        return [];
    }
    return store
        .getAllDelegations()
        .filter((delegation) => wuIds.has(delegation.parentWuId) || wuIds.has(delegation.targetWuId));
}
export async function buildInitiativeDelegationSnapshot(baseDir, initiativeId, providedWus) {
    const initiativeWus = providedWus ?? loadInitiativeWUs(initiativeId).wus;
    const wuIds = new Set(initiativeWus.map((wu) => wu.id));
    const executionSummary = await loadInitiativeExecutionSummary(baseDir, initiativeId, initiativeWus);
    const delegations = await loadInitiativeDelegations(baseDir, wuIds);
    // WU-2754: Cross-link session records with the delegations they belong to
    // so lifecycle_role + specialty_profile surface alongside the delegation
    // registry axes (requested_role / actual_role / capabilities /
    // ownership_scope). Best-effort: if the sessions dir is empty we still
    // render the delegation view unchanged.
    const sessionIndex = collectSessionCrossLinkIndex(baseDir);
    const sessionAxes = {};
    for (const delegation of delegations) {
        const match = resolveSessionForDelegation(delegation, sessionIndex);
        if (match) {
            sessionAxes[delegation.targetWuId] = match;
        }
    }
    return {
        initiative: initiativeId,
        launchAttempt: executionSummary.launchAttempt,
        logicalWaves: executionSummary.logicalWaves,
        launchReceipts: executionSummary.launchReceipts,
        delegations,
        liveState: executionSummary.wus,
        sessionAxes,
    };
}
function printInitiativeSnapshot(snapshot) {
    console.log(`${LOG_PREFIX} Orchestration snapshot for ${snapshot.initiative}:\n`);
    if (snapshot.launchReceipts.length === 0) {
        console.log('Launch receipts:\n  (none)');
    }
    else {
        console.log('Launch receipts:');
        for (const receipt of snapshot.launchReceipts) {
            const extras = [];
            if (typeof receipt.logical_wave === 'number') {
                extras.push(`logical wave ${receipt.logical_wave}`);
            }
            if (typeof receipt.launch_attempt === 'number') {
                extras.push(`attempt ${receipt.launch_attempt}`);
            }
            if (receipt.handoff_format) {
                extras.push(receipt.handoff_format);
            }
            console.log(`  - ${receipt.wu_id} [handoff_emitted${extras.length > 0 ? `; ${extras.join('; ')}` : ''}]`);
        }
    }
    console.log('');
    if (snapshot.delegations.length === 0) {
        console.log('Explicit delegations:\n  (none)');
    }
    else {
        console.log('Explicit delegations:');
        for (const delegation of snapshot.delegations) {
            const parts = [
                delegation.id,
                `${delegation.parentWuId} -> ${delegation.targetWuId}`,
                delegation.status ?? 'unknown',
            ];
            if (delegation.pickedUpBy) {
                parts.push(`picked up by ${delegation.pickedUpBy}`);
            }
            console.log(`  - ${parts.join(' | ')}`);
            // WU-2753 (ADR-014 extension 1): surface the four typed axes.
            // WU-2754 (ADR-014 extension 2): also render the session's
            // lifecycle_role + specialty_profile when a matching session exists.
            const matchedSession = snapshot.sessionAxes[delegation.targetWuId];
            for (const line of buildRoleAxesBlock(delegation, '      ', matchedSession)) {
                console.log(line);
            }
        }
    }
    console.log('');
    console.log('Live WU state:');
    for (const wu of snapshot.liveState) {
        console.log(`  - ${wu.wuId} [${formatExecutionStateLabel(wu.state)}; ${wu.reason}]`);
    }
}
export async function main() {
    const args = createWUParser({
        name: 'delegation-list',
        description: 'Display delegation trees for WUs or initiatives',
        options: [WU_OPTIONS.wu, DELEGATION_LIST_OPTIONS.initiative, DELEGATION_LIST_OPTIONS.json],
        required: [],
        allowPositionalId: false,
    });
    const { wu, initiative, json } = args;
    if (!wu && !initiative) {
        die('Either --wu or --initiative is required.\n\nUsage:\n  pnpm delegation:list --wu WU-XXX\n  pnpm delegation:list --initiative INIT-XXX');
    }
    if (wu && initiative) {
        die('Cannot specify both --wu and --initiative. Choose one.');
    }
    if (wu) {
        const wuId = wu.toUpperCase();
        if (!PATTERNS.WU_ID.test(wuId)) {
            die(`Invalid WU ID format: ${wu}. Expected format: WU-XXX`);
        }
        const delegations = await getDelegationsByWU(wuId, DEFAULT_PATHS.REGISTRY_DIR);
        if (json) {
            const tree = buildDelegationTree(delegations, wuId);
            console.log(JSON.stringify(treeToJSON(tree), null, 2));
            return;
        }
        if (delegations.length === 0) {
            console.log(`${LOG_PREFIX} ${EMOJI.WARNING} No delegations found for ${wuId}`);
            console.log(`\n${wuId} (root)`);
            console.log('  (no delegations)');
            return;
        }
        console.log(`${LOG_PREFIX} Delegation tree for ${wuId}:\n`);
        const tree = buildDelegationTree(delegations, wuId);
        console.log(formatDelegationTree(tree));
        // WU-2753: surface ADR-014 role axes per delegation below the tree.
        // WU-2754: cross-link with active session records for lifecycle_role +
        // specialty_profile.
        const sessionIndex = collectSessionCrossLinkIndex(process.cwd());
        const hasAnyV2Record = delegations.some((d) => d.schemaVersion === DELEGATION_SCHEMA_VERSION_V2 ||
            d.requested_role !== undefined ||
            d.actual_role !== undefined ||
            (Array.isArray(d.capabilities) && d.capabilities.length > 0) ||
            d.ownership_scope !== undefined);
        const hasAnySessionRecord = delegations.some((d) => resolveSessionForDelegation(d, sessionIndex));
        if (hasAnyV2Record || hasAnySessionRecord) {
            console.log('\nRole axes:');
            for (const delegation of delegations) {
                const matchedSession = resolveSessionForDelegation(delegation, sessionIndex);
                const axesLines = buildRoleAxesBlock(delegation, '      ', matchedSession);
                if (axesLines.length === 0) {
                    continue;
                }
                console.log(`  ${delegation.id} (${delegation.parentWuId} -> ${delegation.targetWuId}):`);
                for (const line of axesLines) {
                    console.log(line);
                }
            }
        }
        printLegend();
        console.log(`\nTotal: ${delegations.length} delegation(s)`);
        return;
    }
    if (initiative) {
        const initId = initiative.toUpperCase();
        if (!INIT_PATTERN.test(initId)) {
            die(`Invalid initiative ID format: ${initiative}. Expected format: INIT-XXX`);
        }
        const snapshot = await buildInitiativeDelegationSnapshot(process.cwd(), initId);
        if (json) {
            console.log(JSON.stringify(snapshot, null, 2));
            return;
        }
        if (snapshot.launchReceipts.length === 0 && snapshot.delegations.length === 0) {
            console.log(`${LOG_PREFIX} ${EMOJI.WARNING} No delegations found for ${initId}`);
            return;
        }
        printInitiativeSnapshot(snapshot);
    }
}
if (import.meta.main) {
    void runCLI(main);
}
//# sourceMappingURL=delegation-list.js.map