/**
 * V2 WorkItem Type Definitions
 *
 * A WorkItem is the unified execution primitive. Every action the system
 * takes to fulfill a Request or advance a Mission materializes as a WorkItem.
 * This replaces v1's separate delegation, scheduled-message, and event-subscription concepts.
 *
 * @module types/v2/work-item.types
 */
import { v4 as uuidv4 } from 'uuid';
/** All valid WorkItemType values. */
export const WORK_ITEM_TYPES = [
    'delegate',
    'project_task',
    'check',
    'notify',
    'cron_run',
    'review',
    'confirm',
    'reconcile',
];
/** All valid WorkItemOwner values. */
export const WORK_ITEM_OWNERS = [
    'orchestrator',
    'team_lead',
    'agent',
    'system',
];
/** All valid WorkItemStatus values. */
export const WORK_ITEM_STATUSES = [
    'queued',
    'scheduled',
    'proposed',
    'accepted',
    'running',
    'blocked',
    'escalated',
    'done_by_worker',
    'verified',
    'rejected',
    'done',
    'failed',
    'cancelled',
];
/** Terminal statuses — no further transitions allowed. */
export const TERMINAL_WORK_ITEM_STATUSES = new Set([
    'done',
    'verified',
    'cancelled',
]);
/**
 * Broader "exited the active queue" status set used by SLA-style consumers
 * (e.g. `RequestSlaSubscriber`, `MissionReminderService`) that need to
 * treat `failed` and `rejected` as terminal for *their* purposes — even
 * though those statuses are NOT terminal in the strict state-machine sense
 * (they can transition back to `queued` via the retry path).
 *
 * Hoisted to close the canonical-set duplication anti-pattern called out
 * by Arch on PR #357 (INBOUND-1) N2. Previously each SLA caller redeclared
 * a 5-element local set; this single source of truth keeps them aligned
 * if a new "exit-the-queue" status is added in the future.
 *
 * Members:
 * - `done` / `verified` / `cancelled` — strictly terminal (cannot transition).
 * - `failed` / `rejected` — semantically "exited active queue" for SLA timer
 *   no-op + reentrancy-lock release purposes, even though the state machine
 *   permits `rejected → queued` (retry) and `failed` is not formally terminal.
 *
 * Use this set when answering "should the SLA timer / reentrancy lock
 * release based on the current WI state?", and use {@link TERMINAL_WORK_ITEM_STATUSES}
 * when answering "is the state machine done with this WI?".
 */
export const SLA_TERMINAL_WORK_ITEM_STATUSES = new Set([
    'done',
    'verified',
    'cancelled',
    'failed',
    'rejected',
]);
// ---------------------------------------------------------------------------
// Valid State Transitions
// ---------------------------------------------------------------------------
/**
 * Map of valid status transitions for WorkItems.
 *
 * Includes both the original simple flow (queued→running→done) and
 * the extended acceptance/verification flow (proposed→accepted→running→done_by_worker→verified).
 */
export const WORK_ITEM_TRANSITIONS = {
    queued: new Set(['running', 'proposed', 'scheduled', 'cancelled']),
    scheduled: new Set(['queued', 'cancelled']),
    proposed: new Set(['accepted', 'rejected', 'cancelled']),
    accepted: new Set(['running', 'cancelled']),
    // TRANS-2: `running → queued` legalised so TaskPoolService.releaseBack
    // (Reconciler abandon path, controller-initiated agent-busy releases)
    // can route through the guarded {@link transitionStatus} helper. Gated
    // in TRANSITION_PERMISSIONS to TL/orchestrator/system — agents cannot
    // self-revive a running claim.
    running: new Set(['done', 'done_by_worker', 'failed', 'blocked', 'escalated', 'cancelled', 'queued']),
    blocked: new Set(['queued', 'cancelled']),
    escalated: new Set(['queued', 'cancelled']),
    done_by_worker: new Set(['verified', 'rejected']),
    verified: new Set(),
    rejected: new Set(['queued']),
    done: new Set(),
    failed: new Set(['queued']),
    cancelled: new Set(),
};
// ---------------------------------------------------------------------------
// Role-Based Transition Permissions
// ---------------------------------------------------------------------------
/**
 * Defines which roles are allowed to trigger specific status transitions.
 *
 * Format: `from→to` key maps to a set of allowed WorkItemOwner roles.
 * If a transition is not listed here, any role may trigger it (backward compatible).
 * The 'system' role (reconciler) can always trigger any valid transition.
 */
