import { commands, Disposable, OutputChannel, window, workspace } from 'vscode';
import { Pijul } from './pijul';
import { Repository } from './repository';
import { dispose } from './utils/disposableUtils';
/**
 * Interface representing the command creation options,
 * which determines which parameters are passed from the
 * command centre to the command.
 */
interface ICommandOptions {
  repository?: boolean
  // diff?: boolean
}
/**
 * Interface that describes the fields required to register
 * a new command with VS Code.
 */
interface ICommand {
  /** The command ID that the command will be registered under, matching the one in `package.json` */
  commandId: string
  /** The name of the function that the command invokes */
  key: string
  /** The function that will be invoked by the command */
  method: Function
  /** The options for the command creation */
  options: ICommandOptions
}
/**
 * An array which holds all of the commands which will
 * be registered by the command centre
 */
const Commands: ICommand[] = [];
/**
 * Decorator function for adding functions to the Commands array
 * for registration.
 * @param commandId The id that will be assigned to the command, matching the one in `package.json`
 * @param options The command options which control how it is created
 */
function command (commandId: string, options: ICommandOptions = {}): Function {
  return (_target: any, key: string, descriptor: any) => {
    if (!(typeof descriptor.value === 'function')) {
      throw new Error('not supported');
    }
    Commands.push({ commandId, key, method: descriptor.value, options });
  };
}
export class CommandCentre {
  private disposables: Disposable[];
  constructor (
    private readonly pijul: Pijul,
    private readonly repository: Repository,
    private readonly outputChannel: OutputChannel
  ) {
    this.disposables = Commands.map(({ commandId, key, method, options }) => {
      const command = this.createCommand(key, method, options);
      return commands.registerCommand(commandId, command);
    });
    this.outputChannel.appendLine('Activated Command Centre');
  }
  /**
   * Wraps a specific command function in a generic command function for error
   * and argument handling.
   * @param key The name of the function that the command invokes
   * @param func The actual function which will be wrapped in the generic one
   * @param options The options which determine which Command centre fields will be passed to the command
   */
  private createCommand (key: string, func: Function, options: ICommandOptions): (...args: any[]) => any {
    const result = async (...args: any[]): Promise<any> => {
      let result: Promise<any>;
      if (!options.repository) {
        result = Promise.resolve(func.apply(this, args));
      } else {
        // TODO: Handle multiple open repositories
        return await Promise.resolve(func.apply(this, [this.repository, ...args]));
      }
      // TODO: Generic command-level error handling
      return await result;
    };
    // Update the object so that directly invoking the command function on the command centre calls the wrapped version.
    (this as any)[key] = result;
    return result;
  }
  /**
   * Initialize a new Pijul repository in the current working directory.
   */
  @command('pijul.init')
  async init (): Promise<void> {
    // TODO: Select workspace folder to init in
    const cwd = workspace.workspaceFolders?.[0]?.uri.path;
    if (cwd) {
      await this.pijul.init(cwd);
      this.refresh(this.repository);
    } else {
      await window.showErrorMessage('No workspace folders open, cannot initialize Pijul repository');
    }
  }
  /**
   * Refresh the the state of the files within the extension.
   * @param repository The repository to run the file in
   */
  @command('pijul.refresh', { repository: true })
  async refresh (repository: Repository): Promise<void> {
    await repository.refreshStatus();
  }
  dispose (): void {
    this.disposables = dispose(this.disposables);
  }
}