// Copyright (c) 2026 Hellmai Ltd
// SPDX-License-Identifier: AGPL-3.0-only

/**
 * Delegation Registry Schema (ADR-014 follow-up, WU-2763)
 *
 * Canonical delegation schema and helpers for agent-runtime coordination
 * records. Core now re-exports this module as a compatibility shim.
 */

import crypto from 'node:crypto';
import { z } from 'zod';

/**
 * Delegation status values.
 */
const DELEGATION_STATUS_COMPLETED = 'completed';

export const DelegationStatus = {
  PENDING: 'pending',
  COMPLETED: DELEGATION_STATUS_COMPLETED,
  TIMEOUT: 'timeout',
  CRASHED: 'crashed',
  ESCALATED: 'escalated',
} as const;

/** Type for delegation status values */
export type DelegationStatusValue = (typeof DelegationStatus)[keyof typeof DelegationStatus];

/** Array of valid delegation statuses */
export const DELEGATION_STATUSES = [
  DelegationStatus.PENDING,
  DelegationStatus.COMPLETED,
  DelegationStatus.TIMEOUT,
  DelegationStatus.CRASHED,
  DelegationStatus.ESCALATED,
] as const;

/**
 * Optional delegation intent source values.
 */
export const DelegationIntent = {
  DELEGATION: 'delegation',
  LEGACY_SPAWN: 'legacy-spawn',
} as const;

/** Type for delegation intent values */
export type DelegationIntentValue = (typeof DelegationIntent)[keyof typeof DelegationIntent];

/** Array of valid delegation intent values */
export const DELEGATION_INTENTS = [
  DelegationIntent.DELEGATION,
  DelegationIntent.LEGACY_SPAWN,
] as const;

/**
 * Regex patterns for delegation validation.
 */
export const DELEGATION_PATTERNS = {
  DELEGATION_ID: /^dlg-[0-9a-f]{4}$/,
  WU_ID: /^WU-\d+$/,
};

/**
 * Delegation schema version discriminator (WU-2753, ADR-014 extension 1).
 *
 * v1 records have no `schemaVersion` field (legacy compat path).
 * v2 records stamp `schemaVersion: 'v2'` and MUST carry the four ADR-014
 * axes: requested_role, actual_role, capabilities, ownership_scope.
 *
 * Agent-runtime owns the role metadata contract as opaque strings. The
 * software-delivery pack still owns the canonical vocabulary.
 */
export const DELEGATION_SCHEMA_VERSION_V2 = 'v2' as const;

/** Known delegation schema versions (extensible by additive pack updates). */
export const DELEGATION_SCHEMA_VERSIONS = [DELEGATION_SCHEMA_VERSION_V2] as const;

/** Type union for delegation schema version values. */
export type DelegationSchemaVersion = (typeof DELEGATION_SCHEMA_VERSIONS)[number];

/**
 * Error messages for schema validation.
 */
const ERROR_MESSAGES = {
  DELEGATION_ID: 'Delegation ID must match pattern dlg-XXXX (e.g., dlg-a1b2)',
  WU_ID: 'WU ID must match pattern WU-XXX (e.g., WU-1000)',
  LANE_REQUIRED: 'Lane is required',
  STATUS: `Status must be one of: ${DELEGATION_STATUSES.join(', ')}`,
  INTENT: `Intent must be one of: ${DELEGATION_INTENTS.join(', ')}`,
  TIMESTAMP_REQUIRED: 'Timestamp is required',
  PICKUP_BY_REQUIRED: 'pickedUpBy must be a non-empty string when pickup is recorded',
  BRIEF_HASH: 'briefAttestation.promptHash must be a 64-character lowercase sha256 hex digest',
  BRIEF_ALGORITHM: 'briefAttestation.algorithm must be sha256',
  BRIEF_CLIENT_REQUIRED: 'briefAttestation.clientName must be a non-empty string',
} as const;

const SHA256_HEX_256_REGEX = /^[a-f0-9]{64}$/;

/**
 * Optional wu:brief prompt attestation captured by wu:delegate.
 */
export const DelegationBriefAttestationSchema = z.object({
  algorithm: z.literal('sha256', { error: ERROR_MESSAGES.BRIEF_ALGORITHM }),
  promptHash: z.string().regex(SHA256_HEX_256_REGEX, { message: ERROR_MESSAGES.BRIEF_HASH }),
  promptLength: z.number().int().nonnegative(),
  generatedAt: z.string().datetime({ message: ERROR_MESSAGES.TIMESTAMP_REQUIRED }),
  clientName: z.string().min(1, { message: ERROR_MESSAGES.BRIEF_CLIENT_REQUIRED }),
});

/**
 * ADR-014 role descriptor — agent-runtime carries opaque strings; the
 * software-delivery pack owns the AgentLifecycleRole vocabulary.
 */
