/**
 * V2 Mission & MissionPolicy Type Definitions
 *
 * A Mission represents a team's long-term objective. Missions drive
 * autonomous work independent of user Requests. MissionPolicy governs
 * what a team can do autonomously under a Mission.
 *
 * @module types/v2/mission.types
 */
import { v4 as uuidv4 } from 'uuid';
/** All valid MissionStatus values. */
export const MISSION_STATUSES = [
    'active',
    'paused',
    'completed',
    'cancelled',
];
/** Terminal mission statuses. */
export const TERMINAL_MISSION_STATUSES = new Set([
    'completed',
    'cancelled',
]);
/** Valid mission status transitions. */
export const MISSION_TRANSITIONS = {
    active: new Set(['paused', 'completed', 'cancelled']),
    paused: new Set(['active', 'cancelled']),
    completed: new Set(),
    cancelled: new Set(),
};
/** All valid MissionLevel values, ordered top → bottom of the cascade. */
export const MISSION_LEVELS = ['company', 'team', 'project'];
/**
 * Numeric depth of each cascade tier (company = 0, team = 1, project = 2).
 * Used to enforce the "child is exactly one tier below parent" rule.
 */
export const MISSION_LEVEL_DEPTH = {
    company: 0,
    team: 1,
    project: 2,
};
/** Depth threshold at or beyond which a mission is treated as `project` level. */
export const PROJECT_LEVEL_DEPTH = 2;
/**
 * Checks whether a string is a valid MissionLevel.
 *
 * @param value - Candidate string
 * @returns `true` if `value` is one of `company` | `team` | `project`
 */
export function isValidMissionLevel(value) {
    return MISSION_LEVELS.includes(value);
}
/**
 * Validates a single downward cascade step: a child level may only sit exactly
 * one tier below its parent, and `company` must be a root (no parent).
 *
 * Adjacency matrix:
 * | child   | required parent | parentMissionId   |
 * |---------|-----------------|-------------------|
 * | company | —               | must be undefined |
 * | team    | company         | required          |
 * | project | team            | required          |
 *
 * @param childLevel - The level of the child mission
 * @param parentLevel - The level of the parent mission, or `undefined` if no parent
 * @returns `null` if the pairing is legal, otherwise a human-readable reason
 *
 * @example
 * ```ts
 * validateLevelLink('team', 'company'); // null
 * validateLevelLink('company', 'team'); // "A company mission cannot have a parent"
 * ```
 */
export function validateLevelLink(childLevel, parentLevel) {
    if (childLevel === 'company') {
        return parentLevel === undefined
            ? null
            : 'A company mission cannot have a parent';
    }
    if (parentLevel === undefined) {
        return `A ${childLevel} mission requires a parent mission`;
    }
    const expectedDepth = MISSION_LEVEL_DEPTH[childLevel] - 1;
    if (MISSION_LEVEL_DEPTH[parentLevel] !== expectedDepth) {
        return `A ${childLevel} mission must have a ${MISSION_LEVELS[expectedDepth]} parent, not a ${parentLevel} parent`;
    }
    return null;
}
/** All valid ProposalState values. */
export const PROPOSAL_STATES = [
    'draft',
    'pending_approval',
    'approved',
    'rejected',
];
/**
 * Legal proposal-state transitions.
 *
 * - draft → pending_approval
 * - pending_approval → approved | rejected
 * - approved → (terminal)
 * - rejected → draft (owner can ask for a redraft)
 */
export const PROPOSAL_TRANSITIONS = {
    draft: new Set(['pending_approval']),
    pending_approval: new Set(['approved', 'rejected']),
    approved: new Set(),
    rejected: new Set(['draft']),
};
/**
 * Checks whether a string is a valid ProposalState.
 *
 * @param value - Candidate string
 * @returns `true` if `value` is one of the four proposal states
 */
export function isValidProposalState(value) {
    return PROPOSAL_STATES.includes(value);
}
/**
 * Checks whether a proposal-state transition is legal.
 *
 * @param from - Current proposal state
 * @param to - Proposed next proposal state
 * @returns `true` if `from → to` is an allowed transition
 *
 * @example
 * ```ts
 * isValidProposalTransition('pending_approval', 'approved'); // true
 * isValidProposalTransition('approved', 'draft');            // false
 * ```
 */
