import {
  useMutation,
  useQueryClient,
  type UseMutationOptions,
} from "@tanstack/react-query";
import apiFetch from "../../utils/api";

export interface ApiError extends Error {
  status?: number;
}

/**
 * Full success envelope (`data` + server `message`) — the result shape of a
 * mutation created with `envelope: true`. Used by the document detail pages
 * (offers, issued orders) to pass the server's Czech success message through
 * to the toast instead of a hardcoded client string.
 */
export interface ApiEnvelope<T> {
  data?: T;
  message?: string;
}

/**
 * Czech-safe error message for toasts: returns the server-provided message
 * from an `ApiError` (those carry `.status` and a Czech `error` string), and
 * falls back when the message is a client-generated English string — network
 * failures ("Failed to fetch" has no status), "Invalid JSON response",
 * "Unauthorized" (401) or the generic "Request failed (NNN)".
 */
export function apiErrorMessage(err: unknown, fallback: string): string {
  const status = (err as ApiError | null | undefined)?.status;
  const message = err instanceof Error ? err.message : "";
  if (!message || status === undefined || status === 401) return fallback;
  if (/^Request failed \(\d+\)$/.test(message)) return fallback;
  return message;
}

export type HttpMethod = "POST" | "PUT" | "PATCH" | "DELETE";

interface ApiResponseBody<T> {
  success: boolean;
  data?: T;
  error?: string;
  message?: string;
}

async function performMutation<TIn, TOut>(opts: {
  url: string | ((input: TIn) => string);
  method: HttpMethod | ((input: TIn) => HttpMethod);
  body?: TIn;
  envelope?: boolean;
}): Promise<TOut> {
  const url =
    typeof opts.url === "function" ? opts.url(opts.body as TIn) : opts.url;
  const method =
    typeof opts.method === "function"
      ? opts.method(opts.body as TIn)
      : opts.method;
  const response = await apiFetch(url, {
    method,
    headers:
      opts.body !== undefined
        ? { "Content-Type": "application/json" }
        : undefined,
    body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
  });

  if (response.status === 401) {
    const err: ApiError = new Error("Unauthorized");
    err.status = 401;
    throw err;
  }

  let result: ApiResponseBody<TOut>;
  try {
    result = (await response.json()) as ApiResponseBody<TOut>;
  } catch {
    throw new Error("Invalid JSON response");
  }

  if (!response.ok || !result.success) {
    const err: ApiError = new Error(
      result.error || `Request failed (${response.status})`,
    );
    err.status = response.status;
    throw err;
  }

  if (opts.envelope) {
    return { data: result.data, message: result.message } as TOut;
  }
  return result.data as TOut;
}

export interface ApiMutationOptions<TIn, TOut> {
  url: string | ((input: TIn) => string);
  method: HttpMethod | ((input: TIn) => HttpMethod);
  /** Query-key prefixes to invalidate on success. Broad per CLAUDE.md. */
  invalidate?: readonly string[];
  /**
   * When true, the mutation resolves with the full `ApiEnvelope` (`{ data,
   * message }`) instead of the bare `data` — declare `TOut` as
   * `ApiEnvelope<...>` accordingly.
   */
  envelope?: boolean;
}

/**
 * Hook that wraps `useMutation` with `apiFetch` and broad-invalidation.
 *
 * Usage:
 *   const createCategory = useApiMutation<CategoryForm, WarehouseCategory>({
 *     url: "/api/admin/warehouse/categories",
 *     method: "POST",
 *     invalidate: ["warehouse"],
 *   });
 *   createCategory.mutate(form, {
 *     onSuccess: () => { ... },
 *   });
 *
 * The returned `data` from a successful mutation is `TOut`. Errors are thrown
 * as `ApiError` (with `.status` attached) so callers can branch on HTTP code.
 */
export function useApiMutation<TIn, TOut>(
  opts: ApiMutationOptions<TIn, TOut> &
    Omit<UseMutationOptions<TOut, ApiError, TIn>, "mutationFn">,
) {
  const { url, method, invalidate, envelope, onSuccess, ...rest } = opts;
  const queryClient = useQueryClient();

  return useMutation<TOut, ApiError, TIn, unknown>({
    mutationFn: (input: TIn) =>
      performMutation<TIn, TOut>({ url, method, body: input, envelope }),
    ...rest,
    onSuccess: (data, variables, onMutateResult, context) => {
      if (invalidate) {
        for (const key of invalidate) {
          queryClient.invalidateQueries({ queryKey: [key] });
        }
      }
      // Forward to user-provided onSuccess with the 4-arg signature expected by TanStack.
      (
        onSuccess as
          | ((d: TOut, v: TIn, r: unknown, c: unknown) => void)
          | undefined
      )?.(data, variables, onMutateResult, context);
    },
  });
}
