import type { AgentSession, ExtensionAPI } from "@earendil-works/pi-coding-agent";

/**
 * Process-local lifecycle contract consumed by pi-permission-system.
 *
 * Keep these string channels local rather than importing permission-system
 * internals: pi-subagents only publishes child identity, while the permission
 * extension remains the sole owner of policy and authorization decisions.
 */
export const PERMISSION_CHILD_CREATED_CHANNEL = "subagents:child:session-created";
export const PERMISSION_CHILD_DISPOSED_CHANNEL = "subagents:child:disposed";

interface EventPublisher {
  events?: {
    emit(channel: string, payload: unknown): void;
  };
}

interface SessionIdentity {
  sessionManager?: {
    getSessionId?: () => string | undefined;
  };
}

interface ChildRegistration {
  events: NonNullable<EventPublisher["events"]>;
  sessionId: string;
}

const childRegistrations = new WeakMap<object, ChildRegistration>();
const ACTIVE_AGENT_TAG = /<active_agent\s+name=["'][^"']+["'][^>]*>\s*/gi;
const SAFE_AGENT_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;

function normalizedSessionId(value: unknown): string | undefined {
  if (typeof value !== "string") return undefined;
  const normalized = value.trim();
  return normalized || undefined;
}

/**
 * Replace inherited agent identity with this child's stable config key.
 * The permission system reads the first active_agent tag, so inherited parent
 * tags must be removed rather than merely appending another one.
 */
export function withActiveAgentIdentity(systemPrompt: string, agentName: string): string {
  const withoutInheritedIdentity = systemPrompt.replace(ACTIVE_AGENT_TAG, "").trimEnd();
  const normalizedAgentName = agentName.trim();
  if (!SAFE_AGENT_NAME.test(normalizedAgentName)) return withoutInheritedIdentity;
  return `${withoutInheritedIdentity}\n\n<active_agent name="${normalizedAgentName}"/>`;
}

/**
 * Register a child synchronously before bindExtensions(). This is deliberately
 * fail-closed when an event publisher exists: a thrown lifecycle listener must
 * abort child startup rather than bind a headless permission system as if it
 * were an ordinary top-level session.
 */
export function registerPermissionChildSession(
  pi: Pick<ExtensionAPI, "events"> | EventPublisher,
  session: Pick<AgentSession, "sessionManager"> | SessionIdentity,
  parentSessionId: string | undefined,
): boolean {
  const events = (pi as EventPublisher).events;
  const childSessionId = normalizedSessionId(
    (session as SessionIdentity).sessionManager?.getSessionId?.(),
  );
  const normalizedParentSessionId = normalizedSessionId(parentSessionId);
  if (!events || !childSessionId || !normalizedParentSessionId) return false;

  const sessionKey = session as object;
  if (childRegistrations.has(sessionKey)) return true;

  events.emit(PERMISSION_CHILD_CREATED_CHANNEL, {
    sessionId: childSessionId,
    parentSessionId: normalizedParentSessionId,
  });
  childRegistrations.set(sessionKey, { events, sessionId: childSessionId });
  return true;
}

/** Publish disposal at most once, after the child session has actually closed. */
export function unregisterPermissionChildSession(session: object | undefined): void {
  if (!session) return;
  const registration = childRegistrations.get(session);
  if (!registration) return;
  childRegistrations.delete(session);
  registration.events.emit(PERMISSION_CHILD_DISPOSED_CHANNEL, {
    sessionId: registration.sessionId,
  });
}