export function isValidProposalTransition(from, to) {
    return PROPOSAL_TRANSITIONS[from].has(to);
}
/** All valid MissionPriority values (ordered most → least important). */
export const MISSION_PRIORITIES = [
    'critical',
    'high',
    'medium',
    'low',
];
/**
 * Numeric rank for sorting missions by priority (lower rank = higher priority).
 * Missing priority is treated as `low`.
 */
export const MISSION_PRIORITY_RANK = {
    critical: 0,
    high: 1,
    medium: 2,
    low: 3,
};
/** All valid escalation conditions. */
export const ESCALATION_CONDITIONS = [
    'cost_exceeded',
    'time_exceeded',
    'failure_count',
    'scope_change',
    'security_concern',
];
/** All valid PhaseGateApproval values. */
export const PHASE_GATE_APPROVALS = [
    'none',
    'team_lead',
    'human',
];
// ---------------------------------------------------------------------------
// Default ExecutionCadence values
// ---------------------------------------------------------------------------
/**
 * Conservative cadence — slow, human-gated, business hours only.
 */
export const CONSERVATIVE_CADENCE = {
    reviewSchedule: '0 9 * * 1',
    dailyItemLimit: 3,
    workHours: {
        startHour: 9,
        endHour: 17,
        timezone: 'UTC',
        activeDays: [1, 2, 3, 4, 5],
    },
    phaseGateApproval: 'human',
    requireVerificationGate: true,
};
/**
 * Moderate cadence — steady flow, TL-gated phases, extended hours.
 */
export const MODERATE_CADENCE = {
    reviewSchedule: '0 9 * * 1,4',
    dailyItemLimit: 10,
    workHours: {
        startHour: 8,
        endHour: 20,
        timezone: 'UTC',
        activeDays: [1, 2, 3, 4, 5],
    },
    phaseGateApproval: 'team_lead',
    requireVerificationGate: true,
};
/**
 * Autonomous cadence — fast, self-advancing, 24/7.
 */
export const AUTONOMOUS_CADENCE = {
    reviewSchedule: '0 9 * * *',
    dailyItemLimit: 0,
    workHours: null,
    phaseGateApproval: 'none',
    requireVerificationGate: false,
};
/** All valid policy actions. */
export const POLICY_ACTIONS = [
    'create_task',
    'reprioritize_task',
    'close_task',
    'deploy_staging',
    'deploy_prod',
    'spend_money',
    'change_user_visible_behavior',
    'replan_mission',
    'advance_phase',
    'adjust_kr_target',
];
/**
 * Maps PolicyAction to the corresponding MissionPolicy boolean field.
 */
export const ACTION_TO_POLICY_FIELD = {
    create_task: 'canCreateTasks',
    reprioritize_task: 'canReprioritizeTasks',
    close_task: 'canCloseTasks',
    deploy_staging: 'canDeployToStaging',
    deploy_prod: 'canDeployToProd',
    spend_money: 'canSpendMoney',
    change_user_visible_behavior: 'canChangeUserVisibleBehaviorWithoutReview',
    replan_mission: 'canReplanMission',
    advance_phase: 'canAdvancePhase',
    adjust_kr_target: 'canAdjustKRTargets',
};
// ---------------------------------------------------------------------------
// Default Policies (Templates)
// ---------------------------------------------------------------------------
/**
 * Conservative policy — all capabilities disabled, human approval for everything.
 */
export const CONSERVATIVE_POLICY = {
    canCreateTasks: false,
    canReprioritizeTasks: false,
    canCloseTasks: false,
    canDeployToStaging: false,
    canDeployToProd: false,
    canSpendMoney: false,
    canChangeUserVisibleBehaviorWithoutReview: false,
    maxParallelExecutions: 1,
    escalationRules: [],
    executionCadence: CONSERVATIVE_CADENCE,
};
/**
 * Moderate policy — can create and close tasks, but no deploy or spend.
 */
export const MODERATE_POLICY = {
    canCreateTasks: true,
    canReprioritizeTasks: true,
    canCloseTasks: true,
    canDeployToStaging: false,
    canDeployToProd: false,
    canSpendMoney: false,
    canChangeUserVisibleBehaviorWithoutReview: false,
    maxParallelExecutions: 3,
    escalationRules: [
        { condition: 'failure_count', threshold: 3, escalateTo: 'team_lead', action: 'notify' },
    ],
    executionCadence: MODERATE_CADENCE,
};
/**
 * Autonomous policy — most capabilities enabled, but no prod deploy.
 */