export const DelegationRoleDescriptorSchema = z.object({
  lifecycle_role: z.string().min(1, { message: 'lifecycle_role must be non-empty' }),
  specialty_profile: z.string().min(1, { message: 'specialty_profile must be non-empty' }),
  role_alias: z.string().min(1).optional(),
  capabilities: z.array(z.string()),
});

/**
 * ADR-014 ownership scope for agent coordination.
 */
export const DelegationOwnershipScopeSchema = z.object({
  initiative_id: z.string().min(1).optional(),
  wu_ids: z.array(z.string().min(1)).optional(),
  lane: z.string().min(1).optional(),
  responsibility_kinds: z.array(z.string().min(1)),
});

/**
 * Delegation event base schema (shared by v1-compat and v2).
 */
const DelegationEventBaseSchema = z.object({
  id: z.string().regex(DELEGATION_PATTERNS.DELEGATION_ID, {
    message: ERROR_MESSAGES.DELEGATION_ID,
  }),
  parentWuId: z.string().regex(DELEGATION_PATTERNS.WU_ID, { message: ERROR_MESSAGES.WU_ID }),
  targetWuId: z.string().regex(DELEGATION_PATTERNS.WU_ID, { message: ERROR_MESSAGES.WU_ID }),
  lane: z.string().min(1, { message: ERROR_MESSAGES.LANE_REQUIRED }),
  intent: z.enum(DELEGATION_INTENTS, { error: ERROR_MESSAGES.INTENT }).optional(),
  delegatedAt: z.string().datetime({ message: ERROR_MESSAGES.TIMESTAMP_REQUIRED }),
  status: z.enum(DELEGATION_STATUSES, { error: ERROR_MESSAGES.STATUS }),
  completedAt: z.string().datetime().nullable(),
  pickedUpAt: z.string().datetime().optional(),
  pickedUpBy: z.string().min(1, { message: ERROR_MESSAGES.PICKUP_BY_REQUIRED }).optional(),
  briefAttestation: DelegationBriefAttestationSchema.optional(),
});

/**
 * Legacy v1-compat delegation event schema.
 */
const DelegationEventV1Schema = DelegationEventBaseSchema.extend({
  schemaVersion: z.undefined().optional(),
  requested_role: DelegationRoleDescriptorSchema.optional(),
  actual_role: DelegationRoleDescriptorSchema.optional(),
  capabilities: z.array(z.string()).optional(),
  ownership_scope: DelegationOwnershipScopeSchema.optional(),
});

/**
 * v2 delegation event schema (WU-2753, ADR-014 extension 1).
 */
const DelegationEventV2Schema = DelegationEventBaseSchema.extend({
  schemaVersion: z.literal(DELEGATION_SCHEMA_VERSION_V2),
  requested_role: DelegationRoleDescriptorSchema,
  actual_role: DelegationRoleDescriptorSchema,
  capabilities: z.array(z.string()),
  ownership_scope: DelegationOwnershipScopeSchema,
});

/**
 * Delegation event schema — discriminated union across v1-compat and v2.
 */
export const DelegationEventSchema = z.union([DelegationEventV2Schema, DelegationEventV1Schema]);

/** TypeScript type inferred from schema */
export type DelegationEvent = z.infer<typeof DelegationEventSchema>;
export type DelegationEventV1 = z.infer<typeof DelegationEventV1Schema>;
export type DelegationEventV2 = z.infer<typeof DelegationEventV2Schema>;
export type DelegationBriefAttestation = z.infer<typeof DelegationBriefAttestationSchema>;
export type DelegationRoleDescriptor = z.infer<typeof DelegationRoleDescriptorSchema>;
export type DelegationOwnershipScope = z.infer<typeof DelegationOwnershipScopeSchema>;

/**
 * Bundle of the four ADR-014 axes that launch receipts and wu:delegate emit
 * on every new delegation (WU-2753).
 */
export interface DelegationRoleContract {
  requested_role: DelegationRoleDescriptor;
  actual_role: DelegationRoleDescriptor;
  capabilities: string[];
  ownership_scope: DelegationOwnershipScope;
}

/**
 * Validates delegation event data against schema.
 */
export function validateDelegationEvent(data: unknown) {
  return DelegationEventSchema.safeParse(data);
}

/**
 * Returns true if the delegation event carries the v2 role contract.
 */
export function isV2DelegationEvent(event: DelegationEvent): event is DelegationEventV2 {
  return event.schemaVersion === DELEGATION_SCHEMA_VERSION_V2;
}

/**
 * Generates a unique delegation ID.
 */
export function generateDelegationId(parentWuId: string, targetWuId: string): string {
  const timestamp = Date.now().toString();
  const randomBytes = crypto.randomBytes(4).toString('hex');
  const input = `${parentWuId}:${targetWuId}:${timestamp}:${randomBytes}`;
  const hash = crypto.createHash('sha256').update(input).digest('hex');
  return `dlg-${hash.slice(0, 4)}`;
}
