export interface CommandInterface<T, R> {
  execute(taskData?: T): Promise<R>;
  getDescription(): string | undefined;
}


/**
 * Example usage:
 * @example
 * ```typescript
 * // Create an instance of Command for a synchronous task.
 * const multiplyCommand = new Command<number, number>();
 *
 * // Set the task to multiply the input by 2.
 * multiplyCommand.setTask((num: number) => {
 * return num * 2;
 * });
 *
 * // Execute the task, passing in a number.
 * multiplyCommand.execute(5)
 * .then(result => console.log("Synchronous task result:", result))  // Expected output: 10
 * .catch(err => console.error(err));
 * ```
 */
export class Command<T, R> implements CommandInterface<T, R> {
  private task?: Task<T, R>;
  private description?: string;
  private agentName?: string;

  constructor(agentName: string) {
    this.agentName = agentName;
  }

  public async execute(taskData?: T): Promise<R> {
    if (!this.task) {
      throw new Error(
        "No task has been set. Please use setTask to define a task."
      );
    }

    return await this.task(taskData);
  }

  public getDescription(): string | undefined {
    return `${this.agentName}: ${this.description}`;
  }

  public getTask(): Task<T, R> | undefined {
    return this.task;
  }

  public setTask(task: Task<T, R>, description: string): void {
    this.task = task;
    this.description = description;
  }
}

/**
 * ChainCommand supports chaining several tasks.
 * When execute() is called, it runs all tasks in order,
 * feeding the output of task[i] as input to task[i+1].
 */
export class ChainCommand<T, R> implements CommandInterface<T, R> {
  private commands: Array<CommandInterface<any, any>> = [];

  // Add a task to the chain.
  public add<In, Out>(command: CommandInterface<In, Out>): this {
    this.commands.push(command);
    return this;
  }

  public getDescription(): string {
    return this.commands
      .map((cmd) => cmd.getDescription() || "Unnamed Command")
      .join(" -> ");
  }

  public async execute(input?: T): Promise<R> {
    let current: any = input;
    for (const cmd of this.commands) {
      current = await cmd.execute(current);
    }
    return current as R;
  }
}

export type Task<T, R> = (arg?: T) => R | Promise<R>;
