/**
 * Workflow utility functions
 */

import type { z } from 'zod'
import type { ExecutionContext } from '../base/types'
import type { WorkflowStep, ConditionalNext } from './types'
import { StepType } from './types'
import type {
  WorkflowExecutionContext,
  WorkflowFailureContext,
  StepStartedContext,
  StepCompletedContext,
  StepFailedContext,
  ConditionalRouteContext,
  ExecutionPathContext
} from './logging'
import { errorToString } from '../../../platform/utils/error'
import { truncateForLogging } from './log-truncate'
import { WorkflowStepError, WorkflowValidationError } from './errors'

/**
 * Validation utilities
 */

export function validateEntryPoint(steps: Record<string, WorkflowStep>, entryPoint: string): void {
  if (!(entryPoint in steps)) {
    throw new WorkflowValidationError(`Entry point step '${entryPoint}' not found in workflow`, {
      entryPoint
    })
  }
}

function findTerminalSteps(steps: Record<string, WorkflowStep>): WorkflowStep[] {
  return Object.values(steps).filter((step) => step.next === null)
}

export function validateTerminalSteps(steps: Record<string, WorkflowStep>, workflowId: string): void {
  const terminalSteps = findTerminalSteps(steps)
  if (terminalSteps.length === 0) {
    throw new WorkflowValidationError(`Workflow '${workflowId}' must have at least one terminal step (next: null)`, {
      workflowId
    })
  }
  // Validation passed - terminal steps exist
  // (Previously logged in development, removed to eliminate process.env dependency)
}

export function validateStepReferences(steps: Record<string, WorkflowStep>): void {
  for (const [id, step] of Object.entries(steps)) {
    if (step.next === null) {
      // Terminal step - no validation needed
      continue
    }

    if (step.next.type === StepType.LINEAR) {
      if (!(step.next.target in steps)) {
        throw new WorkflowValidationError(`Step '${id}' references non-existent target '${step.next.target}'`, {
          stepId: id,
          target: step.next.target
        })
      }
    } else if (step.next.type === StepType.CONDITIONAL) {
      const targets = [...step.next.routes.map((r: { target: string }) => r.target), step.next.default]
      for (const target of targets) {
        if (!(target in steps)) {
          throw new WorkflowValidationError(`Step '${id}' references non-existent target '${target}'`, {
            stepId: id,
            target
          })
        }
      }
    }
  }
}

/**
 * Execution utilities
 */

export function detectCycle(visited: Set<string>, executionPath: string[], currentStepId: string): void {
  if (visited.has(currentStepId)) {
    const cycle = executionPath.slice(executionPath.indexOf(currentStepId))
    throw new WorkflowStepError(`Cycle detected in workflow: ${cycle.join(' -> ')} -> ${currentStepId}`, {
      stepId: currentStepId,
      cycle: [...cycle, currentStepId]
    })
  }
}

export function validateTerminalOutput(stepId: string, output: unknown, outputSchema?: z.ZodSchema): void {
  // Early return if no schema provided (fire-and-forget workflow)
  if (!outputSchema) {
    return
  }

  try {
    outputSchema.parse(output)
  } catch (error) {
    throw new WorkflowValidationError(
      `Terminal step '${stepId}' produced invalid output: ${error instanceof Error ? error.message : String(error)}`,
      { stepId, validationError: error instanceof Error ? error.message : String(error) }
    )
  }
}

export async function determineNextStep(
  step: WorkflowStep,
  currentData: unknown,
  context: ExecutionContext
): Promise<string | null> {
  const { next } = step

  // Null means workflow ends here
  if (next === null) {
    return null
  }

  // Linear progression to specific step
  if (next.type === StepType.LINEAR) {
    return next.target
  }

  // Conditional routing
  if (next.type === StepType.CONDITIONAL) {
    return evaluateConditionalRoutes(step.id, next, currentData, context)
  }

  throw new WorkflowStepError(`Unknown next configuration type in step ${step.id}`, {
    stepId: step.id
  })
}