export const TRANSITION_PERMISSIONS = {
    // Only TL or orchestrator can propose tasks
    'queued→proposed': new Set(['team_lead', 'orchestrator']),
    // Only the assigned agent can accept or reject a proposal
    'proposed→accepted': new Set(['agent']),
    'proposed→rejected': new Set(['agent']),
    // Only the assigned agent can report completion
    'running→done_by_worker': new Set(['agent']),
    // Only the assigned agent can escalate
    'running→escalated': new Set(['agent']),
    // Only TL can verify or reject worker output
    'done_by_worker→verified': new Set(['team_lead']),
    'done_by_worker→rejected': new Set(['team_lead']),
    // Simple done — allowed for agents (simple tasks) and system (reconciler)
    'running→done': new Set(['agent', 'system', 'orchestrator']),
    // TRANS-1 F-F: only TL / orchestrator / system may re-queue a rejected
    // WorkItem. Without this entry, the backward-compat default-allow at
    // isTransitionPermitted line ~322 lets any actor (including the agent
    // whose work was rejected) re-queue itself — a self-revival hazard.
    // BRIDGE-1 retry policy is the canonical re-queueing path; manual
    // TL action (or system reconciler) is the only other legal path.
    'rejected→queued': new Set(['team_lead', 'orchestrator', 'system']),
    // failed→queued (BRIDGE-1 retry path) — same gate as rejected→queued.
    // Agent cannot self-resurrect a failed WorkItem.
    'failed→queued': new Set(['team_lead', 'orchestrator', 'system']),
    // blocked→queued (dependency-resolution path) — system-only by
    // default. The TaskPoolService.resolveBlockedDependents() helper
    // is the canonical caller and runs as system.
    'blocked→queued': new Set(['team_lead', 'orchestrator', 'system']),
    // TRANS-2: running→queued (releaseBack abandon path). Same gate as
    // the other re-queue transitions — Reconciler revoke and TL manual
    // release are the legitimate callers; agents cannot self-revive a
    // running claim by re-queueing it.
    'running→queued': new Set(['team_lead', 'orchestrator', 'system']),
};
/**
 * Checks whether a role is permitted to trigger a specific status transition.
 *
 * If no explicit permission is defined for a transition, it is considered
 * open to all roles (backward compatible). The 'system' role always has
 * permission for any valid transition.
 *
 * @param from - Current status
 * @param to - Desired next status
 * @param actorRole - Role of the agent/system attempting the transition
 * @returns True if the role is permitted to trigger this transition
 */
export function isTransitionPermitted(from, to, actorRole) {
    // System role can always transition
    if (actorRole === 'system')
        return true;
    const key = `${from}→${to}`;
    const allowed = TRANSITION_PERMISSIONS[key];
    // If no explicit permission defined, allow any role (backward compatible)
    if (!allowed)
        return true;
    return allowed.has(actorRole);
}
// ---------------------------------------------------------------------------
// Type Guards
// ---------------------------------------------------------------------------
/**
 * Checks whether a string is a valid WorkItemType.
 *
 * @param value - The string to check
 * @returns True if value is a valid WorkItemType
 */
export function isValidWorkItemType(value) {
    return WORK_ITEM_TYPES.includes(value);
}
/**
 * Checks whether a string is a valid WorkItemStatus.
 *
 * @param value - The string to check
 * @returns True if value is a valid WorkItemStatus
 */
export function isValidWorkItemStatus(value) {
    return WORK_ITEM_STATUSES.includes(value);
}
/**
 * Checks whether a string is a valid WorkItemOwner.
 *
 * @param value - The string to check
 * @returns True if value is a valid WorkItemOwner
 */
export function isValidWorkItemOwner(value) {
    return WORK_ITEM_OWNERS.includes(value);
}
/**
 * Checks whether a WorkItem status transition is valid.
 *
 * @param from - Current status
 * @param to - Desired next status
 * @returns True if transition is allowed
 */
export function isValidWorkItemTransition(from, to) {
    return WORK_ITEM_TRANSITIONS[from].has(to);
}
/**
 * Validates that an unknown value is structurally a valid WorkItem.
 *
 * @param value - Unknown value to validate
 * @returns True if value conforms to the WorkItem interface
 */
