type SearchItemCompletionOutcomeParams = {
  childSucceeded: boolean;
  finalSelectedProductCount?: number | null;
  qualifiedResultCount?: number | null;
  targetResultCount?: number | null;
  shortageMessage?: string;
};

export function buildSearchItemCompletionOutcome(params: SearchItemCompletionOutcomeParams) {
  const finalSelectedProductCount = Number(params.finalSelectedProductCount ?? 0);
  const qualifiedResultCount = Number(params.qualifiedResultCount ?? 0);
  const targetResultCount = Number(params.targetResultCount ?? 0);
  const hasSuccessfulMerchant = finalSelectedProductCount > 0 || qualifiedResultCount > 0;
  const countAsCompleted = params.childSucceeded || hasSuccessfulMerchant;
  const warningMessage = hasSuccessfulMerchant
    && targetResultCount > qualifiedResultCount
    && params.shortageMessage
    ? params.shortageMessage
    : "";

  return {
    countAsCompleted,
    countAsFailed: !countAsCompleted,
    status: countAsCompleted ? "completed" : "failed",
    warningMessage,
  };
}

export function isRetryableSearchTaskItemUpdateError(error: unknown) {
  const message = error instanceof Error ? error.message : String(error ?? "");
  return /fetch failed|network|ECONNRESET|ETIMEDOUT|EAI_AGAIN|ENOTFOUND/i.test(message);
}

function sleep(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

export async function retrySearchTaskItemUpdate<T>(
  operation: () => Promise<T>,
  options: { attempts?: number; baseDelayMs?: number; onRetry?: (error: unknown, attempt: number, attempts: number) => void } = {},
) {
  const attempts = Math.max(1, Math.floor(options.attempts ?? 3));
  const baseDelayMs = Math.max(0, Math.floor(options.baseDelayMs ?? 300));
  let lastError: unknown;

  for (let attempt = 1; attempt <= attempts; attempt += 1) {
    try {
      return await operation();
    } catch (error) {
      lastError = error;
      if (!isRetryableSearchTaskItemUpdateError(error) || attempt >= attempts) {
        throw error;
      }
      options.onRetry?.(error, attempt, attempts);
      await sleep(baseDelayMs * attempt);
    }
  }

  throw lastError;
}
