import 'package:dart_framework/utils/system_util.dart';
import 'package:oto/cli/cli.dart';
import 'package:oto/oto/application.dart';
import 'package:oto/oto/commands/command.dart';
import 'package:oto/oto/core/execution_context.dart';
import 'package:oto/oto/data/command_data.dart';
import 'package:oto/oto/pipeline/pipeline.dart';
import 'package:oto/oto/pipeline/pipeline_condition.dart';

/// Base class for all pipeline executors.
///
/// Example YAML (workflow snippet):
/// ```yaml
/// workflow:
///   - exe: buildIos          # execute a command by ID
///   - async: uploadSymbols   # execute a command asynchronously (non-blocking)
/// ```
abstract class PipelineExecutor {
  ExecutionContext? context;
  String workflowKey = '';

  ExecutionContext get runtimeContext =>
      context ?? Application.instance.context;
  String get stepType => workflowKey;
  String? get stepCommandId => null;
  String? get stepCommandType => null;

  //set & validate data
  PipelineValidateResult initialize(Map<String, dynamic> set);
  Future execute();

  static PipelineValidateResult validateTasks(List<dynamic>? list,
      {ExecutionContext? context}) {
    var result = PipelineValidateResult();
    if (list != null) {
      return Pipeline.pipelineInitialize(list, context: context);
    }
    return result;
  }

  static Future printCondition(String syntax, String condition, String postfix,
      ConditionCheckResult result) async {
    await CLI.println('[Pipeline-$syntax]', color: Color.magentaStrong);
    Application.log(
        ' - Condition: $condition\n - Values:    ${result.conditionValue}\n - Result:    ${result.result} ===> $postfix');

    return simpleFuture;
  }
}

/// Executes a single command synchronously by its command ID.
///
/// Example YAML:
/// ```yaml
/// - exe: buildIos
/// ```
class PipelineExe extends PipelineExecutor {
  late String commandID;
  String printPrefix = '';

  @override
  String? get stepCommandId => commandID;

  @override
  String? get stepCommandType =>
      runtimeContext.dataCommandMap[commandID]?.command.name;

  @override
  PipelineValidateResult initialize(Map<String, dynamic> set) {
    var result = PipelineValidateResult();
    final value = set.values.first;
    if (value == null) {
      result.enable = false;
      result.message = 'Pipeline Workflow\'s "Command ID" is (null)';
    } else if (value is! String || value.trim().isEmpty) {
      result.enable = false;
      result.message =
          'exe: command id must be a non-empty string, got: ${value.runtimeType}.';
    } else {
      commandID = value;
      if (!runtimeContext.dataCommandMap.containsKey(commandID)) {
        result.enable = false;
        result.message =
            'The "$commandID" command does not exist in the command list.';
      }
      updateState(CommandState.ready);
    }
    return result;
  }

  Future<DataCommand> get commandData async {
    var commandData = runtimeContext.dataCommandMap[commandID]!;
    await Application.instance.printBuildStep(
        '${printPrefix}Phase Start: ${commandData.name} ($commandID)',
        Color.green);
    return dataFutrue(commandData);
  }

  @override
  Future execute() async {
    updateState(CommandState.progress);
    var data = await commandData;
    var command = Command.byType(data.command);
    command.id = commandID;
    command.context = runtimeContext;
    await command.execute(data);
    updateState(CommandState.complete);
    return simpleFuture;
  }

  void updateState(CommandState state) {
    runtimeContext.commandStates[commandID] = state;
  }
}

/// Executes a command asynchronously (fire-and-forget, does not wait for completion).
///
/// Example YAML:
/// ```yaml
/// - async: uploadSymbols
/// ```
class PipelineAsync extends PipelineExe {
  @override
  Future execute() async {
    printPrefix = 'Async ';
    updateState(CommandState.progress);
    executeAsync(await commandData);
    return simpleFuture;
  }

  void executeAsync(DataCommand data) async {
    var command = Command.byType(data.command);
    command.context = runtimeContext;
    await command.execute(data);
    updateState(CommandState.complete);
  }
}
