import { WorkflowHandleService } from '../services/meshflow/handle';

import { LogLevel } from './logger';
import { ProviderConfig, ProvidersConfig } from './provider';
import { StringAnyType, StringStringType } from './serializer';
import { StreamData, StreamError } from './stream';

/**
 * Type definition for workflow configuration.
 */
type WorkflowConfig = {
  /**
   * Backoff coefficient for retry mechanism.
   * @default 10 (HMSH_MESHFLOW_EXP_BACKOFF)
   */
  backoffCoefficient?: number;

  /**
   * Maximum number of attempts for retries.
   * @default 5 (HMSH_MESHFLOW_MAX_ATTEMPTS)
   */
  maximumAttempts?: number;

  /**
   * Maximum interval between retries.
   * @default 120s (HMSH_MESHFLOW_MAX_INTERVAL)
   */
  maximumInterval?: string;

  /**
   * Whether to throw an error on final failure after retries are exhausted
   * or return the error object as a standard response containing error-related
   * fields like `stack`, `code`, `message`.
   * @default true
   */
  throwOnError?: boolean;
};

type WorkflowContext = {
  /**
   * can the workflow be retried if an error occurs
   */
  canRetry: boolean;

  COUNTER: {
    /**
     * the reentrant semaphore parent counter object for object reference during increment
     */
    counter: number;
  };

  /**
   * the reentrant semaphore, incremented in real-time as idempotent statements are re-traversed upon reentry. Indicates the current semaphore count.
   */
  counter: number;

  /**
   * number as string for the replay cursor
   */
  cursor: string;

  /**
   * the replay hash of name/value pairs representing prior executions
   */
  replay: StringStringType;

  /**
   * the HotMesh App namespace
   * @default meshflow
   */
  namespace: string;

  /**
   * holds list of interruption payloads; if list is longer than 1 when the error is thrown, a `collator` subflow will be used
   */
  interruptionRegistry: any[];

  /**
   * entry point ancestor flow; might be the parent; will never be self
   */
  originJobId: string;

  /**
   * the workflow/job ID
   */
  workflowId: string;

  /**
   * the dimensional isolation for the reentrant hook, expressed in the format `0,0`, `0,1`, etc
   */
  workflowDimension: string;

  /**
   * a concatenation of the task queue and workflow name (e.g., `${taskQueueName}-${workflowName}`)
   */
  workflowTopic: string;

  /**
   * the open telemetry trace context for the workflow, used for logging and tracing. If a sink is enabled, this will be sent to the sink.
   */
  workflowTrace: string;

  /**
   * the open telemetry span context for the workflow, used for logging and tracing. If a sink is enabled, this will be sent to the sink.
   */
  workflowSpan: string;

  /**
   * the native HotMesh message that encapsulates the arguments, metadata, and raw data for the workflow
   */
  raw: StreamData;

  /**
   * the HotMesh connection configuration (io/redis NPM package reference and login credentials)
   */
  connection: Connection;

  /**
   * if present, the workflow will delay expiration for the specified number of seconds
   */
  expire?: number;
};

/**
 * The schema for the full-text-search (RediSearch) index.
 */
