/**
 * Definitions for Connection interceptors.
 *
 * @module
 */

import type { Duration, SearchAttributePair, TypedSearchAttributes } from '@temporalio/common';
import { Headers, Next } from '@temporalio/common';
import type { temporal } from '@temporalio/proto';
import type { NexusOperationHandle } from './nexus-client';
import type {
  NexusOperationExecutionCount,
  NexusOperationExecutionDescription,
  NexusOperationExecution,
  NexusOperationIdConflictPolicy,
  NexusOperationIdReusePolicy,
} from './nexus-types';
import type { CompiledScheduleOptions } from './schedule-types';
import type {
  ActivityExecutionDescription,
  ActivityExecutionInfo,
  CountActivityExecutions,
  DescribeWorkflowExecutionResponse,
  RequestCancelWorkflowExecutionResponse,
  TerminateWorkflowExecutionResponse,
  WorkflowExecution,
} from './types';
import type { CompiledWorkflowOptions, WorkflowUpdateOptions } from './workflow-options';
import type { ActivityHandle, ActivityOptions } from './activity-client';

export { Headers, Next };

/** Input for WorkflowClientInterceptor.start */
export interface WorkflowStartInput {
  /** Name of Workflow to start */
  readonly workflowType: string;
  readonly headers: Headers;
  readonly options: CompiledWorkflowOptions;
}

/** Input for WorkflowClientInterceptor.update */
export interface WorkflowStartUpdateInput {
  readonly updateName: string;
  readonly args: unknown[];
  readonly workflowExecution: WorkflowExecution;
  readonly firstExecutionRunId?: string;
  readonly headers: Headers;
  readonly options: WorkflowUpdateOptions;
}

/** Output for WorkflowClientInterceptor.startWithDetails */
export interface WorkflowStartOutput {
  readonly runId: string;
  readonly eagerlyStarted: boolean;
}

/** Output for WorkflowClientInterceptor.startUpdate */
export interface WorkflowStartUpdateOutput {
  readonly updateId: string;
  readonly workflowRunId: string;
  readonly outcome?: temporal.api.update.v1.IOutcome;
}

/**
 * Input for WorkflowClientInterceptor.startUpdateWithStart
 */
export interface WorkflowStartUpdateWithStartInput {
  readonly workflowType: string;
  readonly workflowStartOptions: CompiledWorkflowOptions;
  readonly workflowStartHeaders: Headers;
  readonly updateName: string;
  readonly updateArgs: unknown[];
  readonly updateOptions: WorkflowUpdateOptions;
  readonly updateHeaders: Headers;
}

/**
 * Output for WorkflowClientInterceptor.startUpdateWithStart
 */
export interface WorkflowStartUpdateWithStartOutput {
  readonly workflowExecution: WorkflowExecution;
  readonly updateId: string;
  readonly updateOutcome?: temporal.api.update.v1.IOutcome;
}

/** Input for WorkflowClientInterceptor.signal */
export interface WorkflowSignalInput {
  readonly signalName: string;
  readonly args: unknown[];
  readonly workflowExecution: WorkflowExecution;
  readonly headers: Headers;
}

/** Input for WorkflowClientInterceptor.signalWithStart */
export interface WorkflowSignalWithStartInput {
  readonly workflowType: string;
  readonly signalName: string;
  readonly signalArgs: unknown[];
  readonly headers: Headers;
  readonly options: CompiledWorkflowOptions;
}

/** Input for WorkflowClientInterceptor.query */
export interface WorkflowQueryInput {
  readonly queryType: string;
  readonly args: unknown[];
  readonly workflowExecution: WorkflowExecution;
  readonly queryRejectCondition?: temporal.api.enums.v1.QueryRejectCondition;
  readonly headers: Headers;
}

/** Input for WorkflowClientInterceptor.terminate */
export interface WorkflowTerminateInput {
  readonly workflowExecution: WorkflowExecution;
  readonly reason?: string;
  readonly details?: unknown[];
  readonly firstExecutionRunId?: string;
}

