import type {
  AgentRuntimeProvider,
  RuntimeAttachHandle,
  RuntimeAttachMode,
  RuntimeCapability,
  RuntimeCapabilitySet,
  RuntimeHealth,
  RuntimeMessage,
  RuntimeScope,
  RuntimeSession,
  RuntimeSessionState,
  RuntimeSessionTree,
  RuntimeStreamEvent,
  TransitionalCapabilityInventoryEntry,
  TransitionalCapabilityInventoryProvider,
  TransitionalRuntimeCapability,
} from '@mosaicstack/types';

const HERMES_PROVIDER_ID = 'runtime.hermes';
const TRANSITIONAL_CAPABILITIES: readonly TransitionalRuntimeCapability[] = [
  'kanban',
  'skills',
  'memory',
  'tools',
  'cron',
];
const RUNTIME_CAPABILITIES: readonly RuntimeCapability[] = [
  'session.list',
  'session.tree',
  'session.stream',
  'session.send',
  'session.attach',
  'session.terminate',
];

/** Legacy transport boundary. These shapes are intentionally adapter-local. */
export interface HermesLegacySession {
  conversation_id: string;
  agent_id: string;
  parent_conversation_id?: string;
  status: string;
  created_at: string;
  updated_at: string;
}
export interface HermesRuntimeTransport {
  capabilities(scope: RuntimeScope): Promise<string[]>;
  health(scope: RuntimeScope): Promise<{ status: string; detail?: string }>;
  sessions(scope: RuntimeScope): Promise<HermesLegacySession[]>;
  stream(
    sessionId: string,
    cursor: string | undefined,
    scope: RuntimeScope,
  ): AsyncIterable<RuntimeStreamEvent>;
  send(sessionId: string, message: RuntimeMessage, scope: RuntimeScope): Promise<void>;
  attach(
    sessionId: string,
    mode: RuntimeAttachMode,
    scope: RuntimeScope,
  ): Promise<RuntimeAttachHandle>;
  detach(attachmentId: string, scope: RuntimeScope): Promise<void>;
  terminate(sessionId: string, approvalRef: string, scope: RuntimeScope): Promise<void>;
}

export class HermesRuntimeProviderError extends Error {
  constructor(
    readonly code: 'capability_unsupported' | 'invalid_request',
    message: string,
  ) {
    super(message);
    this.name = HermesRuntimeProviderError.name;
  }
}

/**
 * Transitional Hermes adapter. Legacy identifiers and schemas do not cross this
 * boundary: callers only observe Mosaic AgentRuntimeProvider contracts.
 */
export class HermesRuntimeProvider
  implements AgentRuntimeProvider, TransitionalCapabilityInventoryProvider
{
  readonly id = HERMES_PROVIDER_ID;

  constructor(private readonly transport: HermesRuntimeTransport) {}

  /**
   * Full AC-TESS-05 migration inventory. These operations are deliberately
   * unsupported until their Mosaic-owned plugin contracts exist.
   */
  async transitionalCapabilityMatrix(
    _scope: RuntimeScope,
  ): Promise<TransitionalCapabilityInventoryEntry[]> {
    return TRANSITIONAL_CAPABILITIES.map((capability) => ({ capability, status: 'unsupported' }));
  }

  async assertTransitionalCapability(
    capability: TransitionalRuntimeCapability,
    scope: RuntimeScope,
  ): Promise<void> {
    const entry = (await this.transitionalCapabilityMatrix(scope)).find(
      (candidate) => candidate.capability === capability,
    );
    if (!entry || entry.status !== 'supported') {
      throw new HermesRuntimeProviderError(
        'capability_unsupported',
        `Hermes transitional capability is unsupported: ${capability}`,
      );
    }
  }

  async capabilities(scope: RuntimeScope): Promise<RuntimeCapabilitySet> {
    const legacyCapabilities = await this.transport.capabilities(scope);
    return {
      supported: RUNTIME_CAPABILITIES.filter((capability) =>
        legacyCapabilities.includes(capability),
      ),
    };
  }

  async health(scope: RuntimeScope): Promise<RuntimeHealth> {
    const health = await this.transport.health(scope);
    return {
      status: health.status === 'healthy' || health.status === 'degraded' ? health.status : 'down',
      checkedAt: new Date().toISOString(),
      ...(health.detail ? { detail: health.detail } : {}),
    };
  }

  async listSessions(scope: RuntimeScope): Promise<RuntimeSession[]> {
    await this.requireCapability('session.list', scope);
    return (await this.transport.sessions(scope)).map((session) => this.session(session));
  }

  async getSessionTree(scope: RuntimeScope): Promise<RuntimeSessionTree[]> {
    await this.requireCapability('session.tree', scope);
    const sessions = (await this.transport.sessions(scope)).map((session) => this.session(session));
    const nodes = new Map<string, RuntimeSessionTree>(
      sessions.map((session) => [session.id, { session, children: [] }]),
    );
    const roots: RuntimeSessionTree[] = [];
    for (const session of sessions) {
      const node = nodes.get(session.id)!;
      const parent = session.parentSessionId ? nodes.get(session.parentSessionId) : undefined;
      if (parent) parent.children.push(node);
      else roots.push(node);
    }
    return roots;
  }

  async *streamSession(
    sessionId: string,
    cursor: string | undefined,
    scope: RuntimeScope,
  ): AsyncIterable<RuntimeStreamEvent> {
    await this.requireCapability('session.stream', scope);
    yield* this.transport.stream(sessionId, cursor, scope);
  }
  async sendMessage(
    sessionId: string,
    message: RuntimeMessage,
    scope: RuntimeScope,
  ): Promise<void> {
    await this.requireCapability('session.send', scope);
    if (!message.content.trim())
      throw new HermesRuntimeProviderError('invalid_request', 'Message content is required');
    await this.transport.send(sessionId, message, scope);
  }
  async attach(
    sessionId: string,
    mode: RuntimeAttachMode,
    scope: RuntimeScope,
  ): Promise<RuntimeAttachHandle> {
    await this.requireCapability('session.attach', scope);
    return this.transport.attach(sessionId, mode, scope);
  }
  async detach(attachmentId: string, scope: RuntimeScope): Promise<void> {
    await this.transport.detach(attachmentId, scope);
  }
  async terminate(sessionId: string, approvalRef: string, scope: RuntimeScope): Promise<void> {
    await this.requireCapability('session.terminate', scope);
    if (!approvalRef.trim())
      throw new HermesRuntimeProviderError('invalid_request', 'Termination approval is required');
    await this.transport.terminate(sessionId, approvalRef, scope);
  }

  private async requireCapability(
    capability: RuntimeCapability,
    scope: RuntimeScope,
  ): Promise<void> {
    if (!(await this.capabilities(scope)).supported.includes(capability)) {
      throw new HermesRuntimeProviderError(
        'capability_unsupported',
        `Hermes does not bridge ${capability}`,
      );
    }
  }
  private session(value: HermesLegacySession): RuntimeSession {
    return {
      id: value.conversation_id,
      providerId: this.id,
      runtimeId: value.agent_id,
      ...(value.parent_conversation_id ? { parentSessionId: value.parent_conversation_id } : {}),
      state: state(value.status),
      createdAt: value.created_at,
      updatedAt: value.updated_at,
    };
  }
}
function state(value: string): RuntimeSessionState {
  return (
    (
      { running: 'active', waiting: 'idle', starting: 'starting', stopped: 'stopped' } as Record<
        string,
        RuntimeSessionState
      >
    )[value] ?? 'failed'
  );
}