export type WorkflowSearchSchema = Record<
  string,
  {
    /**
     * The FT.SEARCH field type. One of: TEXT, NUMERIC, TAG. TEXT is
     * most expensive, but also most expressive.
     */
    type: 'TEXT' | 'NUMERIC' | 'TAG';

    /**
     * FT.SEARCH SORTABLE field. If true, results may be sorted according to this field
     * @default false
     */
    sortable?: boolean;

    /**
     * FT.SEARCH NOSTEM field. applies to TEXT fields types.
     * If true, the text field index will not stem words
     * @default false
     */
    nostem?: boolean;

    /**
     * FT.SEARCH NOINDEX field. If true and if the field is sortable, the field will aid
     * in sorting results but not be directly indexed as a standalone
     * @default false
     */
    noindex?: boolean;

    /**
     * if true, the field is indexed and searchable within the FT.SEARCH index
     * This is different from `noindex` which is FT.SEARCH specific and relates
     * to sorting and indexing. This is a general flag for the field that will
     * enable or disable indexing and searching entirely. Use for fields with
     * absolutely no meaning to query or sorting but which are important
     * nonetheless as part of the data record that is saved and returned.
     * @default true
     */
    indexed?: boolean;

    /**
     * An array of possible values for the field
     */
    examples?: string[];

    /**
     * The 'nilable' setting may NOT be set to `true` for
     * NUMBER types as it causes an indexing error;
     * consider a custom (e.g., negative number) value to represent
     * `null` if desired for a NUMERIC field.
     * Set to true only if the field is a TEXT or TAG type and
     * you wish to save the string `null` as a value to search
     * on (the tag, {null}, or the string, (null)
     * @default false
     */
    nilable?: boolean;

    /**
     * possible scalar/primitive types for the field. Use when
     * serializing and restoring data to ensure the field is
     * properly typed. If not provided, the field will be
     * treated as a string.
     */
    primitive?: 'string' | 'number' | 'boolean' | 'array' | 'object';

    /**
     * if true, the field is required to be present in the data record
     * @default false
     */
    required?: boolean;

    /**
     * an enumerated list of allowed values; if field is nilable, it is implied
     * and therefore not necessary to include `null` in the list
     * @default []
     */
    enum?: string[];

    /**
     * a regular expression pattern for the field
     * @default '.*'
     * @example '^[a-zA-Z0-9_]*$'
     */
    pattern?: string;

    /**
     * literal value to use for the indexed field name (without including the standard underscore (_) prefix isolate)
     */
    fieldName?: string;
  }
>;

type WorkflowSearchOptions = {
  /** FT index name (myapp:myindex) */
  index?: string;

  /** FT prefixes (['myapp:myindex:prefix1', 'myapp:myindex:prefix2']) */
  prefix?: string[];

  /**
   * Schema mapping each field. Each field is a key-value pair where the key is the field name
   * and the value is a record of field options. If the fieldName is provided,
   * it will be used as the indexed field name. If not provided
   * key will be used as the indexed field name with an underscore prefix.
   *
   */
  schema?: WorkflowSearchSchema;

  /** Additional data as a key-value record */
  data?: StringStringType;
};

type SearchResults = {
  /**
   * the total number of results
   */
  count: number;
  /**
   * the raw FT.SEARCH query string
   */
  query: string;
  /**
   * the raw FT.SEARCH results as an array of objects
   */
  data: StringStringType[];
};

type WorkflowOptions = {
  /**
   * the namespace for the workflow
   * @default meshflow
   */
  namespace?: string;

  /**
   * the task queue for the workflow; optional if entity is provided
   */
  taskQueue?: string;

  /**
   * input arguments to pass in
   */
  args: any[];

  /**
   * the job id
   */
  workflowId?: string;

  /**
   * if invoking a workflow, passing 'entity' will apply the value as the workflowName, taskQueue, and prefix, ensuring the FT.SEARCH index is properly scoped. This is a convenience method but limits options.
   */
  entity?: string;

  /**
   * the name of the user's workflow function; optional if 'entity' is provided
   */
  workflowName?: string;

  /**
   * the parent workflow id; adjacent ancestor ID
   */
  parentWorkflowId?: string;

  /**
   * the entry point workflow id
   */
  originJobId?: string;

  /**
   * OpenTelemetry trace context for the workflow
   */
  workflowTrace?: string;

  /**
   * OpenTelemetry span context for the workflow
   */
  workflowSpan?: string;

  /**
   * the full-text-search (RediSearch) options for the workflow
   */
  search?: WorkflowSearchOptions;

  /**
   * marker data (begins with a -)
   */
  marker?: StringStringType;

  /**
   * the workflow configuration object
   */
  config?: WorkflowConfig;

  /**
   * sets the number of seconds a workflow may exist after completion. As the process engine is an in-memory cache, the default policy is to expire and scrub the job hash as soon as it completes.
   */
  expire?: number;

  /**
   * system flag to indicate that the flow should remain open beyond main method completion while still emitting the 'job done' event
   */
  persistent?: boolean;

  /**
   * default is true; set to false to optimize workflows that do not require a `signal in`
   */
  signalIn?: boolean;

  /**
   * default is true; if false, will not await the execution
   */
  await?: boolean;

  /**
   * If provided, the job will initialize in a pending state, reserving
   * only the job ID (HSETNX) and persisting search and marker (if provided).
   * If a `resume` signal is sent before the specified number of seconds,
   * the job will resume as normal, transitioning to the adjacent children
   * of the trigger. If the job is not resumed within the number
   * of seconds specified, the job will be scrubbed. No dependencies
   * are added for a job in a pending state; however, dependencies
   * will be added after the job is resumed if relevant.
   */
  pending?: number;

  /**
   * Provide to set the engine name. This MUST be unique, so do not
   * provide unless it is guaranteed to be a unique engine/worker guid
   * when identifying the point of presence within the mesh.
   */
  guid?: string;
};