/** Input for WorkflowClientInterceptor.cancel */
export interface WorkflowCancelInput {
  readonly workflowExecution: WorkflowExecution;
  readonly firstExecutionRunId?: string;
}

/** Input for WorkflowClientInterceptor.describe */
export interface WorkflowDescribeInput {
  readonly workflowExecution: WorkflowExecution;
}

/**
 * Implement any of these methods to intercept {@link WorkflowClient} outbound calls
 *
 * @experimental Standalone Activities are experimental. APIs may be subject to change.
 */
export interface WorkflowClientInterceptor {
  /**
   * Intercept a service call to startWorkflowExecution
   *
   * If you implement this method,
   * {@link signalWithStart} most likely needs to be implemented too
   *
   * @deprecated in favour of {@link startWithDetails}
   */
  start?: (input: WorkflowStartInput, next: Next<this, 'start'>) => Promise<string /* runId */>;

  /**
   * Intercept a service call to startWorkflowExecution
   *
   * This method returns start details via {@link WorkflowStartOutput}.
   *
   * If you implement this method,
   * {@link signalWithStart} most likely needs to be implemented too
   */
  startWithDetails?: (input: WorkflowStartInput, next: Next<this, 'startWithDetails'>) => Promise<WorkflowStartOutput>;

  /**
   * Intercept a service call to updateWorkflowExecution
   */
  startUpdate?: (
    input: WorkflowStartUpdateInput,
    next: Next<this, 'startUpdate'>
  ) => Promise<WorkflowStartUpdateOutput>;
  /**
   * Intercept a service call to startUpdateWithStart
   */
  startUpdateWithStart?: (
    input: WorkflowStartUpdateWithStartInput,
    next: Next<this, 'startUpdateWithStart'>
  ) => Promise<WorkflowStartUpdateWithStartOutput>;
  /**
   * Intercept a service call to signalWorkflowExecution
   *
   * If you implement this method,
   * {@link signalWithStart} most likely needs to be implemented too
   */
  signal?: (input: WorkflowSignalInput, next: Next<this, 'signal'>) => Promise<void>;
  /**
   * Intercept a service call to signalWithStartWorkflowExecution
   */
  signalWithStart?: (input: WorkflowSignalWithStartInput, next: Next<this, 'signalWithStart'>) => Promise<string>;
  /**
   * Intercept a service call to queryWorkflow
   */
  query?: (input: WorkflowQueryInput, next: Next<this, 'query'>) => Promise<unknown>;
  /**
   * Intercept a service call to terminateWorkflowExecution
   */
  terminate?: (
    input: WorkflowTerminateInput,
    next: Next<this, 'terminate'>
  ) => Promise<TerminateWorkflowExecutionResponse>;
  /**
   * Intercept a service call to requestCancelWorkflowExecution
   */
  cancel?: (input: WorkflowCancelInput, next: Next<this, 'cancel'>) => Promise<RequestCancelWorkflowExecutionResponse>;
  /**
   * Intercept a service call to describeWorkflowExecution
   */
  describe?: (input: WorkflowDescribeInput, next: Next<this, 'describe'>) => Promise<DescribeWorkflowExecutionResponse>;
}

/** @deprecated: Use {@link WorkflowClientInterceptor} instead */
export type WorkflowClientCallsInterceptor = WorkflowClientInterceptor;

/** @deprecated */
export interface WorkflowClientCallsInterceptorFactoryInput {
  workflowId: string;
  runId?: string;
}

/**
 * A function that takes {@link CompiledWorkflowOptions} and returns an interceptor
 *
 * @deprecated: Please define interceptors directly, without factory
 */
export interface WorkflowClientCallsInterceptorFactory {
  (input: WorkflowClientCallsInterceptorFactoryInput): WorkflowClientCallsInterceptor;
}

