from __future__ import annotations

import collections.abc
from collections.abc import Mapping
import dataclasses
from typing import TypeGuard


def issequence(x: object) -> TypeGuard[collections.abc.Sequence[object]]:
    return isinstance(x, collections.abc.Sequence) and not isinstance(x, str)


def istext(x: object) -> TypeGuard[str]:
    return isinstance(x, str)


def ismapping(x: object) -> TypeGuard[Mapping[object, object]]:
    return isinstance(x, Mapping)


def isset(x: object) -> TypeGuard[set[object] | frozenset[object]]:
    return isinstance(x, set | frozenset)


def isnamedtuple(obj: object) -> bool:
    return isinstance(obj, tuple) and getattr(obj, "_fields", None) is not None


isdatacls = dataclasses.is_dataclass


def isattrs(obj: object) -> bool:
    return getattr(obj, "__attrs_attrs__", None) is not None


def isiterable(obj: object) -> TypeGuard[collections.abc.Iterable[object]]:
    try:
        iter(obj)  # type: ignore[call-overload]
        return not istext(obj)
    except Exception:
        return False


def has_default_eq(obj: object) -> bool:
    """Check if an instance of an object contains the default eq

    First, we check if the object's __eq__ attribute has __code__,
    if so, we check the equally of the method code filename (__code__.co_filename)
    to the default one generated by the dataclass and attr module
    for dataclasses the default co_filename is <string>, for attrs class, the __eq__ should contain "attrs eq generated"
    """
    # inspired from https://github.com/willmcgugan/rich/blob/07d51ffc1aee6f16bd2e5a25b4e82850fb9ed778/rich/pretty.py#L68
    if hasattr(obj.__eq__, "__code__") and hasattr(obj.__eq__.__code__, "co_filename"):
        code_filename = obj.__eq__.__code__.co_filename

        if isattrs(obj):
            return "attrs generated " in code_filename

        return code_filename == "<string>"  # data class
    return True