export const AUTONOMOUS_POLICY = {
    canCreateTasks: true,
    canReprioritizeTasks: true,
    canCloseTasks: true,
    canDeployToStaging: true,
    canDeployToProd: false,
    canSpendMoney: true,
    canChangeUserVisibleBehaviorWithoutReview: false,
    maxParallelExecutions: 5,
    escalationRules: [
        { condition: 'cost_exceeded', threshold: 50, escalateTo: 'user', action: 'pause' },
        { condition: 'failure_count', threshold: 5, escalateTo: 'team_lead', action: 'notify' },
        { condition: 'security_concern', threshold: 1, escalateTo: 'user', action: 'block' },
    ],
    executionCadence: AUTONOMOUS_CADENCE,
};
// ---------------------------------------------------------------------------
// Type Guards
// ---------------------------------------------------------------------------
/**
 * Checks whether a string is a valid MissionStatus.
 */
export function isValidMissionStatus(value) {
    return MISSION_STATUSES.includes(value);
}
/**
 * Checks whether a string is a valid PolicyAction.
 */
export function isValidPolicyAction(value) {
    return POLICY_ACTIONS.includes(value);
}
/**
 * Checks whether a string is a valid EscalationCondition.
 */
export function isValidEscalationCondition(value) {
    return ESCALATION_CONDITIONS.includes(value);
}
/**
 * Checks whether a mission status transition is valid.
 */
export function isValidMissionTransition(from, to) {
    return MISSION_TRANSITIONS[from].has(to);
}
/**
 * Validates an EscalationRule.
 */
export function isValidEscalationRule(rule) {
    if (typeof rule !== 'object' || rule === null)
        return false;
    const obj = rule;
    return (typeof obj.condition === 'string' &&
        isValidEscalationCondition(obj.condition) &&
        typeof obj.threshold === 'number' &&
        obj.threshold >= 0 &&
        typeof obj.escalateTo === 'string' &&
        ['user', 'orchestrator', 'team_lead'].includes(obj.escalateTo) &&
        typeof obj.action === 'string' &&
        ['pause', 'notify', 'block'].includes(obj.action));
}
/**
 * Checks whether a string is a valid PhaseGateApproval.
 */
export function isValidPhaseGateApproval(value) {
    return PHASE_GATE_APPROVALS.includes(value);
}
/**
 * Validates a WorkHoursWindow object.
 */
export function isValidWorkHoursWindow(value) {
    if (typeof value !== 'object' || value === null)
        return false;
    const obj = value;
    return (typeof obj.startHour === 'number' &&
        Number.isInteger(obj.startHour) &&
        obj.startHour >= 0 && obj.startHour <= 23 &&
        typeof obj.endHour === 'number' &&
        Number.isInteger(obj.endHour) &&
        obj.endHour >= 0 && obj.endHour <= 23 &&
        typeof obj.timezone === 'string' &&
        obj.timezone.length > 0 &&
        Array.isArray(obj.activeDays) &&
        obj.activeDays.every((d) => typeof d === 'number' && Number.isInteger(d) && d >= 0 && d <= 6));
}
/**
 * Validates an ExecutionCadence object.
 */
export function isValidExecutionCadence(value) {
    if (typeof value !== 'object' || value === null)
        return false;
    const obj = value;
    return (typeof obj.reviewSchedule === 'string' &&
        obj.reviewSchedule.length > 0 &&
        typeof obj.dailyItemLimit === 'number' &&
        Number.isInteger(obj.dailyItemLimit) &&
        obj.dailyItemLimit >= 0 &&
        (obj.workHours === null || isValidWorkHoursWindow(obj.workHours)) &&
        typeof obj.phaseGateApproval === 'string' &&
        isValidPhaseGateApproval(obj.phaseGateApproval) &&
        typeof obj.requireVerificationGate === 'boolean');
}
/**
 * Validates a MissionPolicy object.
 */