/**
 * A mapping of interceptor type of a list of factory functions
 *
 * @deprecated: Please define interceptors directly, without factory
 */
export interface WorkflowClientInterceptors {
  /** @deprecated */
  calls?: WorkflowClientCallsInterceptorFactory[];
}

/**
 * Implement any of these methods to intercept {@link ScheduleClient} outbound calls
 */
export interface ScheduleClientInterceptor {
  /**
   * Intercept a service call to CreateSchedule
   */
  create?: (input: CreateScheduleInput, next: Next<this, 'create'>) => Promise<CreateScheduleOutput>;
}

/**
 * Input for {@link ScheduleClientInterceptor.create}
 */
export interface CreateScheduleInput {
  readonly headers: Headers;
  readonly options: CompiledScheduleOptions;
}

export type CreateScheduleOutput = {
  readonly conflictToken: Uint8Array;
};

/**
 * Implement any of these methods to intercept NexusClient outbound calls.
 */
export interface NexusClientInterceptor {
  /** Intercept a call to {@link NexusServiceClient.startOperation}. */
  startOperation?: (
    input: StartNexusOperationInput,
    next: Next<this, 'startOperation'>
  ) => Promise<NexusOperationHandle>;

  /** Intercept {@link NexusOperationHandle.result}. */
  getResult?: (input: GetNexusOperationResultInput, next: Next<this, 'getResult'>) => Promise<unknown>;

  /** Intercept {@link NexusOperationHandle.describe}. */
  describe?: (
    input: DescribeNexusOperationInput,
    next: Next<this, 'describe'>
  ) => Promise<NexusOperationExecutionDescription>;

  /** Intercept {@link NexusOperationHandle.cancel}. */
  cancel?: (input: CancelNexusOperationInput, next: Next<this, 'cancel'>) => Promise<void>;

  /** Intercept {@link NexusOperationHandle.terminate}. */
  terminate?: (input: TerminateNexusOperationInput, next: Next<this, 'terminate'>) => Promise<void>;

  /** Intercept {@link NexusClient.list}. */
  list?: (input: ListNexusOperationsInput, next: Next<this, 'list'>) => AsyncIterable<NexusOperationExecution>;

  /** Intercept {@link NexusClient.count}. */
  count?: (input: CountNexusOperationsInput, next: Next<this, 'count'>) => Promise<NexusOperationExecutionCount>;
}

/** Input for {@link NexusClientInterceptor.startOperation}. */
export interface StartNexusOperationInput {
  readonly endpoint: string;
  readonly service: string;
  readonly operation: string;
  readonly id: string;
  readonly arg: unknown;
  readonly scheduleToCloseTimeout?: Duration;
  readonly scheduleToStartTimeout?: Duration;
  readonly startToCloseTimeout?: Duration;
  readonly summary?: string;
  readonly idReusePolicy?: NexusOperationIdReusePolicy;
  readonly idConflictPolicy?: NexusOperationIdConflictPolicy;
  readonly searchAttributes?: SearchAttributePair[] | TypedSearchAttributes;
  readonly headers?: Record<string, string>;
}

/** Input for {@link NexusClientInterceptor.getResult}. */
export interface GetNexusOperationResultInput {
  readonly operationId: string;
  readonly runId?: string;
}

/** Input for {@link NexusClientInterceptor.describe}. */
export interface DescribeNexusOperationInput {
  readonly operationId: string;
  readonly runId?: string;
}

/** Input for {@link NexusClientInterceptor.cancel}. */
export interface CancelNexusOperationInput {
  readonly operationId: string;
  readonly runId?: string;
  readonly reason?: string;
}

/** Input for {@link NexusClientInterceptor.terminate}. */
export interface TerminateNexusOperationInput {
  readonly operationId: string;
  readonly runId?: string;
  readonly reason?: string;
}

/** Input for {@link NexusClientInterceptor.list}. */
export interface ListNexusOperationsInput {
  readonly query?: string;
  readonly pageSize?: number;
}

