/**
 * Extract display text and metadata from native/original errors
 * (Video.js, Shaka, EME, Error, xhr, etc.).
 */

/**
 * @param {*} error
 * @returns {string|null}
 */
export function extractErrorMessage(error) {
  if (error == null) {
    return null;
  }

  if (typeof error === "string") {
    return error;
  }

  // Video.js player "error" event: { detail: ... }
  if (error.detail != null) {
    const fromDetail = extractErrorMessage(error.detail);
    if (fromDetail) {
      return fromDetail;
    }
  }

  // Video.js MediaError / Shaka Util.Error / standard Error
  if (typeof error.message === "string" && error.message.trim()) {
    return error.message.trim();
  }

  // Shaka error without message string but with code
  if (error.code != null && error.category != null) {
    return String(error.code);
  }

  // videojs.xhr style error object
  if (error.statusText) {
    return String(error.statusText);
  }
  if (error.status != null) {
    return `HTTP ${error.status}`;
  }

  if (error instanceof Error) {
    return error.message || error.name || null;
  }

  try {
    const serialized = JSON.stringify(error);
    if (serialized && serialized !== "{}") {
      return serialized;
    }
  } catch {
    // fall through
  }

  return String(error);
}

/**
 * @param {*} error - Original error from player, Shaka, DRM, catch, etc.
 * @returns {{ code: number|string|null, message: string|null, source: * }}
 */
export function normalizePlayerError(error) {
  const message = extractErrorMessage(error);
  const code =
    error && typeof error === "object"
      ? error.code ?? error.type ?? error.name ?? null
      : null;

  return {
    code,
    message,
    source: error,
  };
}

/**
 * Map arbitrary error codes to Video.js MediaError codes (0–5).
 * @param {number|string|null} code
 * @returns {number}
 */
export function toMediaErrorCode(code) {
  const numeric = typeof code === "number" ? code : Number(code);
  if (Number.isInteger(numeric) && numeric >= 0 && numeric <= 5) {
    return numeric;
  }
  return 0;
}

/**
 * Build a readable message from a Shaka Util.Error (or similar).
 * @param {*} shakaError
 * @returns {string|null}
 */
export function formatShakaErrorMessage(shakaError) {
  if (shakaError == null) {
    return null;
  }

  const code = shakaError.code;
  const message = extractErrorMessage(shakaError);

  if (code != null && message) {
    return `${code} - ${message}`;
  }

  return message;
}

/**
 * Resolve user-facing text from the current Video.js MediaError state.
 *
 * @param {import("video.js").Player} player
 * @param {(key: string) => string} [localize]
 * @returns {string|null}
 */
export function getErrorDisplayMessage(player, localize) {
  const vjsError = player?.error?.();
  if (!vjsError) {
    return null;
  }

  const rawMessage = vjsError.message?.trim();
  if (!rawMessage) {
    return null;
  }

  if (typeof localize === "function") {
    const localized = localize(rawMessage);
    if (localized && localized !== rawMessage) {
      return localized;
    }
  }

  return rawMessage;
}

/**
 * Report an error through the native Video.js player.error() API.
 * Triggers the player "error" event for ErrorOverlay and host listeners.
 *
 * @param {import("video.js").Player} player
 * @param {*} error
 * @returns {{ code: number|string|null, message: string|null, source: * }}
 */
export function reportPlayerError(player, error) {
  const payload = normalizePlayerError(error);

  if (error && error.category != null) {
    payload.message = formatShakaErrorMessage(error) || payload.message;
  }

  if (player && typeof player.error === "function") {
    player.error({
      code: toMediaErrorCode(payload.code),
      message: payload.message || undefined,
    });
  }

  return payload;
}

/**
 * Clear Video.js error state and hide overlay if present.
 *
 * @param {import("video.js").Player} player
 */
export function clearPlayerError(player) {
  if (player && typeof player.error === "function") {
    player.error(null);
  }

  const overlay = player?.getChild?.("ErrorOverlay");
  if (overlay && typeof overlay.hide === "function") {
    overlay.hide();
  }
}