export function isValidMissionPolicy(value) {
    if (typeof value !== 'object' || value === null)
        return false;
    const obj = value;
    const baseValid = (typeof obj.missionId === 'string' &&
        typeof obj.canCreateTasks === 'boolean' &&
        typeof obj.canReprioritizeTasks === 'boolean' &&
        typeof obj.canCloseTasks === 'boolean' &&
        typeof obj.canDeployToStaging === 'boolean' &&
        typeof obj.canDeployToProd === 'boolean' &&
        typeof obj.canSpendMoney === 'boolean' &&
        typeof obj.canChangeUserVisibleBehaviorWithoutReview === 'boolean' &&
        typeof obj.maxParallelExecutions === 'number' &&
        obj.maxParallelExecutions > 0 &&
        Array.isArray(obj.escalationRules));
    if (!baseValid)
        return false;
    // executionCadence is optional; validate only if present
    if (obj.executionCadence !== undefined && !isValidExecutionCadence(obj.executionCadence)) {
        return false;
    }
    return true;
}
/**
 * Validates that an unknown value is structurally a valid Mission.
 */
export function isMission(value) {
    if (typeof value !== 'object' || value === null)
        return false;
    const obj = value;
    return (typeof obj.id === 'string' &&
        typeof obj.objective === 'string' &&
        typeof obj.ownerTeamId === 'string' &&
        typeof obj.status === 'string' &&
        isValidMissionStatus(obj.status) &&
        typeof obj.createdAt === 'string' &&
        Array.isArray(obj.successCriteria) &&
        Array.isArray(obj.activeProjectTaskIds));
}
// ---------------------------------------------------------------------------
// Factory
// ---------------------------------------------------------------------------
/**
 * Creates a default MissionPolicy for a given mission.
 * Defaults to conservative (all disabled).
 *
 * @param missionId - The mission this policy applies to
 * @param template - Optional template: 'conservative', 'moderate', 'autonomous'
 * @returns A fully populated MissionPolicy
 */
export function createMissionPolicy(missionId, template = 'conservative') {
    const templates = {
        conservative: CONSERVATIVE_POLICY,
        moderate: MODERATE_POLICY,
        autonomous: AUTONOMOUS_POLICY,
    };
    return {
        missionId,
        ...templates[template],
        // Deep copy escalation rules to avoid shared references
        escalationRules: [...templates[template].escalationRules.map(r => ({ ...r }))],
    };
}
/**
 * Creates a new Mission with sensible defaults.
 *
 * @param input - Required and optional creation fields
 * @returns A fully populated Mission object
 */
export function createMission(input) {
    const now = new Date().toISOString();
    const id = uuidv4();
    const reviewSchedule = input.cadence ?? '0 9 * * 1';
    const basePolicy = createMissionPolicy(id, 'conservative');
    const mergedPolicy = {
        ...basePolicy,
        ...input.policy,
        missionId: id,
        // Deep copy escalation rules from the merged result
        escalationRules: [...(input.policy?.escalationRules ?? basePolicy.escalationRules).map(r => ({ ...r }))],
    };
    // Sync reviewSchedule into executionCadence if caller provided cadence but no executionCadence
    if (!mergedPolicy.executionCadence) {
        mergedPolicy.executionCadence = { ...CONSERVATIVE_CADENCE, reviewSchedule };
    }
    else if (input.cadence && !input.policy?.executionCadence?.reviewSchedule) {
        mergedPolicy.executionCadence = {
            ...mergedPolicy.executionCadence,
            reviewSchedule,
        };
    }
    return {
        id,
        objective: input.objective,
        ownerTeamId: input.ownerTeamId,
        successCriteria: input.successCriteria,
        currentStrategy: input.currentStrategy,
        activeProjectTaskIds: [],
        cadence: reviewSchedule,
        policy: mergedPolicy,
        status: 'active',
        ownerId: input.ownerId,
        createdAt: now,
        updatedAt: now,
        learnings: [],
        period: input.period,
        parentMissionId: input.parentMissionId,
        level: input.level ?? (input.parentMissionId ? undefined : 'company'),
        projectId: input.projectId,
        approval: input.approval,
        priority: input.priority,
    };
}
// ---------------------------------------------------------------------------
// ExecutionCadence Helpers
// ---------------------------------------------------------------------------
/**
 * Returns the effective ExecutionCadence for a mission policy.
 * Falls back to CONSERVATIVE_CADENCE when no cadence is defined.
 *
 * @param policy - The MissionPolicy to read from
 * @returns Resolved ExecutionCadence (never undefined)
 */