/** Input for {@link NexusClientInterceptor.count}. */
export interface CountNexusOperationsInput {
  readonly query?: string;
}

/**
 * Interceptors for any high-level SDK client.
 */
export interface ClientInterceptors {
  workflow?: WorkflowClientInterceptors | WorkflowClientInterceptor[];
  schedule?: ScheduleClientInterceptor[];

  nexus?: NexusClientInterceptor[];
  activity?: ActivityClientInterceptor[];
}

/**
 * Implement any of these methods to intercept {@link ActivityClient} outbound calls
 *
 * @experimental Standalone Activities are experimental. APIs may be subject to change.
 */
export interface ActivityClientInterceptor {
  /**
   * Intercept a service call to startActivityExecution
   */
  start?: (input: ActivityStartInput, next: Next<this, 'start'>) => Promise<ActivityHandle>;
  /**
   * Intercept a service call to pollActivityExecution
   */
  getResult?: (input: ActivityGetResultInput, next: Next<this, 'getResult'>) => Promise<any>;
  /**
   * Intercept a service call to describeActivityExecution
   */
  describe?: (input: ActivityDescribeInput, next: Next<this, 'describe'>) => Promise<ActivityExecutionDescription>;
  /**
   * Intercept a service call to requestCancelActivityExecution
   */
  cancel?: (input: ActivityCancelInput, next: Next<this, 'cancel'>) => Promise<void>;
  /**
   * Intercept a service call to terminateActivityExecution
   */
  terminate?: (input: ActivityTerminateInput, next: Next<this, 'terminate'>) => Promise<void>;
  /**
   * Intercept a service call to listActivityExecutions
   */
  list?: (input: ActivityListInput, next: Next<this, 'list'>) => AsyncIterable<ActivityExecutionInfo>;
  /**
   * Intercept a service call to countActivityExecutions
   */
  count?: (input: ActivityCountInput, next: Next<this, 'count'>) => Promise<CountActivityExecutions>;
}

/**
 * Input for {@link ActivityClientInterceptor.start}
 *
 * @experimental Standalone Activities are experimental. APIs may be subject to change.
 */
export interface ActivityStartInput {
  readonly activityType: string;
  readonly options: ActivityOptions;
  readonly headers: Headers;
}

/**
 * Input for {@link ActivityClientInterceptor.getResult}
 *
 * @experimental Standalone Activities are experimental. APIs may be subject to change.
 */
export interface ActivityGetResultInput {
  readonly activityId: string;
  readonly activityRunId: string;
  readonly headers: Headers;
}

/**
 * Input for {@link ActivityClientInterceptor.describe}
 *
 * @experimental Standalone Activities are experimental. APIs may be subject to change.
 */
export interface ActivityDescribeInput {
  readonly activityId: string;
  readonly activityRunId: string;
  readonly headers: Headers;
}

/**
 * Input for {@link ActivityClientInterceptor.cancel}
 *
 * @experimental Standalone Activities are experimental. APIs may be subject to change.
 */
export interface ActivityCancelInput {
  readonly activityId: string;
  readonly activityRunId: string;
  readonly reason: string;
  readonly headers: Headers;
}

/**
 * Input for {@link ActivityClientInterceptor.terminate}
 *
 * @experimental Standalone Activities are experimental. APIs may be subject to change.
 */
export interface ActivityTerminateInput {
  readonly activityId: string;
  readonly activityRunId: string;
  readonly reason: string;
  readonly headers: Headers;
}

/**
 * Input for {@link ActivityClientInterceptor.list}
 *
 * @experimental Standalone Activities are experimental. APIs may be subject to change.
 */
export interface ActivityListInput {
  readonly query: string;
  readonly headers: Headers;
}

/**
 * Input for {@link ActivityClientInterceptor.count}
 *
 * @experimental Standalone Activities are experimental. APIs may be subject to change.
 */
export interface ActivityCountInput {
  readonly query: string;
  readonly headers: Headers;
}
