/**
 * Blocks ACP dispatch on permission/elicitation prompts until the user responds via API.
 */
import type { RequestPermissionRequest, RequestPermissionResponse, CreateElicitationRequest, CreateElicitationResponse } from '@agentclientprotocol/sdk';
import { isUnattendedMode } from './provider-manifest.js';

const DEFAULT_TIMEOUT_MS = 120_000;

interface PendingPermission {
  type: 'permission';
  request: RequestPermissionRequest;
  sessionId: string;
  resolve: (response: RequestPermissionResponse) => void;
  reject: (err: Error) => void;
  timer: ReturnType<typeof setTimeout>;
}

interface PendingElicitation {
  type: 'elicitation';
  request: CreateElicitationRequest;
  sessionId: string;
  resolve: (response: CreateElicitationResponse) => void;
  reject: (err: Error) => void;
  timer: ReturnType<typeof setTimeout>;
}

type PendingEntry = PendingPermission | PendingElicitation;

const pendingByTask = new Map<string, PendingEntry>();

export type PermissionKind = 'tool' | 'question' | 'plan' | 'elicitation';

export interface PermissionPrompt {
  taskId: string;
  kind: PermissionKind;
  toolTitle?: string;
  description?: string;
  input?: Record<string, unknown>;
  options: Array<{ optionId: string; label: string }>;
}

export interface PermissionHooks {
  onPrompt?: (prompt: PermissionPrompt & { sessionId: string }) => void | Promise<void>;
  onResolved?: (taskId: string, sessionId: string) => void | Promise<void>;
}

let hooks: PermissionHooks = {};

export function setPermissionHooks(next: PermissionHooks): void {
  hooks = next;
}

function resolveKind(params: RequestPermissionRequest): PermissionKind {
  const input = params.toolCall?.rawInput;
  if (input && typeof input === 'object' && !Array.isArray(input) && 'questions' in input && Array.isArray((input as Record<string, unknown>).questions)) {
    return 'question';
  }
  return 'tool';
}

function toPrompt(taskId: string, params: RequestPermissionRequest): PermissionPrompt {
  const kind = resolveKind(params);
  const rawInput = params.toolCall?.rawInput;
  const input = rawInput && typeof rawInput === 'object' && !Array.isArray(rawInput)
    ? rawInput as Record<string, unknown>
    : undefined;
  return {
    taskId,
    kind,
    toolTitle: params.toolCall?.title ?? undefined,
    ...(input ? { input } : {}),
    options: params.options.map((o) => ({
      optionId: o.optionId,
      label: o.name,
    })),
  };
}

export function waitForPermissionResponse(
  taskId: string,
  sessionId: string,
  provider: string,
  modeId: string | undefined,
  params: RequestPermissionRequest,
  timeoutMs = DEFAULT_TIMEOUT_MS,
): Promise<RequestPermissionResponse> {
  if (isUnattendedMode(provider, modeId)) {
    const first = params.options[0];
    if (first) {
      return Promise.resolve({ outcome: { outcome: 'selected', optionId: first.optionId } });
    }
    return Promise.resolve({ outcome: { outcome: 'cancelled' } });
  }

  return new Promise((resolve, reject) => {
    const existing = pendingByTask.get(taskId);
    if (existing) {
      clearTimeout(existing.timer);
      existing.reject(new Error('superseded by newer permission request'));
    }

    const timer = setTimeout(() => {
      pendingByTask.delete(taskId);
      void hooks.onResolved?.(taskId, sessionId);
      resolve({ outcome: { outcome: 'cancelled' } });
    }, timeoutMs);

    pendingByTask.set(taskId, { type: 'permission', request: params, sessionId, resolve, reject, timer });

    const prompt = toPrompt(taskId, params);
    void hooks.onPrompt?.({ ...prompt, sessionId });
  });
}

export function respondToPermission(taskId: string, optionId: string | null, updatedInput?: Record<string, unknown>): boolean {
  const pending = pendingByTask.get(taskId);
  if (!pending) return false;

  clearTimeout(pending.timer);
  pendingByTask.delete(taskId);

  if (pending.type === 'elicitation') {
    if (updatedInput) {
      const content = updatedInput as { [key: string]: string | number | boolean | string[] };
      pending.resolve({ action: 'accept', content });
    } else {
      pending.resolve({ action: 'decline' });
    }
  } else {
    if (optionId) {
      pending.resolve({ outcome: { outcome: 'selected', optionId } });
    } else {
      pending.resolve({ outcome: { outcome: 'cancelled' } });
    }
  }

  void hooks.onResolved?.(taskId, pending.sessionId);
  return true;
}

export function getPendingPermission(taskId: string): PermissionPrompt | null {
  const pending = pendingByTask.get(taskId);
  if (!pending) return null;
  if (pending.type === 'elicitation') {
    return elicitationToPrompt(taskId, pending.request);
  }
  return toPrompt(taskId, pending.request);
}

function elicitationToPrompt(taskId: string, params: CreateElicitationRequest): PermissionPrompt {
  const input: Record<string, unknown> = { message: params.message };
  if ('requestedSchema' in params) {
    input.requestedSchema = params.requestedSchema;
  }
  return {
    taskId,
    kind: 'elicitation',
    toolTitle: params.message,
    input,
    options: [],
  };
}

export function waitForElicitationResponse(
  taskId: string,
  sessionId: string,
  provider: string,
  modeId: string | undefined,
  params: CreateElicitationRequest,
  timeoutMs = DEFAULT_TIMEOUT_MS,
): Promise<CreateElicitationResponse> {
  if (isUnattendedMode(provider, modeId)) {
    return Promise.resolve({ action: 'decline' });
  }

  return new Promise((resolve, reject) => {
    const existing = pendingByTask.get(taskId);
    if (existing) {
      clearTimeout(existing.timer);
      existing.reject(new Error('superseded by newer elicitation request'));
    }

    const timer = setTimeout(() => {
      pendingByTask.delete(taskId);
      void hooks.onResolved?.(taskId, sessionId);
      resolve({ action: 'cancel' });
    }, timeoutMs);

    pendingByTask.set(taskId, { type: 'elicitation', request: params, sessionId, resolve, reject, timer });

    const prompt = elicitationToPrompt(taskId, params);
    void hooks.onPrompt?.({ ...prompt, sessionId });
  });
}

export function cancelPendingPermission(taskId: string): void {
  const pending = pendingByTask.get(taskId);
  if (!pending) return;
  clearTimeout(pending.timer);
  pendingByTask.delete(taskId);
  if (pending.type === 'elicitation') {
    pending.resolve({ action: 'cancel' });
  } else {
    pending.resolve({ outcome: { outcome: 'cancelled' } });
  }
  void hooks.onResolved?.(taskId, pending.sessionId);
}
