/*
 * Priority Forge — memory-hub integration
 * Vendor-neutral interface for cross-team task-state sync.
 *
 * The MemoryHub interface is the ONLY contract Priority Forge depends on.
 * XMem is one impl; noop and dryrun ship alongside it. A future self-hosted
 * Postgres impl drops in without touching any PF code outside this directory.
 */

export type MemoryHubBackend = 'noop' | 'dryrun' | 'xmem' | 'pgvector';

export type TeamMemoryEventType =
  | 'task_created'
  | 'task_selected'
  | 'task_updated'
  | 'task_completed'
  | 'context_switch'
  | 'project_created'
  | 'project_updated'
  | 'derived_blocker'
  // Orchestration plane (cross-forge SharedTask lifecycle). Observation
  // events ONLY — the SharedTask record itself lives separately, accessed
  // via OrchestrationHub. These flow through the same TeamMemoryEvent
  // channel so the reader/subscriber pipeline absorbs them uniformly.
  | 'shared_task_published'
  | 'shared_task_claimed'
  | 'shared_task_released'
  | 'shared_task_lease_renewed'
  | 'shared_task_completed'
  | 'shared_task_cancelled'
  // A lead's advisory priority directive for a SharedTask. The owning forge
  // surfaces it (get_priority_directives) and applies it in place to the local
  // projection — logged as a normal priority change. NOT auto-applied by the hub.
  | 'priority_directive';

export interface TeamMemoryEvent {
  eventId: string;
  teamId: string;          // → user_id when XMem is the backend
  agentId: string;         // → agent_id (forge owner)
  workspaceId: string;
  eventType: TeamMemoryEventType;
  timestamp: string;
  payload: Record<string, unknown>;                                          // structured PF event payload
  summary: string;                                                            // natural-language one-liner for semantic search
  metadata: Record<string, string | number | boolean | string[] | null>;
}

export interface BlockerEdge {
  blockedTaskId: string;
  blockedAgentId: string;
  blockingTaskId: string;
  blockingAgentId: string;
  status: 'active' | 'cleared';
  observedAt: string;
}

/*
 * MinimalTask is the ONLY task shape the hub package depends on. It MUST
 * stay narrow: identity + dependency edges + claim. Adding priority,
 * effort, notes, weights, startedAt, actualWorkTime, or any other
 * task-content or learning-signal field would violate the hub-boundary
 * guardrail (see root CLAUDE.md) — task content lives in PF, never here.
 */
export interface MinimalTask {
  id: string;
  status: string;
  dependencies?: string[];
  blocking?: string;
  claimedBy?: string;
}

/*
 * Providers injected by the host (Priority Forge) at integration construction.
 * The hub package never reads PF storage or sync config directly; it asks
 * its providers. This is the seam that lets the hub be used by any host.
 */
export interface TaskProvider {
  listTasks(): Promise<MinimalTask[]>;
  getCurrentWorkspaceId(): Promise<string>;
}

export interface TeamActivityFilter {
  teamId: string;
  sinceTs?: string;
  agentIds?: string[];
  workspaceIds?: string[];
  eventTypes?: TeamMemoryEventType[];
  limit?: number;
  /** When true, include events with metadata.tombstonedAt set. */
  includeTombstoned?: boolean;
}

export interface MemoryHubHealth {
  ok: boolean;
  backend: MemoryHubBackend;
  detail?: string;
}

export interface MemoryHub {
  readonly backend: MemoryHubBackend;
  write(event: TeamMemoryEvent): Promise<{ accepted: boolean; remoteId?: string }>;
  read(filter: TeamActivityFilter): Promise<TeamMemoryEvent[]>;
  search(teamId: string, query: string, limit?: number): Promise<TeamMemoryEvent[]>;
  blockersFor(teamId: string, agentId: string): Promise<BlockerEdge[]>;
  health(): Promise<MemoryHubHealth>;
}

/*
 * ─── Orchestration plane ───────────────────────────────────────────────
 *
 * SharedTask is the cross-forge analog of TeamMemoryEvent: a hub-canonical
 * entity that holds cross-forge identity, the active lease, and an
 * append-only audit. The SAME hub-boundary guardrail applies as for
 * MemoryHub — NO task content (priority, effort, notes, body), NO learning
 * signals (selections, skips, weights, work duration). The hub knows only
 * what's needed to coordinate cross-forge work; the actual task body lives
 * in whichever local PF projection the lease-holder materializes on claim.
 *
 * Concretely: the SharedTask carries `title`, `description`, `deadline`, and an
 * advisory `priority` (frozen at publish/create time) as the seeds for
 * projection. `deadline` and `priority` are advisory — the claimer's forge
 * recomputes time-sensitivity and heuristic weights locally, and may override
 * the priority. Everything else (effort, work duration, ranker signals, the
 * forge's computed priority + change history) is per-forge per-claim and stays
 * in the host's PF storage with a `sharedTaskId` back-link.
 */

export interface ClaimInfo {
  holderAgentId: string;
  holderTeamId: string;
  claimedAt: string;     // ISO timestamp
  leaseUntil: string;    // ISO timestamp
}

export type ClaimHistoryEvent = 'claimed' | 'released' | 'renewed' | 'expired';
export type ClaimOutcome = 'completed' | 'deferred' | 'cancelled' | 'handed_back';

