/**
 * Graph-based Workflow execution implementation
 */

import type { Contract, ExecutionContext } from '../base/types'
import type { WorkflowConfig, WorkflowStep, WorkflowDefinition } from './types'
import {
  validateEntryPoint,
  validateTerminalSteps,
  validateStepReferences,
  detectCycle,
  validateTerminalOutput,
  determineNextStep,
  logWorkflowStart,
  logWorkflowSuccess,
  logWorkflowFailure,
  logStepStart,
  logStepSuccess,
  logStepFailure,
  logExecutionPath
} from './utils'
import { errorToString } from '../../../platform/utils/error'
import { WorkflowStepError } from './errors'

/**
 * Workflow class with type-safe input/output
 *
 * @template TInput - Input type validated by contract.inputSchema
 * @template TOutput - Output type validated by contract.outputSchema
 */
export class Workflow<TInput = unknown, TOutput = unknown> {
  public readonly config: WorkflowConfig
  public readonly contract: Contract
  public readonly steps: Record<string, WorkflowStep>
  public readonly entryPoint: string

  // Derived properties (computed from definition)
  private readonly shouldGenerateOutput: boolean

  /**
   * Create a new workflow instance from definition
   *
   * @param definition - Workflow definition with config, contract, steps, and entryPoint
   */
  constructor(definition: WorkflowDefinition) {
    this.config = definition.config
    this.contract = definition.contract
    this.steps = definition.steps
    this.entryPoint = definition.entryPoint

    // Derive shouldGenerateOutput from outputSchema presence
    this.shouldGenerateOutput = !!definition.contract.outputSchema

    // Run all validations
    validateEntryPoint(this.steps, this.entryPoint)
    validateTerminalSteps(this.steps, this.config.resourceId)
    validateStepReferences(this.steps)
  }

  /**
   * Execute the workflow with graph-based flow control
   * Context is required for execution tracking, logging, and organization isolation
   * @returns Validated output matching contract.outputSchema, or null if no output schema
   */
  async execute(input: TInput, context: ExecutionContext): Promise<TOutput | null> {
    // Log workflow start
    logWorkflowStart(context, this.config.resourceId, this.config.name)

    try {
      // Validate input
      const validated = this.contract.inputSchema.parse(input) as TInput

      // Track visited steps to detect cycles
      const visited = new Set<string>()
      const executionPath: string[] = []

      // Start execution from entry point
      let currentData: unknown = validated
      let currentStepId: string | null = this.entryPoint

      while (currentStepId !== null) {
        // Cycle detection
        detectCycle(visited, executionPath, currentStepId)
        visited.add(currentStepId)
        executionPath.push(currentStepId)

        // Get current step
        const step = this.steps[currentStepId]
        if (!step) {
          throw new WorkflowStepError(`Step '${currentStepId}' not found in workflow`, {
            stepId: currentStepId,
            workflowId: this.config.resourceId,
            executionId: context.executionId,
            organizationId: context.organizationId,
            executionPath
          })
        }

        // Execute step
        const stepStartTime = Date.now()
        logStepStart(context, step.id, step.name, currentData, stepStartTime)

        try {
          // 1. Validate step input
          const validatedInput = step.inputSchema.parse(currentData)

          // 2. Execute the step handler with validated input
          const rawOutput = await step.handler(validatedInput, context)

          // 3. Validate step output
          currentData = step.outputSchema.parse(rawOutput)

          // 4. Validate terminal step output against workflow contract (if output generation enabled)
          if (step.next === null && this.shouldGenerateOutput) {
            validateTerminalOutput(step.id, currentData, this.contract.outputSchema)
          }

          // Log step completion
          const stepEndTime = Date.now()
          const duration = stepEndTime - stepStartTime
          logStepSuccess(
            context,
            step.id,
            step.name,
            currentData,
            duration,
            step.next === null,
            stepStartTime,
            stepEndTime
          )

          // Determine next step (currentData is now validated)
          currentStepId = await determineNextStep(step, currentData, context)
        } catch (error) {
          // Log step failure
          const stepEndTime = Date.now()
          const duration = stepEndTime - stepStartTime
          logStepFailure(context, step.id, step.name, error, duration, stepStartTime, stepEndTime)

          throw new WorkflowStepError(`Step failed [${step.id}:${step.name}]: ${errorToString(error)}`, {
            stepId: step.id,
            stepName: step.name,
            workflowId: this.config.resourceId,
            executionId: context.executionId,
            duration: stepEndTime - stepStartTime
          })
        }
      }

      // Log execution path
      logExecutionPath(context, executionPath)

      // Log workflow success
      logWorkflowSuccess(context, this.config.resourceId, executionPath)

      // Return null if no output generation (fire-and-forget), otherwise return validated result
      if (!this.shouldGenerateOutput) {
        return null
      }

      return currentData as TOutput
    } catch (error) {
      // Log workflow failure
      logWorkflowFailure(context, this.config.resourceId, error)
      throw error
    }
  }
}
