// Copyright (C) 2025 Keygraph, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License version 3
// as published by the Free Software Foundation.

/**
 * Workflow error formatting utilities.
 * Pure functions with no side effects — safe for Temporal workflow sandbox.
 */

import { ErrorCode } from '../types/errors.js';

/**
 * Maps an ApplicationFailure type string to a structured ErrorCode.
 *
 * Activities classify errors via classifyErrorForTemporal() and throw
 * ApplicationFailure with a type string. This function maps those strings
 * to stable ErrorCode values so consumers can switch on codes instead of
 * string-matching error messages.
 */
const ERROR_TYPE_TO_CODE: Record<string, ErrorCode> = {
  AuthenticationError: ErrorCode.AUTH_FAILED,
  ConfigurationError: ErrorCode.CONFIG_VALIDATION_FAILED,
  OutputValidationError: ErrorCode.OUTPUT_VALIDATION_FAILED,
  AgentExecutionError: ErrorCode.AGENT_EXECUTION_FAILED,
  GitError: ErrorCode.GIT_CHECKPOINT_FAILED,
  InvalidTargetError: ErrorCode.TARGET_UNREACHABLE,
};

export function classifyErrorCode(error: unknown): ErrorCode | undefined {
  let current: unknown = error;
  while (current instanceof Error) {
    if ('type' in current && typeof (current as { type: unknown }).type === 'string') {
      const code = ERROR_TYPE_TO_CODE[(current as { type: string }).type];
      if (code) return code;
    }
    current = (current as { cause?: unknown }).cause;
  }
  return undefined;
}

/** Maps Temporal error type strings to actionable remediation hints. */
const REMEDIATION_HINTS: Record<string, string> = {
  AuthenticationError: "Verify the selected provider's API key is valid and not expired.",
  ConfigurationError: 'Check your CONFIG file path and contents.',
  GitError: 'Check repository path and git state.',
  InvalidTargetError: 'Verify the target URL is correct and accessible.',
};

/**
 * Walk the .cause chain to find the innermost error with a .type property.
 * Temporal wraps ApplicationFailure in ActivityFailure — the useful info is inside.
 *
 * Uses duck-typing because workflow code cannot import @temporalio/activity types.
 */
function unwrapActivityError(error: unknown): {
  message: string;
  type: string | null;
} {
  let current: unknown = error;
  let typed: { message: string; type: string } | null = null;

  while (current instanceof Error) {
    if ('type' in current && typeof (current as { type: unknown }).type === 'string') {
      typed = {
        message: current.message,
        type: (current as { type: string }).type,
      };
    }
    current = (current as { cause?: unknown }).cause;
  }

  if (typed) {
    return typed;
  }

  return {
    message: error instanceof Error ? error.message : String(error),
    type: null,
  };
}

/**
 * Format a structured error string from workflow catch context.
 * Segments are delimited by | for multi-line rendering by WorkflowLogger.
 */
export function formatWorkflowError(error: unknown, currentPhase: string | null, currentAgent: string | null): string {
  const unwrapped = unwrapActivityError(error);

  // Phase context (first segment)
  let phaseContext = 'Pipeline failed';
  if (currentPhase && currentAgent && currentPhase !== currentAgent) {
    phaseContext = `${currentPhase} failed (agent: ${currentAgent})`;
  } else if (currentPhase) {
    phaseContext = `${currentPhase} failed`;
  }

  const segments: string[] = [phaseContext];

  if (unwrapped.type) {
    segments.push(unwrapped.type);
  }

  // Sanitize pipe characters from message to preserve delimiter format
  segments.push(unwrapped.message.replaceAll('|', '/'));

  if (unwrapped.type) {
    const hint = REMEDIATION_HINTS[unwrapped.type];
    if (hint) {
      segments.push(`Hint: ${hint}`);
    }
  }

  return segments.join('|');
}