/**
 * Options for setting up a hook.
 * 'meshflow' is the default namespace if not provided;
 * similar to setting `appid` in the YAML
 */
type HookOptions = {
  /** Optional namespace under which the hook function will be grouped */
  namespace?: string;

  /** Optional task queue, needed unless 'entity' is provided */
  taskQueue?: string;

  /** Input arguments to pass into the hook */
  args: any[];

  /**
   * Optional entity name. If provided, applies as the workflowName,
   * taskQueue, and prefix. This scopes the FT.SEARCH index appropriately.
   * This is a convenience method but limits options.
   */
  entity?: string;

  /** Execution ID, also known as the job ID to hook into */
  workflowId?: string;

  /** The name of the user's hook function */
  workflowName?: string;

  /** Bind additional search terms immediately before hook reentry */
  search?: WorkflowSearchOptions;

  /** Hook function constraints (backoffCoefficient, maximumAttempts, maximumInterval) */
  config?: WorkflowConfig;
};

/**
 * Options for sending signals in a workflow.
 */
type SignalOptions = {
  /**
   * Task queue associated with the workflow
   */
  taskQueue: string;

  /**
   * Input data for the signal (any serializable object)
   */
  data: StringAnyType;

  /**
   * Execution ID, also known as the job ID
   */
  workflowId: string;

  /**
   * Optional name of the user's workflow function
   */
  workflowName?: string;
};

type ActivityWorkflowDataType = {
  activityName: string;
  arguments: any[];
  workflowId: string;
  workflowTopic: string;
};

type WorkflowDataType = {
  arguments: any[];
  workflowId: string;
  workflowTopic: string;
  workflowDimension?: string; //is present if hook (not main workflow)
  originJobId?: string; //is present if there is an originating ancestor job
  canRetry?: boolean;
  expire?: number;
};

type Connection = ProviderConfig | ProvidersConfig;

type ClientConfig = {
  connection: Connection;
};

type Registry = {
  [key: string]: Function;
};
type WorkerConfig = {
  /** Connection configuration for the worker */
  connection: Connection;

  /**
   * Namespace used in the app configuration, denoted as `appid` in the YAML
   * @default meshflow
   */
  namespace?: string;

  /** Task queue name, denoted as `subscribes` in the YAML (e.g., 'hello-world') */
  taskQueue: string;

  /** Target function or a record type with a name (string) and reference function */
  workflow: Function | Record<string | symbol, Function>;

  /** Additional options for configuring the worker */
  options?: WorkerOptions;

  /** Search options for workflow execution details */
  search?: WorkflowSearchOptions;

  /**
   * Provide to set the engine name. This MUST be unique, so do not
   * provide unless it is guaranteed to be a unique engine/worker guid
   * when identifying the point of presence within the mesh.
   */
  guid?: string;
};

type FindWhereQuery = {
  field: string;
  is: '=' | '==' | '>=' | '<=' | '[]';
  value: string | boolean | number | [number, number];
  type?: string; //default is TEXT
};

type FindOptions = {
  workflowName?: string; //also the function name
  taskQueue?: string;
  namespace?: string;
  index?: string;
  search?: WorkflowSearchOptions;
};

type FindWhereOptions = {
  options?: FindOptions;
  count?: boolean;
  query: FindWhereQuery[];
  return?: string[];
  limit?: {
    start: number;
    size: number;
  };
};

