import { createHash } from "node:crypto"; import { isMediaInputFailure } from "@/lib/ai/runtime/request-failure-classification"; export const VIP98K_REFERENCE_STAGING_KEY_PREFIX = "uploads/vip98k-"; const VIP98K_REFERENCE_CACHE_OBJECT_PREFIX = "uploads/vip98k-cache"; function parseUrl(value: string) { try { return new URL(String(value || "").trim()); } catch { return null; } } export function isVip98kStagedReferenceUrl(value: string) { const parsed = parseUrl(value); return Boolean( parsed?.pathname.includes(`/${VIP98K_REFERENCE_STAGING_KEY_PREFIX}`), ); } export function getVip98kReferenceStagingObjectKey(mediaUrl: string) { const parsed = parseUrl(mediaUrl); if (parsed) { parsed.hash = ""; parsed.searchParams.delete("sg_media_retry"); } const cacheIdentity = parsed?.toString() || mediaUrl; const digest = createHash("sha256").update(cacheIdentity).digest("hex"); return `${VIP98K_REFERENCE_CACHE_OBJECT_PREFIX}/${digest}.jpg`; } export function isUrlWithinPublicBase(mediaUrl: string, publicBaseUrl: string) { const media = parseUrl(mediaUrl); const base = parseUrl(publicBaseUrl); if (!media || !base || media.host.toLowerCase() !== base.host.toLowerCase()) { return false; } const basePath = base.pathname.replace(/\/+$/, ""); if (!basePath) return true; return media.pathname === basePath || media.pathname.startsWith(`${basePath}/`); } export function shouldStageVip98kReferenceUrl(params: { mediaUrl: string; sourcePublicUrl: string; targetPublicUrl?: string; }) { if ( params.targetPublicUrl && isUrlWithinPublicBase(params.mediaUrl, params.targetPublicUrl) ) { return false; } return isUrlWithinPublicBase(params.mediaUrl, params.sourcePublicUrl); } export function getStorageObjectKeyFromPublicUrl( mediaUrl: string, publicBaseUrl: string, ) { if (!isUrlWithinPublicBase(mediaUrl, publicBaseUrl)) return null; const media = parseUrl(mediaUrl); const base = parseUrl(publicBaseUrl); if (!media || !base) return null; const basePath = base.pathname.replace(/\/+$/, ""); const encodedKey = media.pathname.slice(basePath.length).replace(/^\/+/, ""); if (!encodedKey) return null; try { return decodeURIComponent(encodedKey); } catch { return encodedKey; } } export function getVip98kStagedMediaRetryDelay(params: { status: number; message: string; attempt: number; maxAttempts: number; stagedReferenceCount: number; }) { if ( params.status !== 400 || params.stagedReferenceCount < 1 || params.attempt >= params.maxAttempts || !isMediaInputFailure(params.message) ) { return null; } return params.attempt * 20_000; } export function getVip98kSubmissionRetryDelay(params: { status: number; message: string; attempt: number; maxAttempts: number; retryAfterMs?: number | null; }) { const recoverableSubmission = /pending\s+safe\s+reconciliation/i.test(params.message) || /request\s+outcome\s+(?:is\s+)?unknown/i.test(params.message) || /final\s+outcome\s+could\s+not\s+be\s+confirmed/i.test(params.message); if ( params.status !== 503 || params.attempt >= params.maxAttempts || !recoverableSubmission ) { return null; } const providerDelay = Number(params.retryAfterMs); if (Number.isFinite(providerDelay) && providerDelay > 0) { return Math.min(Math.max(providerDelay, 1_000), 60_000); } return Math.min(params.attempt * 15_000, 30_000); } export function addVip98kReferenceRetryToken(mediaUrl: string, token: string) { const parsed = parseUrl(mediaUrl); if (!parsed) return mediaUrl; parsed.searchParams.set("sg_media_retry", token); return parsed.toString(); }