export function isWorkItem(value) {
    if (typeof value !== 'object' || value === null)
        return false;
    const obj = value;
    return (typeof obj.id === 'string' &&
        typeof obj.type === 'string' &&
        isValidWorkItemType(obj.type) &&
        typeof obj.owner === 'string' &&
        isValidWorkItemOwner(obj.owner) &&
        typeof obj.title === 'string' &&
        typeof obj.status === 'string' &&
        isValidWorkItemStatus(obj.status) &&
        typeof obj.createdAt === 'string' &&
        typeof obj.retryCount === 'number' &&
        typeof obj.maxRetries === 'number');
}
// ---------------------------------------------------------------------------
// Validation
// ---------------------------------------------------------------------------
/**
 * Validates CreateWorkItemInput and returns an array of error messages.
 *
 * @param input - The creation input to validate
 * @returns Array of validation error strings (empty = valid)
 */
export function validateCreateWorkItemInput(input) {
    const errors = [];
    if (!input.type || !isValidWorkItemType(input.type)) {
        errors.push(`type must be one of: ${WORK_ITEM_TYPES.join(', ')}`);
    }
    if (!input.owner || !isValidWorkItemOwner(input.owner)) {
        errors.push(`owner must be one of: ${WORK_ITEM_OWNERS.join(', ')}`);
    }
    if (!input.title || typeof input.title !== 'string') {
        errors.push('title is required and must be a non-empty string');
    }
    if (input.scheduledAt) {
        const d = new Date(input.scheduledAt);
        if (isNaN(d.getTime())) {
            errors.push('scheduledAt must be a valid ISO8601 date string');
        }
    }
    if (input.maxRetries !== undefined && (input.maxRetries < 0 || !Number.isInteger(input.maxRetries))) {
        errors.push('maxRetries must be a non-negative integer');
    }
    if (input.briefMarkdown !== undefined) {
        if (typeof input.briefMarkdown !== 'string') {
            errors.push('briefMarkdown must be a string');
        }
        else if (Buffer.byteLength(input.briefMarkdown, 'utf8') > MAX_BRIEF_MARKDOWN_BYTES) {
            errors.push(`briefMarkdown exceeds ${MAX_BRIEF_MARKDOWN_BYTES} bytes; trim or attach via metadata reference`);
        }
    }
    return errors;
}
/**
 * Maximum size of {@link WorkItem.briefMarkdown} in UTF-8 bytes.
 *
 * Replaces the legacy `.crewly/tasks/delegated/*.md` files which had no
 * size cap. 16 KB is enough to carry a typical TL-to-worker dispatch
 * brief (~ 3000 words) plus a small spec excerpt; longer briefs should
 * reference an attached file via `metadata` rather than inlining the
 * full body so `pool.json` stays human-readable and `atomicWriteJson`
 * stays fast.
 */
export const MAX_BRIEF_MARKDOWN_BYTES = 16 * 1024;
// ---------------------------------------------------------------------------
// Factory
// ---------------------------------------------------------------------------
/** Default max retries for WorkItems. */
export const DEFAULT_MAX_RETRIES = 3;
/**
 * Creates a new WorkItem with sensible defaults.
 *
 * @param input - Required and optional creation fields
 * @returns A fully populated WorkItem object
 *
 * @example
 * ```typescript
 * const workItem = createWorkItem({
 *   type: 'delegate',
 *   owner: 'agent',
 *   target: 'crewly-product-leo-member-n',
 *   title: 'Implement TaskPoolService',
 * });
 * ```
 */
export function createWorkItem(input) {
    const now = new Date().toISOString();
    const hasDeps = Array.isArray(input.dependsOn) && input.dependsOn.length > 0;
    const hasSchedule = !!input.scheduledAt;
    // Dep gating wins over schedule — a blocked item should not be queued or
    // fired by a cron. The dependency resolver will promote it to 'queued' once
    // all upstream items reach terminal success.
    const initialStatus = hasDeps
        ? 'blocked'
        : hasSchedule
            ? 'scheduled'
            : 'queued';
    return {
        id: input.id ?? uuidv4(),
        requestId: input.requestId,
        parentWorkItemId: input.parentWorkItemId,
        type: input.type,
        owner: input.owner,
        target: input.target,
        title: input.title,
        description: input.description,
        briefMarkdown: input.briefMarkdown,
        status: initialStatus,
        scheduledAt: input.scheduledAt,
        createdAt: now,
        retryCount: 0,
        maxRetries: input.maxRetries ?? DEFAULT_MAX_RETRIES,
        triggerId: input.triggerId,
        projectTaskId: input.projectTaskId,
        missionId: input.missionId,
        inputTokens: 0,
        outputTokens: 0,
        cost: 0,
        metadata: input.metadata,
        dependsOn: hasDeps ? [...input.dependsOn] : undefined,
    };
}
//# sourceMappingURL=work-item.types.js.map