type FindJobsOptions = {
  /** The workflow name; include an asterisk for wilcard search; refer to Redis SCAN for the allowed format */
  match?: string;

  /**
   * application namespace
   * @default meshflow
   */
  namespace?: string;

  /** The suggested response limit. Reduce batch size to reduce the likelihood of large overages. */
  limit?: number;

  /** How many records to scan at a time */
  batch?: number;

  /** The start cursor; defaults to 0 */
  cursor?: string;
};

type WorkerOptions = {
  /** Log level: debug, info, warn, error */
  logLevel?: LogLevel;

  /** Maximum number of attempts, default 5 (HMSH_MESHFLOW_MAX_ATTEMPTS) */
  maximumAttempts?: number;

  /** Backoff coefficient for retry logic, default 10 (HMSH_MESHFLOW_EXP_BACKOFF) */
  backoffCoefficient?: number;

  /** Maximum interval between retries, default 120s (HMSH_MESHFLOW_MAX_INTERVAL) */
  maximumInterval?: string;
};

type ContextType = {
  workflowId: string;
  workflowTopic: string;
};

type FunctionSignature<T> = T extends (...args: infer A) => infer R
  ? (...args: A) => R
  : never;
type ProxyType<ACT> = {
  [K in keyof ACT]: FunctionSignature<ACT[K]>;
};

/**
 * Configuration settings for activities within a workflow.
 */
type ActivityConfig = {
  /** place holder setting; unused at this time (re: activity workflow expire configuration) */
  expire?: number;

  /** Start to close timeout for the activity; not yet implemented */
  startToCloseTimeout?: string;

  /** Configuration for specific activities, type not yet specified */
  activities?: any;

  /** Retry policy configuration for activities */
  retryPolicy?: {
    /** Maximum number of retry attempts, default is 5 (HMSH_MESHFLOW_MAX_ATTEMPTS) */
    maximumAttempts?: number;
    /** Factor by which the retry timeout increases, default is 10 (HMSH_MESHFLOW_MAX_INTERVAL) */
    backoffCoefficient?: number;
    /** Maximum interval between retries, default is '120s' (HMSH_MESHFLOW_EXP_BACKOFF) */
    maximumInterval?: string;
    /** Whether to throw an error on failure, default is true */
    throwOnError?: boolean;
  };
};

/**
 * The proxy response object returned from the activity proxy flow
 */
type ProxyResponseType<T> = {
  data?: T; //expected data
  $error?: StreamError;
  done?: boolean; //non-existent if error was thrown in transition (not during execution)
  jc: string;
  ju: string;
};

/**
 * The child flow response object returned from the  main flow during recursion
 */
type ChildResponseType<T> = {
  data?: T; //expected data
  $error?: StreamError;
  done?: boolean; //non-existent if error was thrown in transition (not during execution)
  jc: string;
  ju: string;
};

interface ClientWorkflow {
  start(options: WorkflowOptions): Promise<WorkflowHandleService>;
  signal(
    signalId: string,
    data: StringAnyType,
    namespace?: string,
  ): Promise<string>;
  hook(options: HookOptions): Promise<string>;
  getHandle(
    taskQueue: string,
    workflowName: string,
    workflowId: string,
    namespace?: string,
  ): Promise<WorkflowHandleService>;
  search(
    taskQueue: string,
    workflowName: string,
    namespace: string | null,
    index: string,
    ...query: string[]
  ): Promise<string[]>;
}

export {
  ActivityConfig,
  ActivityWorkflowDataType,
  ChildResponseType,
  ClientConfig,
  ClientWorkflow,
  ContextType,
  Connection,
  ProxyResponseType,
  ProxyType,
  Registry,
  SignalOptions,
  FindJobsOptions,
  FindOptions,
  FindWhereOptions,
  FindWhereQuery,
  HookOptions,
  SearchResults,
  WorkerConfig,
  WorkflowConfig,
  WorkerOptions,
  WorkflowSearchOptions,
  WorkflowDataType,
  WorkflowOptions,
  WorkflowContext,
};
