const isRecord = <K extends string | number | symbol>(obj: unknown): obj is Record<K, unknown> => (
    typeof obj === 'object' &&
    !Array.isArray(obj) &&
    Object.getOwnPropertySymbols(obj).length === 0 && // We don't like symbols as keys here
    obj !== null
)

type Primitive = 'bigint' | 'boolean' | 'function' | 'number' | 'object' | 'string' | 'symbol' | 'undefined'

type PropertyGuard = Primitive | `${Primitive}?` | ((obj: unknown) => boolean)

type PropertiesGuards = Record<
    string | number | symbol,
    PropertyGuard>

const isObject = <T>(obj: unknown, properties: PropertiesGuards): obj is T => (
    typeof obj === 'object' &&
    obj !== null &&
    // Does not actually iterate over symbols. Hope, will not need to use them
    Object.entries(properties).every(([name, guard]) => {
        const param: unknown = Reflect.get(obj, name)

        console.log(name, param, guard)

        if (typeof guard === 'function') {
            return guard(param)
        }

        if (guard[guard.length - 1] === '?')
            return (
                (param !== undefined && param !== null && typeof param === guard.slice(0, -1)) ||
                param === undefined || param === null
            )

        return typeof param === guard
    })
)

const isConst = <T>(val: T) => (obj: unknown): obj is T => (
    obj === val ||
    (
        typeof obj === 'number' && isNaN(obj) &&
        typeof val === 'number' && isNaN(val)
    )
)

const isLiteralUnion = <T extends readonly string[]>(obj: unknown, list: T): obj is T[number] => (
    typeof obj === 'string' &&
    list.includes(obj as T[number])
)

const isArrayOf = <T>(obj: unknown, itemGuard: ((obj: unknown) => obj is T)): obj is T[] => (
    Array.isArray(obj) &&
    obj.every(itemGuard)
)

const isString = (obj: unknown): obj is string => typeof obj === 'string'

type SetState<T> = React.Dispatch<React.SetStateAction<T>>

export type { SetState }

export { isRecord, isObject, isConst, isLiteralUnion, isArrayOf, isString }
