'use client';

import { useRouter, useSearchParams } from 'next/navigation';

type Primitive = string | number | boolean;
type ParamValue = Primitive | Record<string, any> | null;

/**
 * Encodes a string using base64.
 */
const encode = (input: string): string => btoa(encodeURIComponent(input));

/**
 * Decodes a base64 string.
 */
const decode = (input: string): string => decodeURIComponent(atob(input));

/**
 * Encodes a value using base64, supporting objects.
 */
const encodeValue = (value: ParamValue): string => {
    if (value === null) return '';
    if (typeof value === 'object') return encode(JSON.stringify(value));
    return encode(String(value));
};

/**
 * Decodes a base64 value and parses it if it's JSON.
 */
const decodeValue = (value: string): any => {
    try {
        const decoded = decode(value);
        return JSON.parse(decoded);
    } catch {
        return decode(value);
    }
};

/**
 * Prepares query params: skips only null or empty strings.
 */
const prepareQueryParams = (params: Record<string, ParamValue>): Record<string, string | null> => {
    const encodedParams: Record<string, string | null> = {};

    Object.entries(params).forEach(([key, value]) => {
        const shouldSkip = value === null || value === '';

        const encodedKey = encode(key);
        encodedParams[encodedKey] = shouldSkip ? null : encodeValue(value);
    });

    return encodedParams;
};

export const useQueryParams = () => {
    const router = useRouter();
    const searchParams = useSearchParams();

    const setQueryParams = (params: Record<string, ParamValue>) => {
        const current = new URLSearchParams(searchParams.toString());
        const cleanedParams = prepareQueryParams(params);

        Object.entries(cleanedParams).forEach(([encodedKey, encodedValue]) => {
            if (encodedValue === null) {
                current.delete(encodedKey);
            } else {
                current.set(encodedKey, encodedValue);
            }
        });

        router.push(`?${current.toString()}`);
    };

    const updateQueryParam = (key: string, value: ParamValue) => {
        setQueryParams({ [key]: value });
    };

    const deleteQueryParam = (key: string) => {
        setQueryParams({ [key]: null });
    };

    const deleteQueryParams = (keys: string[]) => {
        const paramsToDelete = keys.reduce((acc, key) => {
            acc[key] = null;
            return acc;
        }, {} as Record<string, null>);
        setQueryParams(paramsToDelete);
    };

    const currentParams: Record<string, any> = {};
    for (const [encodedKey, encodedValue] of searchParams.entries()) {
        try {
            const key = decode(encodedKey);
            currentParams[key] = decodeValue(encodedValue);
        } catch {
            // skip keys we can't decode
        }
    }

    type UpsertParams = [string, ParamValue] | Record<string, ParamValue>;

    const upsertQueryParam = (input: UpsertParams) => {
        const current = new URLSearchParams(searchParams.toString());

        const entries = Array.isArray(input) ? [input] : Object.entries(input);

        entries.forEach(([key, value]) => {
            const encodedKey = encode(key);

            if (value === null || value === '') {
                current.delete(encodedKey);
            } else {
                const encodedValue = encodeValue(value);
                current.set(encodedKey, encodedValue);
            }
        });

        router.push(`?${current.toString()}`);
    };

    return {
        setQueryParams,
        updateQueryParam,
        deleteQueryParam,
        deleteQueryParams,
        currentParams,
        upsertQueryParam,
    };
};