async function evaluateConditionalRoutes(
  stepId: string,
  next: ConditionalNext,
  currentData: unknown,
  context: ExecutionContext
): Promise<string> {
  // Evaluate conditions in order
  for (const route of next.routes) {
    try {
      if (route.condition(currentData)) {
        context.logger.debug(`Conditional route taken: ${stepId} -> ${route.target}`, {
          type: 'workflow',
          contextType: 'conditional-route',
          stepId,
          target: route.target
        } as ConditionalRouteContext)
        return route.target
      }
    } catch (error) {
      context.logger.warn(`Error evaluating condition in step ${stepId}: ${error}`, {
        type: 'workflow',
        contextType: 'conditional-route',
        stepId,
        target: '',
        error: errorToString(error)
      } as ConditionalRouteContext)
    }
  }

  // Use default if no conditions match
  context.logger.debug(`Using default route: ${stepId} -> ${next.default}`, {
    type: 'workflow',
    contextType: 'conditional-route',
    stepId,
    target: next.default
  } as ConditionalRouteContext)
  return next.default
}

/**
 * Graph analysis utilities
 */

/**
 * Logging helper utilities
 */

export function logWorkflowStart(context: ExecutionContext, workflowId: string, workflowName: string): void {
  context.logger.info(`Executing workflow: ${workflowId}`, {
    type: 'workflow',
    contextType: 'workflow-execution',
    executionId: context.executionId,
    workflowId,
    workflowName,
    organizationId: context.organizationId
  } as WorkflowExecutionContext)
}

export function logWorkflowSuccess(context: ExecutionContext, workflowId: string, executionPath: string[]): void {
  context.logger.info(`Workflow completed successfully: ${workflowId}`, {
    type: 'workflow',
    contextType: 'workflow-execution',
    executionId: context.executionId,
    workflowId,
    organizationId: context.organizationId,
    executionPath
  } as WorkflowExecutionContext)
}

export function logWorkflowFailure(context: ExecutionContext, workflowId: string, error: unknown): void {
  context.logger.error(`Execution failed: workflow/${workflowId}`, {
    type: 'workflow',
    contextType: 'workflow-failure',
    executionId: context.executionId,
    workflowId,
    error: errorToString(error)
  } as WorkflowFailureContext)
}

export function logStepStart(
  context: ExecutionContext,
  stepId: string,
  stepName: string,
  input: unknown,
  startTime: number
): void {
  context.logger.info(`Step started: ${stepName}`, {
    type: 'workflow',
    contextType: 'step-started',
    stepId,
    stepStatus: 'started',
    input: truncateForLogging(input),
    startTime
  } as StepStartedContext)
}

export function logStepSuccess(
  context: ExecutionContext,
  stepId: string,
  stepName: string,
  output: unknown,
  duration: number,
  isTerminal: boolean,
  startTime: number,
  endTime: number
): void {
  context.logger.info(`Step completed: ${stepName}`, {
    type: 'workflow',
    contextType: 'step-completed',
    stepId,
    stepStatus: 'completed',
    output: truncateForLogging(output),
    duration,
    isTerminal,
    startTime,
    endTime
  } as StepCompletedContext)
}

export function logStepFailure(
  context: ExecutionContext,
  stepId: string,
  stepName: string,
  error: unknown,
  duration: number,
  startTime: number,
  endTime: number
): void {
  context.logger.error(`Step failed: ${stepName}`, {
    type: 'workflow',
    contextType: 'step-failed',
    stepId,
    stepStatus: 'failed',
    error: errorToString(error),
    duration,
    startTime,
    endTime
  } as StepFailedContext)
}

export function logExecutionPath(context: ExecutionContext, executionPath: string[]): void {
  context.logger.info(`Workflow completed. Execution path: ${executionPath.join(' -> ')}`, {
    type: 'workflow',
    contextType: 'execution-path',
    executionPath
  } as ExecutionPathContext)
}
