/*
 * Priority Forge — derived-blocker emitter
 *
 * Computes cross-agent BlockerEdges from local task state + cross-forge
 * ownership/status (via the sync-reader cache), diffs against a per-instance
 * edge-state map, and emits derived_blocker events for transitions only.
 *
 * Constructed by createMemoryHubIntegration() with the providers it needs:
 *   - taskProvider: how to read local tasks + the current workspace ID
 *   - reader: source of cross-forge ownership/status
 *
 * State (edgeState, inFlight) is encapsulated in the closure — no module
 * globals — so concurrent integrations (e.g. tests) don't interfere.
 */

import { v4 as uuidv4 } from 'uuid';
import { deriveBlockerEdges } from './blocker-derivation';
import { loadMemoryHubConfig } from './config';
import { getMemoryHub } from './factory';
import { TeamMemorySyncReader } from './sync-reader';
import { BlockerEdge, MinimalTask, TaskProvider, TeamMemoryEvent } from './types';

export interface BlockerEmitterDeps {
  taskProvider: TaskProvider;
  reader: TeamMemorySyncReader;
}

export interface BlockerEmitterHandle {
  rederive(): Promise<void>;
  /** test seam — wipes the per-instance edge cache */
  _reset(): void;
}

function edgeKey(edge: BlockerEdge): string {
  return `${edge.blockedTaskId}::${edge.blockingTaskId}`;
}

function summarize(edge: BlockerEdge): string {
  if (edge.status === 'cleared') {
    return `${edge.blockingTaskId} (${edge.blockingAgentId}) cleared — was blocking ${edge.blockedTaskId} (${edge.blockedAgentId})`;
  }
  return `${edge.blockingTaskId} (${edge.blockingAgentId}) is blocking ${edge.blockedTaskId} (${edge.blockedAgentId})`;
}

function buildEvent(
  edge: BlockerEdge,
  teamId: string,
  agentId: string,
  workspaceId: string,
): TeamMemoryEvent {
  return {
    eventId: uuidv4(),
    teamId,
    agentId,
    workspaceId,
    eventType: 'derived_blocker',
    timestamp: new Date().toISOString(),
    payload: {
      blockedTaskId: edge.blockedTaskId,
      blockedAgentId: edge.blockedAgentId,
      blockingTaskId: edge.blockingTaskId,
      blockingAgentId: edge.blockingAgentId,
      status: edge.status,
      observedAt: edge.observedAt,
    },
    summary: summarize(edge),
    metadata: {
      derived_by: agentId,
      blocked_task_id: edge.blockedTaskId,
      blocking_task_id: edge.blockingTaskId,
      blocked_agent_id: edge.blockedAgentId,
      blocking_agent_id: edge.blockingAgentId,
    },
  };
}

export function createBlockerEmitter(deps: BlockerEmitterDeps): BlockerEmitterHandle {
  const edgeState = new Map<string, 'active' | 'cleared'>();
  let inFlight: Promise<void> | null = null;

  async function rederiveOnce(): Promise<void> {
    const cfg = loadMemoryHubConfig();
    if (!cfg.enabled || !cfg.teamId || !cfg.agentId) return;

    let tasks: MinimalTask[];
    try {
      tasks = await deps.taskProvider.listTasks();
    } catch (err) {
      console.error('[memory-hub:blocker] taskProvider.listTasks failed:', err);
      return;
    }
    let workspaceId = 'default';
    try {
      workspaceId = (await deps.taskProvider.getCurrentWorkspaceId()) || 'default';
    } catch {
      // ignore — fall back to default
    }

    const ownership = deps.reader.getOwnershipMap();
    for (const t of tasks) {
      if (!ownership.has(t.id)) ownership.set(t.id, cfg.agentId);
    }
    const allTaskInputs: MinimalTask[] = [...tasks];
    for (const [taskId] of ownership) {
      if (!tasks.find((mt) => mt.id === taskId)) {
        const remoteStatus = deps.reader.getRemoteTaskStatus(taskId) ?? 'unknown';
        allTaskInputs.push({ id: taskId, status: remoteStatus });
      }
    }

    const edges = deriveBlockerEdges({
      localAgentId: cfg.agentId,
      ownership,
      tasks: allTaskInputs,
    });

    const seenKeys = new Set<string>();
    const transitions: BlockerEdge[] = [];

    for (const edge of edges) {
      const key = edgeKey(edge);
      seenKeys.add(key);
      const prior = edgeState.get(key);
      if (prior !== edge.status) {
        transitions.push(edge);
        edgeState.set(key, edge.status);
      }
    }

    // Edges that previously existed but no longer appear in the derivation
    // (e.g. dependency removed from a task) → emit synthetic 'cleared'.
    for (const [key, status] of [...edgeState.entries()]) {
      if (status !== 'active') continue;
      if (seenKeys.has(key)) continue;
      const [blockedTaskId, blockingTaskId] = key.split('::');
      const synthetic: BlockerEdge = {
        blockedTaskId,
        blockedAgentId: ownership.get(blockedTaskId) ?? cfg.agentId,
        blockingTaskId,
        blockingAgentId: ownership.get(blockingTaskId) ?? cfg.agentId,
        status: 'cleared',
        observedAt: new Date().toISOString(),
      };
      transitions.push(synthetic);
      edgeState.set(key, 'cleared');
    }

    if (transitions.length === 0) return;

    const hub = getMemoryHub();
    for (const edge of transitions) {
      const event = buildEvent(edge, cfg.teamId, cfg.agentId, workspaceId);
      try {
        await hub.write(event);
      } catch (err) {
        console.error('[memory-hub:blocker] hub.write failed:', err);
      }
    }
  }

  async function rederive(): Promise<void> {
    // Coalesce concurrent calls: if a derivation is already running, await it
    // and skip — the in-flight call already sees the latest state.
    if (inFlight) return inFlight;
    inFlight = rederiveOnce().finally(() => {
      inFlight = null;
    });
    return inFlight;
  }

  function _reset(): void {
    edgeState.clear();
  }

  return { rederive, _reset };
}
