import type { FieldValues, Path, UseFormReturn } from "react-hook-form";

export interface ParsedFormError {
  /** Form-level message (non_field_errors, detail, network failure, etc.) */
  root?: string;
  /** Field-name → first human-readable error message */
  fields: Record<string, string>;
}

interface MaybeAxiosError {
  response?: { data?: unknown; status?: number };
  message?: string;
  isAxiosError?: boolean;
}

const ROOT_KEYS = new Set(["non_field_errors", "detail", "error", "errors"]);

function firstString(value: unknown): string | undefined {
  if (typeof value === "string") return value;
  if (Array.isArray(value)) {
    for (const v of value) {
      const s = firstString(v);
      if (s) return s;
    }
  }
  return undefined;
}

/**
 * Parses an axios / fetch error from a DRF backend into a normalized shape.
 *
 * Handles: per-field `{name: ["msg", ...]}`, per-field `{name: "msg"}`,
 * `{detail}`, `{non_field_errors}`, plain string body, and network failures
 * (no response).
 */
export function parseDrfErrors(err: unknown): ParsedFormError {
  const result: ParsedFormError = { fields: {} };
  const e = err as MaybeAxiosError;

  // Network or unknown failure — no response at all.
  if (!e?.response) {
    result.root = e?.message || "Síťová chyba. Zkuste to prosím znovu.";
    return result;
  }

  const data = e.response.data;

  // String body: `"Something went wrong"`
  if (typeof data === "string") {
    result.root = data;
    return result;
  }

  // Object body — DRF normal shape.
  if (data && typeof data === "object") {
    for (const [key, value] of Object.entries(data as Record<string, unknown>)) {
      const msg = firstString(value);
      if (!msg) continue;
      if (ROOT_KEYS.has(key)) {
        result.root = result.root ?? msg;
      } else {
        result.fields[key] = msg;
      }
    }
  }

  if (!result.root && Object.keys(result.fields).length === 0) {
    result.root = e.message || `Chyba ${e.response.status ?? ""}`.trim();
  }

  return result;
}

/**
 * Applies a parsed DRF error onto a react-hook-form instance:
 * - calls `setError` for each field with a matching name
 * - returns the root error string (if any) so the caller can render a banner
 * - field errors that don't map to a known form field collapse into `root`
 *   so nothing is silently dropped
 */
export function applyServerErrors<T extends FieldValues>(
  form: UseFormReturn<T>,
  err: unknown,
): string | undefined {
  const parsed = parseDrfErrors(err);
  const known = new Set(Object.keys(form.getValues() as object));
  const orphan: string[] = [];

  for (const [name, message] of Object.entries(parsed.fields)) {
    if (known.has(name)) {
      form.setError(name as Path<T>, { type: "server", message });
    } else {
      // Field name from server doesn't exist in this form — surface in banner
      // rather than swallow it.
      orphan.push(`${name}: ${message}`);
    }
  }

  if (orphan.length) {
    return parsed.root
      ? `${parsed.root}\n${orphan.join("\n")}`
      : orphan.join("\n");
  }
  return parsed.root;
}