export function getEffectiveCadence(policy) {
    return policy.executionCadence ?? { ...CONSERVATIVE_CADENCE };
}
/**
 * Merges a partial cadence update into an existing ExecutionCadence.
 * Deep-merges workHours if both exist. Pass workHours: null to clear.
 *
 * @param existing - Current cadence
 * @param updates - Partial fields to merge
 * @returns New merged ExecutionCadence (does not mutate inputs)
 */
export function mergeExecutionCadence(existing, updates) {
    return {
        ...existing,
        ...updates,
        workHours: updates.workHours === null
            ? null
            : updates.workHours !== undefined
                ? {
                    ...(existing.workHours ?? { startHour: 9, endHour: 17, timezone: 'UTC', activeDays: [1, 2, 3, 4, 5] }),
                    ...updates.workHours,
                }
                : existing.workHours,
    };
}
export function isPeriodActive(period, now = new Date()) {
    return now >= new Date(period.startDate) && now < new Date(period.endDate);
}
export function isPeriodPast(period, now = new Date()) {
    return now >= new Date(period.endDate);
}
export function isPeriodFuture(period, now = new Date()) {
    return now < new Date(period.startDate);
}
/**
 * Build a `monthly` MissionPeriod for a given calendar month.
 *
 * The period is half-open `[first day of month, first day of next month)`,
 * matching the `now >= start && now < end` convention used by
 * {@link isPeriodActive}. Dates are UTC so the boundaries are deterministic
 * regardless of server timezone. Handles December → January year rollover.
 *
 * @param year - Full year, e.g. `2026`.
 * @param month - 1-based month (`1` = January … `12` = December).
 * @returns A `monthly` MissionPeriod with ISO8601 start/end and a `YYYY-MM` label.
 *
 * @example
 * ```typescript
 * createMonthlyPeriod(2026, 3); // March 2026: 2026-03-01 → 2026-04-01, label "2026-03"
 * ```
 */
export function createMonthlyPeriod(year, month) {
    const start = new Date(Date.UTC(year, month - 1, 1));
    // month is 1-based, so passing it as the 0-based Date month index yields the
    // first day of the *next* month (Date normalizes 12 → January of year+1).
    const end = new Date(Date.UTC(year, month, 1));
    return {
        type: 'monthly',
        startDate: start.toISOString(),
        endDate: end.toISOString(),
        label: `${year}-${String(month).padStart(2, '0')}`,
    };
}
// ---------------------------------------------------------------------------
// Mission Hierarchy Helpers
// ---------------------------------------------------------------------------
/**
 * Validates a proposed parent link between a child mission and a parent.
 *
 * Rejects self-reference and any cycle that would be introduced by walking
 * up the existing parent chain from `parentId`. The child mission itself
 * does not need to exist in `byId` (useful for create-time validation).
 *
 * @param childId - The mission that would acquire the parent
 * @param parentId - The proposed parent mission ID
 * @param byId - All existing missions indexed by ID
 * @returns `null` if the link is valid, otherwise a human-readable reason
 *
 * @example
 * ```ts
 * const reason = validateParentLink('m1', 'm2', new Map([['m2', m2]]));
 * if (reason) throw new Error(reason);
 * ```
 */
export function validateParentLink(childId, parentId, byId) {
    if (childId === parentId)
        return 'A mission cannot be its own parent';
    if (!byId.has(parentId))
        return `Parent mission ${parentId} does not exist`;
    // Walk up from the proposed parent; if we reach `childId`, we have a cycle.
    const seen = new Set();
    let cursor = parentId;
    while (cursor) {
        if (seen.has(cursor))
            return `Parent chain contains a cycle at ${cursor}`;
        if (cursor === childId)
            return 'Proposed parent link would create a cycle';
        seen.add(cursor);
        cursor = byId.get(cursor)?.parentMissionId;
    }
    return null;
}
/**
 * Derives a Mission's cascade level by walking its `parentMissionId` chain.
 *
 * Depth 0 (no parent) ⇒ `company`, depth 1 ⇒ `team`, depth ≥ 2 ⇒ `project`.
 * Used to backfill `level` for legacy missions persisted before the cascade
 * fields existed. Guards against cycles defensively (returns the level computed
 * at the point a repeat is detected rather than looping forever).
 *
 * @param missionId - The mission whose level to derive
 * @param byId - All missions indexed by ID (need only `id` + `parentMissionId`)
 * @returns The derived MissionLevel
 *
 * @example
 * ```ts
 * deriveLevel('teamM', new Map([['teamM', { id: 'teamM', parentMissionId: 'co' }], ['co', { id: 'co' }]])); // 'team'
 * ```
 */
