# coding: utf-8


import inspect
from typing import Any, Union, Callable, Optional, Dict
from runpy import run_path
import ast
import string
import sys
import tokenize
from io import StringIO
import textwrap
import tracemalloc
import re
from types import (
    ModuleType,
    FunctionType,
    MethodType,
    TracebackType,
    FrameType,
    DynamicClassAttribute as DCA,
    BuiltinFunctionType as BFT,
)
from inspect import (
    ismodule,
    isclass,
    ismethod,
    isfunction,
    istraceback,
    isframe,
    iscode,
    getattr_static,
)
from .utils import (
    __load__,
    __defs__,
    __imports__,
    __classes__,
    __replace__,
    __dump__,
)
from .._signature import Signature
from ..ImporterException import SourceNotFoundError

try:
    import python_minifier
except ImportError:
    python_minifier = None


class Source:
    """
    A comprehensive code analysis class that provides various properties and methods
    to extract and analyze information from Python source code.

    This class can analyze modules, classes, functions, methods, tracebacks,
    frames, and code objects, providing insights into source code, arguments,
    docstrings, decorators, type hints, and more.

    Args:
                    object (Any): The Python object to analyze (module, class, function, etc.)

    Raises:
                    SourceNotFoundError: When source code cannot be located
                    TypeError: When an unsupported object type is provided
    """

    def __init__(self, object: Any) -> None:
        """
        Initialize the Source analyzer with a Python object.

        Args:
                        object (Any): The Python object to analyze (module, class, function,
                                                                         method, traceback, frame, or code object)
        """
        tracemalloc.start()
        self.obj = object
        self.name = getattr(object, "__name__", None)
        self.type = type(self.obj).__name__

    @property
    def parent(self) -> Union[str, None]:
        """Finding the parent"""
        obj = self.obj
        try:
            if isclass(obj):
                # For classes: The parent is the module that contains it
                module_name = getattr(obj, "__module__", None)
                if module_name:
                    return sys.modules.get(module_name).__name__

            elif isfunction(obj) or ismethod(obj):
                # For functions: The parent is the class or module that contains it
                return getattr(obj, "__objclass__", obj.__globals__)["__name__"]

            elif ismodule(obj):
                # For modules: The parent is the main module
                return sys.modules.get("__main__").__name__

        except (AttributeError, KeyError):
            pass

        return None

    @property
    def source(self) -> str:
        """
        Extract and return the dedented source code of the object.

        Returns:
                        str: The dedented source code as a string

        Raises:
                        SourceNotFoundError: When source code cannot be found
                        TypeError: When object type is not supported for source extraction
        """
        try:
            return textwrap.dedent(inspect.getsource(self.obj))
        except OSError:
            raise SourceNotFoundError("file source not found") from None
        except TypeError:
            raise TypeError(
                f"class, module, method, function, traceback, frame or code object can be object, got <{type(self.obj).__name__}>"
            ) from None

    def sdump(self, filesave: str) -> None:
        """Save a file for the current source code"""
        __dump__(filesave, self.source)

    def fdump(self, filesave: str) -> None:
        """Save a file for the full contents of the current file"""
        __dump__(filesave, self.read)

    def args(self, optional: bool = False) -> list:
        """
        Extract function and method arguments from the source code.

        Returns a list of tuples where each tuple contains:
        - For functions/methods: (function_name, [argument1, argument2, ...])
        - For classes: (class_name, [__init__ arguments])

        Returns:
                        list: List of tuples containing names and their arguments
        """
        tree = ast.parse(self.source)
        args = []

        for node in ast.walk(tree):
            if isinstance(node, ast.FunctionDef) and node.args:
                subargs = []
                for arg in node.args.args:
                    subargs.append(arg.arg)
                if optional:
                    if node.args.kwonlyargs:
                        for arg in node.args.kwonlyargs:
                            subargs.append(arg.arg)
                    if node.args.vararg:
                        for arg in node.args.vararg:
                            subargs.append(arg.arg)
                args.append((node.name, subargs))

            elif isinstance(node, ast.ClassDef):
                init_args = []
                for body_item in node.body:
                    if (
                        isinstance(body_item, ast.FunctionDef)
                        and body_item.name == "__init__"
                    ):
                        for arg in body_item.args.args:
                            init_args.append(arg.arg)
                if init_args:
                    args.append((node.name, init_args))

        return args

    @property
    def docstrings(self) -> list:
        """
        Extract all docstrings from the source code.

        Returns a list of tuples containing:
        - For modules, classes, functions: (name, docstring)
        - Docstrings are extracted using ast.get_docstring()

        Returns:
                        list: List of tuples containing names and their docstrings
        """
        tree = ast.parse(self.source)
        docs = []

        for node in ast.walk(tree):
            if isinstance(node, (ast.FunctionDef, ast.ClassDef, ast.Module)):
                name = (
                    node.name
                    if not isinstance(node, ast.Module)
                    else ast.Module.__name__
                )
                docs.append((name, ast.get_docstring(node)))

        return docs

    def getbody(self, cls_or_func_name: str) -> Union[str, None]:
        """
        Extract the full body of a class or function from the source code by its name.

        Args:
                cls_or_func_name (str): Name of the class or function to extract.

        Returns:
                str | None: The unparsed source code of the class/function if found, otherwise None.

        Raises:
                TypeError: If `cls_or_func_name` is not a string.
        """
        if not isinstance(cls_or_func_name, str):
            raise TypeError(f"expected str, got <{type(cls_or_func_name).__name__}>")

        tree = ast.parse(self.source)
        target_name = cls_or_func_name.lower()

        for node in ast.walk(tree):
            if (
                isinstance(node, ast.FunctionDef) and node.name.lower() == target_name
            ) or (isinstance(node, ast.ClassDef) and node.name.lower() == target_name):
                return ast.unparse(node)
        return None

    @property
    def decorators(self) -> list:
        """
        Extract all decorators applied to classes and functions.

        Returns a list of tuples where each tuple contains:
        - (decorated_name, [decorator1, decorator2, ...])
        - Supports both simple names (e.g., @decorator) and attribute access (e.g., @module.decorator)

        Returns:
                        list: List of tuples containing decorated names and their decorators
        """
        decorators = []

        tree = ast.parse(self.source)
        for node in ast.walk(tree):
            if (
                isinstance(node, (ast.ClassDef, ast.FunctionDef))
                and node.decorator_list
            ):
                decors = []
                for decor in node.decorator_list:
                    # decorator is not always ast.Name, sometimes ast.Attribute
                    if isinstance(decor, ast.Name):
                        decors.append(decor.id)
                    elif isinstance(decor, ast.Attribute):
                        decors.append(ast.unparse(decor))
                decorators.append((node.name, decors))
        return decorators

    @property
    def hints(self) -> dict:
        """
        Extract type hints for function arguments and return types.

        Returns a dictionary where:
        - Keys are function names
        - Values are dictionaries containing:
                        - Argument names as keys and their type hints as values
                        - 'return' key for return type hints

        Returns:
                        dict: Nested dictionary containing type hint information
        """
        tree = ast.parse(self.source)
        hints = {}

        for node in ast.walk(tree):
            if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
                func_hints = {}

                # arguments
                for arg in node.args.args:
                    if arg.annotation:
                        func_hints[arg.arg] = ast.unparse(arg.annotation)
                    else:
                        func_hints[arg.arg] = None

                # return type
                if node.returns:
                    func_hints["return"] = ast.unparse(node.returns)
                else:
                    func_hints["return"] = None

                hints[node.name] = func_hints

        return hints

    @property
    def file(self) -> str:
        """
        Return the source or compiled file where an object was defined.

        Handles various object types:
        - Modules: Returns __file__ attribute
        - Classes: Returns the module file where class is defined
        - Methods/Functions: Converts to code object and gets co_filename
        - Tracebacks/Frames: Navigates to underlying code object

        Returns:
                        str: File path where the object is defined

        Raises:
                        TypeError: For built-in modules/classes without files
                        OSError: For __main__ classes without source files
        """
        obj = self.obj

        # Module
        if ismodule(obj):
            if hasattr(obj, "__file__") and obj.__file__:
                return obj.__file__
            raise TypeError(f"{obj!r} is a built-in module (no file).")

        # Class
        if isclass(obj):
            module_name = getattr(obj, "__module__", None)
            if module_name:
                module = sys.modules.get(module_name)
                if module and hasattr(module, "__file__") and module.__file__:
                    return module.__file__
                if module_name == "__main__":
                    raise OSError("source code not available for __main__ class.")
            raise TypeError(f"{obj!r} is a built-in class (no file).")

        # Method -> convert to function
        if ismethod(obj):
            obj = obj.__func__

        # Function -> code object
        if isfunction(obj):
            obj = obj.__code__

        # Traceback -> frame -> code
        if istraceback(obj):
            obj = obj.tb_frame
        if isframe(obj):
            obj = obj.f_code

        # Source object -> get filename
        if iscode(obj):
            return obj.co_filename

        raise TypeError(
            f"expected module, class, method, function, traceback, frame, or code object, got <{type(obj).__name__}>"
        )

    @property
    def variables(self) -> list:
        """
        Extract all variable names from assignment statements.

        Includes:
        - Simple assignments (x = 1)
        - Type-annotated assignments (x: int = 1)
        - Augmented assignments (x += 1)

        Returns:
                        list: List of variable names found in assignments
        """
        tree = ast.parse(self.source)
        variables = []

        for node in ast.walk(tree):
            if isinstance(node, ast.Assign):
                for target in node.targets:
                    if isinstance(target, ast.Name):
                        variables.append(target.id)
            elif isinstance(node, ast.AnnAssign):  # for type hints
                if isinstance(node.target, ast.Name):
                    variables.append(node.target.id)
            elif isinstance(node, ast.AugAssign):  # for +=, -=, ...
                if isinstance(node.target, ast.Name):
                    variables.append(node.target.id)

        return variables

    def isproperty(self) -> bool:
        """
        Check if the object is a property.

        Returns:
                        bool: True if the object is a property, False otherwise
        """
        return isinstance(self.obj, property)

    def isbuiltin(self):
        return isinstance(self.obj, BFT)

    @property
    def linespan(self) -> list:
        """
        Extract line number spans for classes and functions.

        Returns a list of tuples containing:
        - (name, (start_line, end_line))

        Returns:
                        list: List of tuples with names and their line number ranges
        """
        tree = ast.parse(self.source)
        linenos = []

        for node in ast.walk(tree):
            if isinstance(node, (ast.ClassDef, ast.FunctionDef)):
                linenos.append((node.name, (node.lineno, node.end_lineno)))

        return linenos

    def space(self, to: str = None, spaces: int = 4) -> str:
        """
        Detect or convert indentation style in the source code.

        Args:
                to (str, optional): Target indentation type.
                        - None: Only detect indentation type without modification.
                        - '\t': Convert spaces to tabs.
                        - ' ' : Convert tabs to spaces.
                spaces (int, optional): Number of spaces to use when converting tabs to spaces. Default is 4.

        Returns:
                str:
                        - If `to` is None: Returns 'tap' if tabs are used, otherwise 'space'.
                        - If `to` is specified: Returns the modified source code after conversion.

        Raises:
                ValueError: If `to` is not None, '\t', or ' '.
        """
        if not to:
            lines = self.source.splitlines()

            for line in lines:
                if line.startswith("\t"):
                    return "tap"
            else:
                return "space"
        else:
            if to == "\t":
                return __replace__(self.file, " " * spaces, "\t")
            elif to == " ":
                return __replace__(self.file, "\t", " " * spaces)
            else:
                raise ValueError(f"'to' expected tap or space (' '), got '{to}'")

    @property
    def calls(self) -> list:
        """
        Extract all function/method calls from the source code.

        Returns full call chains including method chaining:
        - Simple calls: function()
        - Method calls: obj.method()
        - Chained calls: obj.attr.method()

        Returns:
                list: List of function/method call names as strings
        """

        def get_call_name(node):
            if isinstance(node, ast.Call):
                if isinstance(node.func, ast.Name):
                    return node.func.id
                elif isinstance(node.func, ast.Attribute):
                    parts = []
                    curr = node.func
                    while isinstance(curr, ast.Attribute):
                        parts.append(curr.attr)
                        curr = curr.value
                    if isinstance(curr, ast.Name):
                        parts.append(curr.id)
                    parts.reverse()
                    return ".".join(parts)
            return None

        tree = ast.parse(self.source)
        calls = []

        for node in ast.walk(tree):
            names = get_call_name(node)
            if names:
                calls.append(names)

        return calls

    @property
    def imports(self) -> list:
        """
        Extract all import statements from the source file.

        Uses external utility function __imports__ to parse imports.

        Returns:
                        list: List of import statements found in the source file
        """
        return __imports__(__load__(self.file))

    @property
    def defs(self) -> list:
        """
        Extract all definitions (functions, classes) from the source code.

        Uses external utility function __defs__ to parse definitions.

        Returns:
                        list: List of definitions found in the source code
        """
        return __defs__(self.source)

    @property
    def classes(self) -> list:
        """
        Extract all class definitions from the source code.

        Uses external utility function __classes__ to parse class definitions.

        Returns:
                        list: List of class names found in the source code
        """
        return __classes__(self.source)

    @property
    def comprehensions(self) -> list:
        """
        Extract all comprehensions (list, dict, set, generator) from the source code.

        Returns:
                        list: List of comprehension expressions as strings
        """
        tree = ast.parse(self.source)
        comprehensions = []

        for node in ast.walk(tree):
            if isinstance(node, ast.comprehension):
                comprehensions.append(ast.unparse(node).strip())

        return comprehensions

    def signature(self):
        return Signature(self.source)

    @property
    def exceptions(self) -> list[dict]:
        """
        Extract all try-except blocks and their structure.

        Returns a list of dictionaries with detailed information about each try block:
        - try_body: The source code of the try block
        - handlers: List of exception handlers with type, name, and body
        - else_body: The else block source code (if present)
        - finally_body: The finally block source code (if present)
        - lineno: Line number range (start, end)

        Returns:
                        list[dict]: List of dictionaries containing exception handling information
        """
        tree = ast.parse(self.source)
        exceptions = []

        for node in ast.walk(tree):
            if isinstance(node, ast.Try):
                exception_info = {
                    "try_body": ast.unparse(node.body),
                    "handlers": [],
                    "else_body": ast.unparse(node.orelse) if node.orelse else None,
                    "finally_body": (
                        ast.unparse(node.finalbody) if node.finalbody else None
                    ),
                    "lineno": (node.lineno, node.end_lineno),
                }

                for handler in node.handlers:
                    handler_info = {
                        "type": ast.unparse(handler.type) if handler.type else None,
                        "name": handler.name if handler.name else None,
                        "body": ast.unparse(handler.body),
                    }
                    exception_info["handlers"].append(handler_info)

                exceptions.append(exception_info)

        return exceptions

    @property
    def inheritance(self) -> list:
        """
        Extract class inheritance information.

        Returns a list of tuples containing:
        - (class_name, base_class_or_tuple)
        - For single inheritance: base class name as string
        - For multiple inheritance: tuple of base class names

        Returns:
                        list: List of tuples containing class names and their base classes
        """
        tree = ast.parse(self.source)
        inheritances = []

        for node in ast.walk(tree):
            if isinstance(node, ast.ClassDef) and node.bases:
                bases = []
                for base in node.bases:
                    bases.append(base.id)
                inheritances.append(
                    (node.name, tuple(bases) if len(bases) > 1 else bases[0])
                )

        return inheritances

    @property
    def lambdas(self) -> list:
        """
        Extract all lambda functions from the source code.

        Returns a list of dictionaries for each lambda containing:
        - Args: List of argument names
        - Body: The lambda body expression as string
        - Lineno: Line number range (start, end)

        Returns:
                        list: List of dictionaries containing lambda function information
        """
        tree = ast.parse(self.source)
        lambdas = []

        for node in ast.walk(tree):
            if isinstance(node, ast.Lambda):
                args = [arg.arg for ar.kg in node.args.args]
                body = ast.unparse(node.body)
                lineno = (node.lineno, node.end_lineno)
                lambdas.append({"Args": args, "Body": body, "Lineno": lineno})

        return lambdas

    @property
    def comments(self) -> list:
        """
        Extract all comments from the source code.

        Uses tokenize to parse comments, preserving their content and position.

        Returns:
                        list: List of tuples containing (comment_string, position_tuple)
        """
        comments = []
        tokens = tokenize.generate_tokens(StringIO(self.source).readline)

        for tok_type, tok_string, start, end, line in tokens:
            if tok_type == tokenize.COMMENT:
                comments.append((tok_string, start))

        return comments

    @property
    def returns(self) -> list:
        """
        Extract all return statements from functions.

        Returns a list of tuples containing:
        - (function_name, [return_value1, return_value2, ...])
        - Return values are represented as strings of the returned expression

        Returns:
                        list: List of tuples containing function names and their return statements
        """
        tree = ast.parse(self.source)
        returns = []

        for node in ast.walk(tree):
            if isinstance(node, ast.FunctionDef):
                subreturns = []
                for r in ast.walk(node):
                    if isinstance(r, ast.Return):
                        subreturns.append(ast.unparse(r.value) if r.value else "None")
                returns.append((node.name, subreturns))

        return returns

    @property
    def unreachable_code(self):
        """
        Identify unreachable code blocks.

        Detects code that follows return, raise, break, or continue statements
        and is therefore unreachable during execution.

        Returns:
                        list: List of tuples containing (line_number, code_snippet) for unreachable code
        """
        tree = ast.parse(self.source)
        source = self.source
        dead = []

        def check_block(statements):
            dead_found = False
            for stmt in statements:
                if dead_found:
                    if hasattr(stmt, "lineno"):
                        dead.append((stmt.lineno, ast.get_source_segment(source, stmt)))
                if isinstance(stmt, (ast.Return, ast.Raise, ast.Break, ast.Continue)):
                    dead_found = True
                for field, value in ast.iter_fields(stmt):
                    if isinstance(value, list):
                        stmts = [item for item in value if isinstance(item, ast.stmt)]
                        if stmts:
                            check_block(stmts)
                    elif isinstance(value, ast.stmt):
                        check_block([value])

        check_block(tree.body)
        return dead

    @property
    def constants(self):
        """
        Extract all constant values from the source code.

        Categorizes constants by type and includes line numbers:
        - int: Integer values
        - float: Floating point values
        - str: String literals
        - bool: Boolean values (True/False)
        - none: None values
        - object: Other constant types

        Returns:
                        list: List of tuples containing (type, value, line_number)
        """
        tree = ast.parse(self.source)
        constants = []

        for node in ast.walk(tree):
            if isinstance(node, ast.Constant):
                val = node.value
                lineno = node.lineno

                if isinstance(val, int):
                    constants.append(("int", val, lineno))
                elif isinstance(val, float):
                    constants.append(("float", val, lineno))
                elif isinstance(val, str):
                    constants.append(("str", val, lineno))
                elif isinstance(val, bool):
                    constants.append(("bool", val, lineno))
                elif val is None:
                    constants.append(("none", val, lineno))
                else:
                    constants.append(("object", val, lineno))

        return constants

    @property
    def minify(self) -> str:
        """
        Minify a Python source string.
        Requires python-minifier.
        """
        if python_minifier is None:
            raise RuntimeError("python-minifier not installed")
        return python_minifier.minify(self.source)

    @property
    def memory_usage(self) -> dict[str, float]:
        """
        Measure memory usage of a module or object.
        Uses sys.getsizeof and tracemalloc.
        Returns dict with size (bytes).
        """
        size = sys.getsizeof(self.obj)
        current, peak = tracemalloc.get_traced_memory()
        tracemalloc.stop()

        return {
            "object_size_bytes": size,
            "current": current,
            "peak": peak,
        }

    def freplace(self, old: str, new: str, save: bool = False) -> str:  # file replace
        try:
            self.file
        except Exception:
            raise FileNotFoundError("Source file not found") from None

        text = __load__(self.file).replace(old, new)
        if save:
            __dump__(self.file, text)
        return text

    def getobjects(
        self,
        filter_func: Optional[Callable[[str, Any], bool]] = None,
        include_private: bool = False,
    ) -> list[tuple[str, Any]]:
        """
        Retrieve all accessible attributes (name-value pairs) of the target object
        with optional filtering and safe access semantics.

        Purpose:
                This method provides a precise and non-intrusive way to enumerate all
                attributes of an object—both static and dynamic—without invoking any
                property, descriptor, or other code-executing mechanisms.

        Design Goals:
                1. **Safety:** Uses `inspect.getattr_static()` to prevent execution of
                   `__get__`, property methods, or descriptors.
                2. **Accuracy:** Includes all visible attributes returned by `dir()`,
                   and augments them with dynamically defined attributes via
                   `@DynamicClassAttribute`.
                3. **Determinism:** Returns results sorted alphabetically for consistent output.
                4. **Flexibility:** Supports attribute-level filtering and optional inclusion
                   of private members.

        Args:
                filter_func (Callable[[str, Any], bool], optional):
                        A user-supplied function used to filter attributes.
                        It receives each `(name, value)` pair and must return `True`
                        to include the attribute in the output.
                        Example:
                        ```python
                        filter_func = lambda n, v: callable(v)  # include only callables
                        ```

                include_private (bool, default=False):
                        If `False`, attributes whose names start with an underscore (`_`)
                        will be excluded from the result.

        Returns:
                list[tuple[str, Any]]:
                        A sorted list of `(attribute_name, value)` pairs representing all
                        accessible attributes that passed filtering.

        Notes:
                - This method does **not** execute code within properties or descriptors.
                - Dynamic class attributes (`@DynamicClassAttribute`) are included
                  even if missing from `dir()`.
                - The returned values may include descriptors, methods, functions,
                  class attributes, or data members depending on the target.

        Example:
                src = Source(str)

                # Get all non-private attributes
                all_attrs = src._getobjects()

                # Get only callable attributes
                callables = src._getobjects(lambda n, v: callable(v))

                # Include private and special attributes
                with_private = src._getobjects(include_private=True)
        """
        obj = self.obj
        names = list(dir(obj))
        results = []
        mro = getattr(obj, "__mro__", ())

        # Include DynamicClassAttributes if object is a class
        if isclass(obj):
            for base in obj.__bases__:
                for k, v in base.__dict__.items():
                    if isinstance(v, DCA) and k not in names:
                        names.append(k)

        # Build results
        append = results.append
        for key in names:
            if not include_private and key.startswith("_"):
                continue
            try:
                value = getattr_static(obj, key)
            except Exception:
                value = None

            if filter_func is None or filter_func(key, value):
                append((key, value))

        results.sort(key=lambda x: x[0])
        return results

    @property
    def examples(self) -> list[str]:
        """
        Extract precise usage examples from docstrings using AST or generate automatic examples.

        This property intelligently analyzes the object (`self.obj`) and returns a list
        of usage examples for functions, methods, and classes. It first attempts to
        extract examples from the docstring using common patterns, and if none are
        found, it generates smart placeholder examples based on AST and function/class
        signatures.

        Features:
            - Differentiates between functions, methods, and classes.
            - Uses AST to parse the source for accurate docstring extraction.
            - Supports Google, NumPy, Sphinx, and interactive Python style docstrings.
            - Auto-generates usage examples when docstring examples are missing.

        Returns:
            list[str]: List of usage example strings.

        Details:
            1️ Docstring Extraction (AST):
                - Looks for patterns like "Example:", "Examples", ":Example:", and ">>>"
                - Dedents multi-line examples for readability.

            2️ Automatic Generation:
                - Functions/Methods: Uses the function signature to create a call example
                  with default values or placeholders for arguments.
                - Classes: Uses the __init__ signature to create an instantiation example,
                  ignoring 'self' and using placeholders for required parameters.

        Notes:
            - Ensures at least one example is always returned, even if docstring is empty.
            - Multi-line examples are properly dedented for clean output.
            - AST-based extraction ensures higher accuracy compared to simple regex.

        Example usage:
            src = Source(some_function)
            src.examples   # ['some_function(arg1=..., arg2=...)']

            src = Source(SomeClass)
            src.examples   # ['SomeClass(param1=..., param2=...)']
        """
        examples = []
        doc = getattr(self.obj, "__doc__", "") or ""

        if doc:
            # Extract examples from common docstring styles using regex
            import re
            pattern_block = r"""
                (?:^|
    )                           # start at line beginning
                (?:Example:|Usage:|:Example:|Examples\s*-{3,})  # docstring indicator
                \s*                                 # optional whitespace
                (.*?)                               # capture example block
                (?=
    \s*
    |$)                       # until empty line or end of string
            """
            matches_block = re.findall(pattern_block, doc, re.DOTALL | re.IGNORECASE)
            examples.extend([textwrap.dedent(m).strip() for m in matches_block if m.strip()])

            # Interactive Python style
            pattern_interactive = r"(^\s*>>> .+?)(?=|$)"
            matches_interactive = re.findall(pattern_interactive, doc, re.MULTILINE)
            examples.extend([m.strip() for m in matches_interactive if m.strip()])

        # Fallback: generate examples using AST for accurate signature parsing
        if not examples:
            try:
                if inspect.isfunction(self.obj) or inspect.ismethod(self.obj):
                    func_name = getattr(self.obj, "__name__", "func")
                    sig = inspect.signature(self.obj)
                    args_list = [
                        f"{p.name}={p.default!r}" if p.default is not p.empty else f"{p.name}=..."
                        for p in sig.parameters.values()
                    ]
                    examples.append(f"{func_name}({', '.join(args_list)})")

                elif inspect.isclass(self.obj):
                    class_name = getattr(self.obj, "__name__", "ClassName")
                    try:
                        init_sig = inspect.signature(self.obj.__init__)
                        args_list = [f"{p.name}=..." for p in list(init_sig.parameters.values())[1:]]  # ignore self
                        examples.append(f"{class_name}({', '.join(args_list)})")
                    except Exception:
                        examples.append(f"{class_name}()")
                else:
                    examples.append(f"{getattr(self.obj, '__name__', repr(self.obj))}(...)")
            except Exception:
                examples.append(f"{getattr(self.obj, '__name__', repr(self.obj))}(...)")

        return examples

    @property
    def read(self):
        """
        Load and return the full content of the source file associated with this object.

        This property reads the file referenced by `self.file` and returns its content.
        It relies on the utility function `__load__`, which supports reading files in
        chunks efficiently, making it suitable for large files without loading everything
        into memory at once.

        Behavior:
            - Attempts to read the file in text mode by default.
            - If a `UnicodeError` occurs (e.g., due to binary content or encoding issues),
              it falls back to reading the file in binary mode (`mode='rb'`).

        Returns:
            str | bytes:
                - Returns a string when the file is successfully read in text mode.
                - Returns bytes when the file requires binary reading.

        Raises:
            FileNotFoundError:
                If `self.file` does not exist or cannot be accessed.
            Any exceptions raised by `__load__` (other than UnicodeError) are propagated.

        Notes:
            - `__load__` is designed to handle large files efficiently by reading in
              chunks, avoiding memory overload.
            - This property ensures that both text and binary files can be safely read
              without manual mode switching.
            - Ideal for source files, scripts, or any associated files that need to
              be analyzed or processed programmatically.

        Example usage:
            src = Source(some_module)
            content = src.read           # returns text or bytes of the file content
        """
        try:
            return __load__(self.file)
        except UnicodeError:
            return __load__(self.file, mode="rb")
