import inspect
import itertools
from pathlib import Path
from types import FrameType

from mower.utils.path import get_path


# https://stackoverflow.com/a/78770438
def fast_stack(max_depth: int | None = None):
    def frame_infos(frame: FrameType | None):
        while frame := frame and frame.f_back:
            yield inspect.FrameInfo(
                frame,
                inspect.getfile(frame),
                frame.f_lineno,
                frame.f_code.co_name,
                None,
                None,
            )

    return list(itertools.islice(frame_infos(inspect.currentframe()), max_depth))


def caller_info():
    caller = fast_stack(3)[2]
    relative_path = Path(caller.filename)
    try:
        relative_path = relative_path.relative_to(get_path("@install"))
    except ValueError:
        pass
    return f"{relative_path}:{caller.lineno}"


def log_info():
    caller_class = []
    tmp_class = []
    stack_info = fast_stack()
    func_name = stack_info[2].function
    lineno = stack_info[2].lineno
    relative_path = Path(stack_info[2].filename)
    try:
        relative_path = relative_path.relative_to(get_path("@install"))
    except ValueError:
        pass
    for st in stack_info[2:]:
        try:
            caller = st.frame.f_locals["self"].__class__
        except Exception:
            continue
        try:
            name = caller.solver_name
            solver = True
        except Exception:
            name = caller.__qualname__
            solver = False
        if name is None:
            continue
        tmp_class.insert(0, name)
        if solver:
            caller_class = tmp_class + caller_class
            tmp_class = []
    caller_class = "|".join([key for key, _ in itertools.groupby(caller_class)])
    if caller_class != "":
        caller_class = f"[{caller_class}]"
    return (caller_class, relative_path, func_name, lineno)