export interface ClaimHistoryEntry {
  event: ClaimHistoryEvent;
  agentId: string;
  agentTeamId: string;
  at: string;            // ISO timestamp
  outcome?: ClaimOutcome;
}

export type SharedTaskStatus = 'claimable' | 'claimed' | 'completed' | 'cancelled';

export interface SharedTask {
  id: string;
  teamId: string;
  title: string;
  description?: string;
  deadline?: string;
  priority?: string;
  originatorAgentId: string;
  originatorTeamId: string;
  createdAt: string;
  currentClaim: ClaimInfo | null;
  claimHistory: ClaimHistoryEntry[];
  status: SharedTaskStatus;
}

export type ClaimResult =
  | { ok: true; sharedTask: SharedTask; claim: ClaimInfo }
  | { ok: false; reason: 'already_claimed'; currentClaim: ClaimInfo }
  | { ok: false; reason: 'not_found' }
  | { ok: false; reason: 'completed' | 'cancelled' }
  | { ok: false; reason: 'unsupported_backend' }
  | { ok: false; reason: 'unauthorized_agent' };

export interface ReleaseResult {
  ok: boolean;
  sharedTask?: SharedTask;
  reason?:
    | 'not_claim_holder'
    | 'not_found'
    | 'unsupported_backend'
    | 'not_currently_claimed'
    | 'unauthorized_agent';
}

export interface RenewResult {
  ok: boolean;
  claim?: ClaimInfo;
  reason?:
    | 'not_claim_holder'
    | 'not_found'
    | 'expired'
    | 'unsupported_backend'
    | 'unauthorized_agent';
}

export interface CancelResult {
  ok: boolean;
  sharedTask?: SharedTask;
  reason?:
    | 'not_found'
    // Only the originator may cancel their own claimable task.
    | 'unauthorized_originator'
    // Someone holds a lease — they must release; cancel is for unclaimed tasks.
    | 'currently_claimed'
    // Already completed or cancelled.
    | 'already_terminal'
    | 'unsupported_backend'
    | 'unauthorized_agent';
}

export interface SharedTaskFilter {
  teamId: string;
  status?: SharedTaskStatus;
  limit?: number;
}

export interface CreateSharedTaskInput {
  teamId: string;
  title: string;
  description?: string;
  deadline?: string;
  priority?: string;
  originatorAgentId: string;
  originatorTeamId: string;
}

/*
 * Adapters implement BOTH MemoryHub and OrchestrationHub on the same
 * backend class so a single backend instance serves both planes.
 * `supportsOrchestration` lets hosts probe whether the active backend can
 * arbitrate claims: noop + xmem (today) → false; dryrun → true (in-process
 * advisory); pgvector → true (atomic CAS via SQL).
 */
export interface OrchestrationHub {
  readonly supportsOrchestration: boolean;

  create(input: CreateSharedTaskInput): Promise<SharedTask>;
  claim(sharedTaskId: string, agentId: string, agentTeamId: string, ttlMs: number): Promise<ClaimResult>;
  release(sharedTaskId: string, agentId: string, outcome: ClaimOutcome): Promise<ReleaseResult>;
  renewLease(sharedTaskId: string, agentId: string, ttlMs: number): Promise<RenewResult>;
  // Retire a claimable (unclaimed) SharedTask. Originator-only; used to clean up
  // duplicates. Claimed tasks must be released by their holder, not cancelled.
  cancel(sharedTaskId: string, byAgentId: string): Promise<CancelResult>;

  get(sharedTaskId: string): Promise<SharedTask | null>;
  list(filter: SharedTaskFilter): Promise<SharedTask[]>;
  getMyClaims(teamId: string, agentId: string): Promise<SharedTask[]>;
  // Text search over the live pool's title + description, for overlap/dedupe
  // detection. Complementary to MemoryHub.search (semantic, over the activity
  // feed); this hits current SharedTask rows directly.
  searchSharedTasks(teamId: string, query: string, limit?: number): Promise<SharedTask[]>;
}

export interface MemoryHubConfig {
  enabled: boolean;
  backend: MemoryHubBackend;
  teamId: string;
  agentId: string;
  syncIntervalMs: number;
  // XMem-specific (only consulted when backend === 'xmem')
  xmem: {
    baseUrl: string;
    apiKey: string;
    orgID: string;
    environment?: 'staging' | 'production';
  };
  // Postgres+pgvector-specific (only consulted when backend === 'pgvector')
  pgvector: {
    connectionString: string;
    // Optional per-agent token. When set AND a matching agent_identities
    // row exists in the database, writes + orchestration mutations verify
    // the configured token against the stored SHA-256 hash before
    // performing the operation. When unset (default), the adapter behaves
    // as before — anyone with the connection string can write as anyone.
    // See packages/priority-forge-hub/ADAPTERS.md "Hardening" for setup.
    agentToken?: string;
  };
  // Privacy passes through from existing TeamPulse rules at subscriber time;
  // memory-hub does not maintain a separate privacy store.
}

export const DEFAULT_MEMORY_HUB_CONFIG: MemoryHubConfig = {
  enabled: false,
  backend: 'noop',
  teamId: '',
  agentId: '',
  syncIntervalMs: 60000,
  xmem: {
    baseUrl: 'https://api.xtrace.ai/v1',
    apiKey: '',
    orgID: '',
    environment: 'production',
  },
  pgvector: {
    connectionString: '',
    agentToken: '',
  },
};
