import {
  useParams as useNextParams,
  useSearchParams as useNextSearchParams,
  useRouter,
} from "next/navigation";

import { z } from "zod";

import type { RouteBuilder } from "./makeRoute";

const _emptySchema = z.object({});

type PushOptions = Parameters<ReturnType<typeof useRouter>["push"]>[1];

export function usePush<
  Params extends z.ZodSchema,
  Search extends z.ZodSchema = typeof _emptySchema,
>(builder: RouteBuilder<Params, Search>) {
  const router = useRouter();
  return (
    p: z.input<Params>,
    search?: z.input<Search>,
    options?: PushOptions,
  ) => {
    router.push(builder(p, search), options);
  };
}

export function useParams<
  Params extends z.ZodSchema,
  Search extends z.ZodSchema = typeof _emptySchema,
>(builder: RouteBuilder<Params, Search>): z.output<Params> {
  const params = useNextParams();
  const res = builder.paramsSchema.safeParse(params);
  if (!res.success) {
    throw new Error(
      `Invalid route params for route ${builder.routeName}: ${res.error.message}`,
    );
  }
  // Zod infiere res.data como any, pero después de la validación exitosa es seguro
  // eslint-disable-next-line @typescript-eslint/no-unsafe-return
  return res.data as z.output<Params>;
}

export function useSearchParams<
  Params extends z.ZodSchema,
  Search extends z.ZodSchema = typeof _emptySchema,
>(builder: RouteBuilder<Params, Search>): z.output<Search> {
  const searchParams = useNextSearchParams();
  const res = builder.searchSchema.safeParse(
    convertURLSearchParamsToObject(searchParams),
  );
  if (!res.success) {
    throw new Error(
      `Invalid search params for route ${builder.routeName}: ${res.error.message}`,
    );
  }
  // Zod infiere res.data como any, pero después de la validación exitosa es seguro
  // eslint-disable-next-line @typescript-eslint/no-unsafe-return
  return res.data as z.output<Search>;
}

function convertURLSearchParamsToObject(
  params: Readonly<URLSearchParams> | null,
): Record<string, string | string[]> {
  if (!params) {
    return {};
  }

  const obj: Record<string, string | string[]> = {};

  for (const [key, value] of params.entries()) {
    if (params.getAll(key).length > 1) {
      obj[key] = params.getAll(key);
    } else {
      obj[key] = value;
    }
  }
  return obj;
}
