/*
 * Priority Forge — memory-hub subscriber
 *
 * Listens to a host-supplied EventEmitter's 'sync_event' channel, translates
 * each event into a TeamMemoryEvent, and writes it through the active
 * MemoryHub when memory persistence is enabled.
 */

import { EventEmitter } from 'events';
import { isMemoryPersistenceActive, loadMemoryHubConfig } from './config';
import { getMemoryHub } from './factory';
import { TeamMemoryEvent, TeamMemoryEventType } from './types';

interface IncomingSyncEvent {
  eventId: string;
  userId: string;
  workspaceId: string;
  timestamp: string;
  version: string;
  eventType: string;
  data: Record<string, unknown>;
  private: boolean;
  visibility: 'team' | 'private';
}

const SYNC_EVENT_TYPES: ReadonlySet<TeamMemoryEventType> = new Set<TeamMemoryEventType>([
  'task_created',
  'task_selected',
  'task_updated',
  'task_completed',
  'context_switch',
  'project_created',
  'project_updated',
  'derived_blocker',
]);

function summarize(eventType: string, data: Record<string, unknown>, agentId: string): string {
  const taskRef = (data.taskId as string | undefined) ?? '';
  const taskName = (data.task as string | undefined) ?? '';
  const project = (data.project as string | undefined) ?? '';
  switch (eventType) {
    case 'task_created':
      return `${agentId} created ${taskRef} "${taskName}" in ${project}`;
    case 'task_selected':
      return `${agentId} started work on ${taskRef} "${taskName}" (${project})`;
    case 'task_updated': {
      const changes = (data.changes as Record<string, unknown> | undefined) ?? {};
      const summary = Object.keys(changes).slice(0, 4).join(', ');
      return `${agentId} updated ${taskRef} (${summary})`;
    }
    case 'task_completed': {
      const outcome = (data.outcome as string | undefined) ?? 'completed';
      return `${agentId} marked ${taskRef} ${outcome} in ${project}`;
    }
    case 'context_switch':
      return `${agentId} context-switched away from ${taskRef} (${project})`;
    case 'project_created':
      return `${agentId} created project ${data.name ?? data.projectId}`;
    case 'project_updated':
      return `${agentId} updated project ${data.name ?? data.projectId}`;
    case 'derived_blocker': {
      const status = (data.status as string | undefined) ?? 'active';
      return `${data.blockingTaskId} (${data.blockingAgentId}) ${status === 'cleared' ? 'cleared' : 'blocking'} ${data.blockedTaskId} (${data.blockedAgentId})`;
    }
    default:
      return `${agentId} ${eventType} ${taskRef}`;
  }
}

/*
 * Hub-boundary redaction (root CLAUDE.md "Hub boundary"). The in-process
 * 'sync_event' carries a rich task object for local subscribers, but the hub
 * must persist OBSERVATION METADATA only — enough to answer "who is doing what,
 * what blocks what" — never learning signals (priority values, effort, work
 * durations, ML deltas, weights) and no task content beyond the title seed.
 * This whitelists fields per event type; anything not listed never reaches the
 * hub. Keep this in sync with the boundary section of CLAUDE.md.
 */
const MEMORY_PAYLOAD_WHITELIST: Record<string, readonly string[]> = {
  task_created: ['taskId', 'task', 'project', 'status', 'dependencies', 'blocking'],
  task_selected: ['taskId', 'task', 'project', 'status', 'startedAt', 'sharedTaskId', 'dependencies', 'blocking'],
  task_completed: ['taskId', 'task', 'project', 'outcome'],
  context_switch: ['fromTaskId', 'fromProject'],
  project_created: ['projectId', 'name', 'status'],
  project_updated: ['projectId', 'name', 'status'],
  derived_blocker: ['blockedTaskId', 'blockedAgentId', 'blockingTaskId', 'blockingAgentId', 'status', 'observedAt'],
};

export function projectMemoryPayload(
  eventType: string,
  data: Record<string, unknown>,
): Record<string, unknown> {
  // task_updated never carries the changed VALUES or the prior task object —
  // that would leak full content plus the priority-change learning signal.
  // Carry only the names of the fields that moved.
  if (eventType === 'task_updated') {
    const changes = (data.changes as Record<string, unknown> | undefined) ?? {};
    const out: Record<string, unknown> = { changedFields: Object.keys(changes) };
    if (data.taskId !== undefined) out.taskId = data.taskId;
    if (data.project !== undefined) out.project = data.project;
    return out;
  }
  const allowed = MEMORY_PAYLOAD_WHITELIST[eventType] ?? ['taskId', 'project'];
  const out: Record<string, unknown> = {};
  for (const key of allowed) {
    if (data[key] !== undefined) out[key] = data[key];
  }
  return out;
}

function toTeamMemoryEvent(
  incoming: IncomingSyncEvent,
  teamId: string,
  agentId: string,
): TeamMemoryEvent | null {
  if (!SYNC_EVENT_TYPES.has(incoming.eventType as TeamMemoryEventType)) return null;
  return {
    eventId: incoming.eventId,
    teamId,
    agentId,
    workspaceId: incoming.workspaceId,
    eventType: incoming.eventType as TeamMemoryEventType,
    timestamp: incoming.timestamp,
    payload: projectMemoryPayload(incoming.eventType, incoming.data),
    summary: summarize(incoming.eventType, incoming.data, agentId),
    metadata: {
      legacy_user_id: incoming.userId,
      visibility: incoming.visibility,
      version: incoming.version,
    },
  };
}

export interface SubscriberDeps {
  emitter: EventEmitter;
  rederive: () => Promise<void>;
}

export interface SubscriberHandle {
  detach(): void;
}

export function attachSubscriber(deps: SubscriberDeps): SubscriberHandle {
  const handler = (event: IncomingSyncEvent) => {
    if (!isMemoryPersistenceActive()) return;
    const cfg = loadMemoryHubConfig();
    if (!cfg.teamId || !cfg.agentId) return;
    const memEvent = toTeamMemoryEvent(event, cfg.teamId, cfg.agentId);
    if (!memEvent) return;
    const hub = getMemoryHub();
    void hub.write(memEvent).then((result) => {
      if (!result.accepted) {
        console.warn('[memory-hub] write not accepted (backend may be noop)');
      }
    }).catch((err) => {
      console.error('[memory-hub] write rejected:', err instanceof Error ? err.message : err);
    });
    if (
      memEvent.eventType === 'task_created' ||
      memEvent.eventType === 'task_updated' ||
      memEvent.eventType === 'task_completed' ||
      memEvent.eventType === 'task_selected'
    ) {
      void deps.rederive().catch((err) => {
        console.error('[memory-hub] blocker rederivation failed:', err);
      });
    }
  };
  deps.emitter.on('sync_event', handler);
  return {
    detach() {
      deps.emitter.off('sync_event', handler);
    },
  };
}
