"""Solveit tools for inspecting the symbol table and importing modules"""

# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/02_inspecttools.ipynb.

# %% auto 0
__all__ = ['doimport', 'resolve', 'symsrc', 'showsrc', 'gettype', 'getdir', 'getval', 'getnth', 'run_code_interactive',
           'inspect_tool_info']

# %% ../nbs/02_inspecttools.ipynb
import inspect, re
from importlib import import_module
from . import add_msg

# %% ../nbs/02_inspecttools.ipynb
def _find_frame_dict(var:str):
    "Find the dict (globals or locals) containing var"
    frame = inspect.currentframe().f_back
    while frame:
        if var in frame.f_globals: return frame.f_globals
        frame = frame.f_back
    raise ValueError(f"Could not find {var} in any scope")

# %% ../nbs/02_inspecttools.ipynb
def doimport(mod: str):
    "Import a module into the caller's global namespace"
    g = _find_frame_dict('__msg_id')
    import_module(mod)
    g[mod.split('.')[0]] = import_module(mod.split('.')[0])

# %% ../nbs/02_inspecttools.ipynb
_last = None

def resolve(
    sym: str  # Dotted symbol path, with optional [n] indexing, e.g. "module.attr.subattr[1]" or "_last" for previous result
):
    """Resolve a dotted symbol string to its Python object, with optional [n] indexing. Sets global `_last` to the resolved object for chaining. Pass `"_last"` to reference the result of the previous tool call.

    Examples:

    - `resolve("sympy.sets.sets.Interval")` -> `<class 'sympy.sets.sets.Interval'>`
    - `resolve("mylist[2]")` -> third element of mylist"""
    global _last
    if (sym := sym.strip()) == '_last': return _last
    g = _find_frame_dict('__msg_id')
    if match := re.match(r'^(\w+)\[(\d+)\]$', sym):
        attr, idx = match.groups()
        parts, _last = ['_last'], _last[int(idx)] if attr == '_last' else g[attr][int(idx)]
    else: parts = re.split(r'\.(?![^\[]*\])', sym)
    obj = _last if parts[0] == '_last' else g[parts[0]]
    for part in parts[1:]:
        match = re.match(r'(\w+)\[(\d+)\]$', part)
        if match:
            attr, idx = match.groups()
            obj = getattr(obj, attr)[int(idx)]
        else: obj = getattr(obj, part)
    _last = obj
    return obj

# %% ../nbs/02_inspecttools.ipynb
def symsrc(
    sym: str  # Dotted symbol path or "_last" for previous result
):
    """Get the source code for a symbol.

    Examples:

    - `symsrc("sympy.sets.sets.Interval")` -> source code of Interval class
    - `symsrc("_last")` -> source of object from previous tool call
    - For dispatchers or registries of callables: `getnth("module.dispatcher.funcs", n) then symsrc("_last")`"""
    obj = resolve(sym)
    source = inspect.getsource(obj)
    filename = inspect.getfile(obj)
    return f"File: {filename}\n\n{source}"

# %% ../nbs/02_inspecttools.ipynb
def showsrc(
    sym: str  # Dotted symbol path or "_last" for previous result
):
    "Create a note to show the user (and following LLM prompts) the source of `sym`, following `symsrc` rules"
    res = symsrc(sym)
    add_msg(f'```python\n{res}\n```')
    return {'success': 'Message has been added to dialog successfully'}

# %% ../nbs/02_inspecttools.ipynb
def gettype(
    sym: str  # Dotted symbol path or "_last" for previous result
):
    """Get the type of a symbol and set `_last`.

    Examples:

    - `gettype("sympy.sets.sets.Interval")` -> `<class 'type'>`
    - `gettype("_last")` -> type of previous result"""
    return type(resolve(sym))

# %% ../nbs/02_inspecttools.ipynb
def getdir(
    sym: str,  # Dotted symbol path or "_last" for previous result
    exclude_private: bool=False # Filter out attrs starting with "_"
):
    """Get dir() listing of a symbol's attributes and set `_last`. E.g: `getdir("sympy.Interval")` -> `['__add__', '__and__', ...]`"""
    res = dir(resolve(sym))
    if not exclude_private: return res
    return [o for o in res if o[0]!='_']

# %% ../nbs/02_inspecttools.ipynb
def getval(
    sym: str  # Dotted symbol path or "_last" for previous result
):
    """Get repr of a symbol's value and set `_last`.

    Examples:
    
    - `getval("sympy.sets.sets.Interval")` -> `<class 'sympy.sets.sets.Interval'>`
    - `getval("some_dict.keys")` -> `dict_keys([...])`"""
    return repr(resolve(sym))

# %% ../nbs/02_inspecttools.ipynb
def getnth(
    sym: str,  # Dotted symbol path to a dict or object with .values()
    n: int     # Index into the values (0-based)
):
    """Get the nth value from a dict (or any object with .values()). Sets `_last` so you can chain with `symsrc("_last")` etc.

    Examples:
    
    - `getnth("dispatcher.funcs", 12)` -> 13th registered function
    - `getnth("dispatcher.funcs", 0); symsrc("_last")` -> source of first handler"""
    global _last
    _last = list(resolve(sym).values())[n]
    return _last

# %% ../nbs/02_inspecttools.ipynb
def run_code_interactive(
    code:str # Code to have user run
):
    """Insert code into user's dialog and request for the user to run it. Use other tools where possible, 
    but if they can not find needed information, *ALWAYS* use this instead of guessing or giving up.
    IMPORTANT: This tool is TERMINAL - after calling it, you MUST stop all tool usage 
    and wait for user response. Never call additional tools after this one."""
    add_msg('# Please run this:\n'+code, msg_type='code')
    return {'success': "CRITICAL: Message added to user dialog. STOP IMMEDIATELY. Do NOT call any more tools. Wait for user to run code and respond."}

# %% ../nbs/02_inspecttools.ipynb
def inspect_tool_info():
    add_msg('Tools available from inspecttools: &`[symsrc,showsrc,gettype,getdir,doimport,getval,getnth,run_code_interactive]`')
