import { NodeRegistry } from './nodeRegistry';
import { WorkflowDefinition } from './definition';
import { WorkflowExecutor } from './executor';
import { ILogger, IPersistence, IScheduler, IEventManager } from '../services'; // Assuming index exports
import { BaseNode } from '../nodes/baseNode';
import { NodeFunction, WorkflowDefinitionData } from '../types/config';
import { IWorkflowContext } from '../types/runtime';
import { ConfigurationError } from '../errors';

export interface FlowLabEngineOptions {
    logger?: ILogger;
    persistence?: IPersistence;
    scheduler?: IScheduler;
    eventManager?: IEventManager;
    // Global engine settings
    maxLoopIterations?: number;
}

export class FlowLabEngine {
  readonly nodeRegistry: NodeRegistry;
  readonly logger: ILogger;
  readonly persistence?: IPersistence;
  readonly scheduler?: IScheduler;
  readonly eventManager?: IEventManager;
  readonly options: Readonly<FlowLabEngineOptions>;

  // Allow storing definitions in memory or loading from persistence
  private workflowDefinitions = new Map<string, WorkflowDefinition>();

  constructor(options: FlowLabEngineOptions = {}) {
    this.options = {
        maxLoopIterations: 1000, // Default loop protection
        ...options
    };
    this.nodeRegistry = new NodeRegistry();
    this.persistence = options.persistence;
    this.scheduler = options.scheduler;
    this.eventManager = options.eventManager;

    // this.logger.info('FlowLabEngine initialized.');
    if (this.persistence) this.logger.info(`Persistence enabled: ${this.persistence.constructor.name}`);
    if (this.scheduler) this.logger.info(`Scheduler enabled: ${this.scheduler.constructor.name}`);
    if (this.eventManager) this.logger.info(`EventManager enabled: ${this.eventManager.constructor.name}`);
  }

  // --- Node Registration (Delegated) ---
  registerNode(node: BaseNode): void;
  registerNode(id: string, func: NodeFunction, description?: string): void;
  registerNode(idOrNode: string | BaseNode, func?: NodeFunction, description?: string): void {
    this.nodeRegistry.register(idOrNode as any, func as any, description);
  }

  // --- Workflow Definition ---
  defineWorkflow(id: string, name?: string, description?: string): WorkflowDefinition {
      const definition = new WorkflowDefinition(id, name, description);
      // Store definition in memory for execution by ID
      // TODO: Decide if registration should be explicit via `registerDefinition`
      this.workflowDefinitions.set(id, definition);
      this.logger.info(`Workflow definition '${id}' created.`);
      return definition;
  }

  // Explicitly register a definition (e.g., after building or loading)
  registerDefinition(definition: WorkflowDefinition | WorkflowDefinitionData): void {
      const def = definition instanceof WorkflowDefinition ? definition : this._hydrateDefinition(definition);
      if (this.workflowDefinitions.has(def.id)) {
         this.logger.warn(`Overwriting existing definition for workflow ID '${def.id}'.`);
      }
      this.workflowDefinitions.set(def.id, def);
      // Optionally save to persistence if configured
      if (this.persistence?.saveDefinition) {
          this.persistence.saveDefinition(def.getData())
              .catch(err => this.logger.error(`Failed to save definition '${def.id}' to persistence`, err));
      }
      this.logger.info(`Workflow definition '${def.id}' registered.`);
  }

  async getDefinition(id: string): Promise<WorkflowDefinition | undefined> {
      let definition = this.workflowDefinitions.get(id);
      if (!definition && this.persistence?.loadDefinition) {
          this.logger.debug(`Definition '${id}' not in memory, attempting to load from persistence...`);
          const data = await this.persistence.loadDefinition(id);
          if (data) {
              definition = this._hydrateDefinition(data);
              this.workflowDefinitions.set(id, definition); // Cache in memory
              this.logger.info(`Definition '${id}' loaded from persistence.`);
          } else {
              this.logger.warn(`Definition '${id}' not found in persistence.`);
          }
      }
      return definition;
  }

   // Re-create WorkflowDefinition instance from data (e.g., loaded from DB)
   private _hydrateDefinition(data: WorkflowDefinitionData): WorkflowDefinition {
      const definition = new WorkflowDefinition(data.id, data.name, data.description);
      // Re-add steps from data
      for (const stepId in data.steps) {
          // This assumes _addStepInternal exists and accepts StepConfig
          (definition as any)._addStepInternal(data.steps[stepId]);
      }
      if (data.startStepId) {
          definition.setStartStep(data.startStepId);
      }
      // TODO: Handle versioning?
      return definition;
   }


  // --- Workflow Execution ---
  createExecutor(): WorkflowExecutor {
    // Pass engine dependencies to the executor
    return new WorkflowExecutor({
      nodeRegistry: this.nodeRegistry,
      logger: this.logger,
      persistence: this.persistence,
      scheduler: this.scheduler,
      eventManager: this.eventManager,
      getWorkflowDefinition: this.getDefinition.bind(this), // Provide lookup function
      maxLoopIterations: this.options.maxLoopIterations,
    });
  }

  /**
   * Convenience method to define AND run a simple workflow immediately.
   * Combines definition and execution for simple cases (closer to Core API).
   * Warning: Definition is ephemeral unless explicitly registered/saved.
   */
  async runWorkflow(
      definitionBuilder: (builder: WorkflowDefinition) => void,
      input: Record<string, any>,
      contextExtras: Partial<IWorkflowContext> = {} // Allow passing tenantId, userId etc.
  ): Promise<IWorkflowContext> {
      // Create a temporary definition ID
      const tempId = `temp-${Date.now()}-${Math.random().toString(16).slice(2)}`;
      const builder = new WorkflowDefinition(tempId);
      definitionBuilder(builder); // Allow user to define steps via callback

      if (!builder.validate()) {
          throw new ConfigurationError(`Temporary workflow definition validation failed.`);
      }

      const executor = this.createExecutor();
      return executor.run(builder, input, contextExtras); // Execute directly
  }
}
