import toast from 'react-hot-toast';

export type ApiError = unknown;

function isHttpError(err: unknown): err is { response?: { data?: { detail?: unknown } }; message?: string } {
  return typeof err === 'object' && err !== null && 'response' in (err as object);
}

function getFastApiDetail(err: unknown): string | null {
  if (!isHttpError(err)) return null;
  const detail = err.response?.data?.detail;
  if (typeof detail === 'string') return detail;
  if (Array.isArray(detail) && detail.length > 0) {
    const first = detail[0] as { msg?: string } | string;
    if (typeof first === 'string') return first;
    if (typeof first === 'object' && first !== null && 'msg' in first) {
      return (first as { msg?: string }).msg ?? null;
    }
  }
  return null;
}

function getNetworkMessage(err: unknown): string | null {
  if (!isHttpError(err)) return null;
  const msg = err.message;
  if (typeof msg === 'string' && msg && msg !== 'Network Error') return msg;
  return null;
}

/**
 * Normalize any thrown value (FastAPI HTTP error, network error, JS error)
 * to a short user-facing string. Falls back to a generic message so we
 * never surface a raw "[object Object]" or stack trace.
 */
export function extractErrorMessage(err: unknown, fallback = 'Something went wrong'): string {
  return (
    getFastApiDetail(err) ??
    getNetworkMessage(err) ??
    (err instanceof Error ? err.message : null) ??
    fallback
  );
}

/**
 * Show a toast for any error. Use as the MutationCache/QueryCache
 * global onError, or call directly from per-mutation onError handlers
 * that need a normalized message.
 */
export function showApiError(err: unknown, fallback?: string): void {
  showToast.error(extractErrorMessage(err, fallback));
}

export const showToast = {
  success: (message: string) => toast.success(message),
  error: (message: string) => toast.error(message),
  loading: (message: string) => toast.loading(message),
  dismiss: (toastId: string) => toast.dismiss(toastId),
  promise: <T,>(
    promise: Promise<T>,
    messages: { loading: string; success: string; error: string }
  ) => toast.promise(promise, messages),
  /**
   * Show a confirmation toast with an Undo button. The toast stays
   * visible for `ms` (default 5000) and the user can click Undo to
   * fire `onUndo`. If the user does nothing, the toast just dismisses.
   */
  undo: (message: string, onUndo: () => void, ms = 5000) => {
    return toast(
      (t) => (
        <span className="flex items-center gap-3">
          <span>{message}</span>
          <button
            type="button"
            onClick={() => {
              onUndo();
              toast.dismiss(t.id);
            }}
            className="font-semibold text-primary-600 hover:text-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-400 rounded px-1"
          >
            Undo
          </button>
        </span>
      ),
      {
        duration: ms,
        icon: '\u2715',
        style: { background: '#fff', color: '#404040' },
      }
    );
  },
};