export function deriveLevel(missionId, byId) {
    let depth = 0;
    const seen = new Set();
    let cursor = byId.get(missionId)?.parentMissionId;
    while (cursor && depth < PROJECT_LEVEL_DEPTH) {
        if (seen.has(cursor))
            break;
        seen.add(cursor);
        depth += 1;
        cursor = byId.get(cursor)?.parentMissionId;
    }
    if (depth === 0)
        return 'company';
    if (depth === 1)
        return 'team';
    return 'project';
}
/**
 * Resolve a mission's effective cascade level through a SINGLE canonical rule,
 * shared by every read path (cascade service, mission routes' read-migration).
 *
 * Prefers the mission's explicit `level`; when absent (legacy missions persisted
 * before the cascade fields existed) it falls back to {@link deriveLevel}, which
 * walks the `parentMissionId` chain (no parent ⇒ `company`, one parent ⇒ `team`,
 * two-or-more ⇒ `project`). Centralising the fallback here guarantees the same
 * document resolves to the same level no matter which code path reads it.
 *
 * @param mission - The mission whose level to resolve (needs `id`, optional
 *   `level` + `parentMissionId`)
 * @param byId - All missions indexed by ID for the parent-chain walk
 * @returns The resolved {@link MissionLevel}
 *
 * @example
 * ```ts
 * resolveMissionLevel({ id: 'co', }, byId);                 // 'company' (no parent)
 * resolveMissionLevel({ id: 't', parentMissionId: 'co' }, byId); // 'team'
 * ```
 */
export function resolveMissionLevel(mission, byId) {
    if (mission.level)
        return mission.level;
    return deriveLevel(mission.id, byId);
}
/**
 * Combined cascade-link guard: runs the raw cycle/self-ref check
 * ({@link validateParentLink}) AND the level-adjacency check
 * ({@link validateLevelLink}). The parent's level is read from `byId` (its own
 * `level` field, falling back to {@link deriveLevel} when absent).
 *
 * `validateParentLink` keeps its original signature for callers that only need
 * the cycle check.
 *
 * @param childId - The mission that would acquire the parent
 * @param childLevel - The proposed level of the child mission
 * @param parentId - The proposed parent mission ID, or `undefined` for a root
 * @param byId - All existing missions indexed by ID
 * @returns `null` if the link is valid, otherwise a human-readable reason
 *
 * @example
 * ```ts
 * const reason = validateCascadeLink('p1', 'project', 't1', byId);
 * if (reason) throw new Error(reason);
 * ```
 */
export function validateCascadeLink(childId, childLevel, parentId, byId) {
    if (parentId === undefined) {
        return validateLevelLink(childLevel, undefined);
    }
    const cycleReason = validateParentLink(childId, parentId, byId);
    if (cycleReason)
        return cycleReason;
    const parent = byId.get(parentId);
    const parentLevel = parent?.level ?? deriveLevel(parentId, byId);
    return validateLevelLink(childLevel, parentLevel);
}
/**
 * Sort missions by status (active first), then by priority rank, then by recency.
 *
 * @param missions - Missions to sort (not mutated)
 * @returns New array sorted by status → priority → createdAt desc
 */
export function sortMissionsByPriority(missions) {
    return [...missions].sort((a, b) => {
        if (a.status === 'active' && b.status !== 'active')
            return -1;
        if (a.status !== 'active' && b.status === 'active')
            return 1;
        const rankA = MISSION_PRIORITY_RANK[a.priority ?? 'low'];
        const rankB = MISSION_PRIORITY_RANK[b.priority ?? 'low'];
        if (rankA !== rankB)
            return rankA - rankB;
        return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
    });
}
//# sourceMappingURL=mission.types.js.map