/**
 * Auth error codes, PDA-friendly message mapping, and error parsing utilities.
 *
 * All user-facing messages follow PDA-friendly language guidelines:
 * no alarming words like OVERDUE, URGENT, MUST, CRITICAL, REQUIRED, ERROR, FAILED.
 */

/** Union of all recognised auth error codes. */
export type AuthErrorCode =
  | "access_denied"
  | "invalid_credentials"
  | "server_error"
  | "network_error"
  | "rate_limited"
  | "session_expired"
  | "unknown";

/** A parsed, UI-ready representation of an auth error. */
export interface ParsedAuthError {
  code: AuthErrorCode;
  /** PDA-friendly message suitable for display to the user. */
  message: string;
  /** Whether the operation that caused this can be retried. */
  retryable: boolean;
}

/**
 * PDA-friendly error messages keyed by error code.
 * Uses calm, informational language throughout.
 */
const ERROR_MESSAGES: Record<AuthErrorCode, string> = {
  access_denied: "Authentication paused. Please try again when ready.",
  invalid_credentials: "The email and password combination wasn't recognized.",
  server_error: "The service is taking a break. Please try again in a moment.",
  network_error: "Unable to connect. Check your network and try again.",
  rate_limited: "You've tried a few times. Take a moment and try again shortly.",
  session_expired: "Your session ended. Please sign in again when ready.",
  unknown: "Authentication didn't complete. Please try again when ready.",
};

/** Error codes that are safe to retry automatically. */
const RETRYABLE_CODES: ReadonlySet<AuthErrorCode> = new Set<AuthErrorCode>([
  "network_error",
  "server_error",
]);

/** Set of recognised error code strings for fast membership testing. */
const KNOWN_CODES: ReadonlySet<string> = new Set<string>(Object.keys(ERROR_MESSAGES));

/**
 * Type-guard: checks whether a string value is a known {@link AuthErrorCode}.
 */
function isAuthErrorCode(value: string): value is AuthErrorCode {
  return KNOWN_CODES.has(value);
}

/**
 * Type-guard: checks whether a value looks like an HTTP response object
 * with a numeric `status` property.
 */
function isHttpResponseLike(value: unknown): value is { status: number } {
  return (
    typeof value === "object" &&
    value !== null &&
    "status" in value &&
    typeof (value as { status: unknown }).status === "number"
  );
}

/**
 * Map an HTTP status code to an {@link AuthErrorCode}.
 */
function httpStatusToCode(status: number): AuthErrorCode {
  // In auth context, both 401 and 403 indicate the user should re-authenticate
  if (status === 401 || status === 403) {
    return "invalid_credentials";
  }
  if (status === 429) {
    return "rate_limited";
  }
  if (status >= 500) {
    return "server_error";
  }
  return "unknown";
}

/**
 * Build a {@link ParsedAuthError} for the given code.
 */
function buildParsedError(code: AuthErrorCode): ParsedAuthError {
  return {
    code,
    message: ERROR_MESSAGES[code],
    retryable: RETRYABLE_CODES.has(code),
  };
}

/**
 * Parse an unknown error value into a structured, PDA-friendly
 * {@link ParsedAuthError}.
 *
 * Handles:
 * - `TypeError` whose message contains "fetch" -> `network_error`
 * - Generic `Error` objects with keyword-based message matching
 * - HTTP-response-shaped objects with a numeric `status` field
 * - Plain strings that match a known error code
 * - Anything else falls back to `unknown`
 */
export function parseAuthError(error: unknown): ParsedAuthError {
  // 1. TypeError with "fetch" in message -> network error
  if (error instanceof TypeError && error.message.toLowerCase().includes("fetch")) {
    return buildParsedError("network_error");
  }

  // 2. Generic Error objects — match on message keywords
  if (error instanceof Error) {
    const msg = error.message.toLowerCase();

    if (msg.includes("unauthorized") || msg.includes("forbidden")) {
      return buildParsedError("invalid_credentials");
    }
    if (msg.includes("rate limit") || msg.includes("too many")) {
      return buildParsedError("rate_limited");
    }
    if (
      msg.includes("internal server") ||
      msg.includes("service unavailable") ||
      msg.includes("bad gateway") ||
      msg.includes("gateway timeout")
    ) {
      return buildParsedError("server_error");
    }
    if (msg.includes("session") && msg.includes("expired")) {
      return buildParsedError("session_expired");
    }
    if (msg.includes("network") || msg.includes("connection")) {
      return buildParsedError("network_error");
    }

    return buildParsedError("unknown");
  }

  // 3. HTTP response-like objects (e.g. { status: 429 })
  if (isHttpResponseLike(error)) {
    return buildParsedError(httpStatusToCode(error.status));
  }

  // 4. Plain string matching a known error code (e.g. from URL query params)
  if (typeof error === "string") {
    if (isAuthErrorCode(error)) {
      return buildParsedError(error);
    }
    return buildParsedError("unknown");
  }

  // 5. Fallback
  return buildParsedError("unknown");
}

/**
 * Look up the PDA-friendly message for a given {@link AuthErrorCode}.
 * Returns the `unknown` message for any unrecognised code.
 */
export function getErrorMessage(code: AuthErrorCode): string {
  return ERROR_MESSAGES[code];
}
