/**
 * Error Parser
 * Routes errors to appropriate error classes based on step and context
 */

import {
  IgnoredSignatureStepError,
  ListAccountError,
  ListCurrencyError,
  NonceStepError,
  NotEnoughFunds,
  PayinExtraIdError,
  PayloadStepError,
  SignatureStepError,
  UnknownAccountError,
} from "./SwapError";

/**
 * Transaction step where error occurred
 */
export enum StepError {
  NONCE = "NonceStepError",
  PAYLOAD = "PayloadStepError",
  SIGNATURE = "SignatureStepError",
  IGNORED_SIGNATURE = "IgnoredSignatureStepError",
  CHECK_FUNDS = "CheckFundsStepError",
  LIST_ACCOUNT = "ListAccountStepError",
  LIST_CURRENCY = "ListCurrencyStepError",
  UNKNOWN_ACCOUNT = "UnknownAccountStepError",
  PAYIN_EXTRA_ID = "PayinExtraIdStepError",
}

/**
 * Input for error parsing
 */
export enum CustomErrorType {
  SWAP = "swap",
}

export type ParseError = {
  error: Error;
  step?: StepError;
  customErrorType?: CustomErrorType;
};

const DRAWER_CLOSED_ERROR_NAME = "DrawerClosedError";

const isDrawerClosedError = (error: Error): boolean => {
  if (error.name === DRAWER_CLOSED_ERROR_NAME) {
    return true;
  }

  const nestedName = (error as { cause?: { name?: string } }).cause?.name;
  return nestedName === DRAWER_CLOSED_ERROR_NAME;
};

/**
 * Maps step errors to error constructors
 */
const ErrorMap: Record<StepError, new (err?: Error) => Error> = {
  [StepError.NONCE]: NonceStepError,
  [StepError.PAYLOAD]: PayloadStepError,
  [StepError.SIGNATURE]: SignatureStepError,
  [StepError.IGNORED_SIGNATURE]: IgnoredSignatureStepError,
  [StepError.CHECK_FUNDS]: NotEnoughFunds,
  [StepError.LIST_ACCOUNT]: ListAccountError,
  [StepError.LIST_CURRENCY]: ListCurrencyError,
  [StepError.UNKNOWN_ACCOUNT]: UnknownAccountError,
  [StepError.PAYIN_EXTRA_ID]: PayinExtraIdError,
};

/**
 * Creates a step-specific error by wrapping the original error
 *
 * @param error - Original error that occurred
 * @param step - Step where error occurred (optional)
 * @returns Wrapped error or original error if no step specified
 */
export function createStepError({ error, step }: ParseError): Error {
  // If no step specified, return original error
  if (!step) {
    return error;
  }

  // Preserve DrawerClosedError so swap cancellations aren't reclassified.
  if (isDrawerClosedError(error)) {
    return error;
  }

  // Get error constructor for this step
  const ErrorConstructor = ErrorMap[step];

  if (!ErrorConstructor) {
    return error;
  }

  // Wrap original error in step-specific error class
  return new ErrorConstructor(error);
}

/**
 * Parses an error to determine the correct error class to return based on the context.
 */
export function parseError({ error, step, customErrorType }: ParseError): Error {
  if (!step || customErrorType !== CustomErrorType.SWAP) {
    return error;
  }

  return createStepError({ error, step });
}

export const hasMessage = (value: unknown): value is { message?: unknown } =>
  Boolean(value && typeof value === "object" && "message" in value);

export const toError = (value: unknown): Error => {
  if (value instanceof Error) {
    return value;
  }

  if (hasMessage(value) && typeof value.message === "string") {
    return new Error(value.message);
  }

  return new Error(
    typeof value === "string"
      ? value
      : (() => {
          try {
            return JSON.stringify(value);
          } catch {
            return String(value);
          }
        })(),
  );
};
