const offsetToISO8601 = offset => {

    const zeroize = number => number < 10 ? `0${number}` : `${number}`

    const sign = offset > 0 ? '-' : '+'
    const hours = zeroize(Math.floor(Math.abs(offset) / 60))
    const minutes = zeroize(Math.abs(offset) % 60)

    return `${sign}${hours}:${minutes}`
}

const toISOLocal = (d: Date) => {
    // format date as: 2022-07-04T16:50:28.334+02:00
    const z = n => ('0' + n).slice(-2);
    const zz = n => ('00' + n).slice(-3);
    let off = d.getTimezoneOffset();
    const sign = off > 0 ? '-' : '+';
    off = Math.abs(off);

    return d.getFullYear() + '-'
        + z(d.getMonth() + 1) + '-' +
        z(d.getDate()) + 'T' +
        z(d.getHours()) + ':' +
        z(d.getMinutes()) + ':' +
        z(d.getSeconds()) + '.' +
        zz(d.getMilliseconds()) +
        sign + z(off / 60 | 0) + ':' + z(off % 60);
}

const toLocalTimestamp = (d: Date): string => {
    const z = (n: number) => ('0' + n).slice(-2);
    const zz = (n: number) => ('00' + n).slice(-3);

    return `${d.getFullYear()}-${z(d.getMonth() + 1)}-${z(d.getDate())} ` +
        `${z(d.getHours())}:${z(d.getMinutes())}:${z(d.getSeconds())}.` +
        `${zz(d.getMilliseconds())}`;
};

/**
 * Checks whether the given date matches the format
 *
 * 1937-01-01T12:00:27.87+02:00
 * @param timestamp
 */
const isISOLocal = (timestamp: string): boolean => {
    const referenceDate = "1937-01-01T12:00:27.87+00:20"

    // millis may have two or three digits
    const correctLength = timestamp.length === referenceDate.length || timestamp.length === referenceDate.length + 1
    const isString = typeof timestamp === 'string'
    const isValidGeneralDate = !isNaN(new Date(timestamp).getTime())

    return correctLength && isString && isValidGeneralDate
}

const hasRepeatedIds = (collection: any[]) => {
    const seen = new Set();
    for (const item of collection) {
        // For objects, use JSON.stringify for shallow comparison
        const key = (typeof item === 'object' && item !== null)
            ? JSON.stringify(item)
            : item;
        if (seen.has(key)) {
            return true;
        }
        seen.add(key);
    }
    return false;
}

const checkObjectFieldEmptyiness = (obj: any, fieldsToCheck: string[]) => {
    for (const field of fieldsToCheck) {
        if (Array.isArray(obj[field])) {
            if (obj[field].length > 0 && obj[field].some((element: any) => typeof element === 'string' && element.trim().length === 0)) {
                throw new Error(`non empty strings in ${field} are mandatory`);
            }
        } else if ((typeof obj[field] === 'string') && (obj[field].trim().length === 0)) {
            throw new Error(`non empty ${field} are mandatory`)
        }
    }
}

const setToUndefinedIfNull = (object: any, keys: string[]) => {
    keys.forEach(key => {
        if (object[key] == null) {
            object[key] = undefined;
        }
    });
    return object; // Return the updated object
};

export {
    checkObjectFieldEmptyiness,
    hasRepeatedIds,
    isISOLocal,
    offsetToISO8601,
    toISOLocal,
    toLocalTimestamp,
    setToUndefinedIfNull
}
