# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/00_core.ipynb.

# %% auto 0
__all__ = ['get_db', 'find_var', 'find_dialog_id', 'find_msgs', 'find_msg_id', 'read_msg_ids', 'msg_idx', 'read_msg', 'add_msg',
           'update_msg', 'add_html', 'load_gist', 'gist_file', 'import_string', 'import_gist']

# %% ../nbs/00_core.ipynb
import inspect, json, importlib, linecache
from tempfile import TemporaryDirectory
from ipykernel_helper import *

from fastcore.utils import *
from ghapi.all import *
from fastlite import *

# %% ../nbs/00_core.ipynb
def get_db(ns:dict=None):
    app_path = Path('/app') if Path('/.dockerenv').exists() else Path('.')
    if os.environ.get('IN_SOLVEIT', False): dataparent,nm = app_path, 'data.db'
    else: dataparent,nm = Path('..'),'dev_data.db'
    db = database(dataparent/'data'/nm)
    dcs = [o for o in all_dcs(db) if o.__name__[0]!='_']
    if ns:
        for o in dcs: ns[o.__name__]=o
    return db

# %% ../nbs/00_core.ipynb
def find_var(var:str):
    "Search for var in all frames of the call stack"
    frame = inspect.currentframe()
    while frame:
        dv = frame.f_globals.get(var, frame.f_locals.get(var, None))
        if dv: return dv
        frame = frame.f_back
    raise ValueError(f"Could not find {var} in any scope")

# %% ../nbs/00_core.ipynb
def find_dialog_id():
    "Get the dialog id by searching the call stack for __dialog_id."
    return find_var('__dialog_id')

# %% ../nbs/00_core.ipynb
def find_msgs(
    pattern: str, # Text to search for
    limit:int=10 # Limit number of returned items
):
    "Find messages in a specific dialog that contain the given pattern."
    did = find_dialog_id()
    db = get_db()
    return db.t.message('did=? AND content LIKE ?', [did, f'%{pattern}%'], limit=limit)

# %% ../nbs/00_core.ipynb
def find_msg_id():
    "Get the message id by searching the call stack for __dialog_id."
    return find_var('__msg_id')

# %% ../nbs/00_core.ipynb
def read_msg_ids():
    "Get all ids in current dialog."
    did = find_dialog_id()
    db = get_db()
    return [o.sid for o in db.t.message('did=?', [did], select='sid', order_by='id')]

# %% ../nbs/00_core.ipynb
def msg_idx():
    "Get index of current message in dialog."
    ids = read_msg_ids()
    return ids,ids.index(find_msg_id())

# %% ../nbs/00_core.ipynb
def read_msg(n:int=-1,     # Message index (if relative, +ve is downwards)
             relative:bool=True  # Is `n` relative to current message (True) or absolute (False)?
    ):
    "Get the message indexed in the current dialog."
    ids,idx = msg_idx()
    if relative:
        idx = idx+n
        if not 0<=idx<len(ids): return None
    else: idx = n
    db = get_db()
    return db.t.message.fetchone('sid=?', [ids[idx]])

# %% ../nbs/00_core.ipynb
def add_msg(
    content:str, # message that we are updating or adding before/after
    msg_type: str='note', # message type, can be 'code', 'note', or 'prompt'
    output='', # for prompts/code, initial output
    placement='add_after', # can be 'add_after', 'add_before', 'update', 'at_start', 'at_end'
    msg_id:str=None, # id of message that placement is relative to (if None, uses current message)
    **kw # Additional Message fields such as skipped i/o_collapsed, etc, passed through to the server
):
    "Add/update a message to the queue to show after code execution completes."
    assert msg_type in ('note', 'code', 'prompt'), "msg_type must be 'code', 'note', or 'prompt'."
#     kwargs = {'msg_id':msg_id} if msg_id else {}
    run_cmd('add_msg', content=content, msg_type=msg_type, output=output, placement=placement, msg_id=msg_id, **kw)

# %% ../nbs/00_core.ipynb
def update_msg(msg: dict):
    "Update an existing message in the dialog."
    if not isinstance(msg, dict): msg = asdict(msg)
    exclude = {'id', 'content', 'msg_type', 'output'} # explicit args
    kw = {k: v for k, v in msg.items() if k not in exclude}
    add_msg(content=msg['content'], msg_type=msg['msg_type'], output=msg['output'],
            placement = 'update', msg_id=msg['id'], **kw)

# %% ../nbs/00_core.ipynb
def add_html(
    html:str, # HTML to add to the DOM
):
    "Dynamically add HTML to the current web page. Supports HTMX attrs too."
    run_cmd('add_ft', html=html)

# %% ../nbs/00_core.ipynb
def load_gist(gist_id:str):
    "Retrieve a gist"
    api = GhApi()
    if '/' in gist_id: *_,user,gist_id = gist_id.split('/')
    else: user = None
    return api.gists.get(gist_id, user=user)

# %% ../nbs/00_core.ipynb
def gist_file(gist_id:str):
    "Get the first file from a gist"
    gist = load_gist(gist_id)
    return first(gist.files.values())

# %% ../nbs/00_core.ipynb
def import_string(
    code:str, # Code to import as a module
    name:str  # Name of module to create
):
    with TemporaryDirectory() as tmpdir:
        path = Path(tmpdir) / f"{name}.py"
        path.write_text(code)
        # linecache.cache storage allows inspect.getsource() after tmpdir lifetime ends
        linecache.cache[str(path)] = (len(code), None, code.splitlines(keepends=True), str(path))
        spec = importlib.util.spec_from_file_location(name, path)
        module = importlib.util.module_from_spec(spec)
        sys.modules[name] = module
        spec.loader.exec_module(module)
        return module

# %% ../nbs/00_core.ipynb
def import_gist(
    gist_id:str, # user/id or just id of gist to import as a module
    mod_name:str=None, # module name to create (taken from gist filename if not passed)
    add_global:bool=True # add module to caller's globals?
):
    "Import gist directly from string without saving to disk"
    fil = gist_file(gist_id)
    mod_name = mod_name or Path(fil['filename']).stem
    module = import_string(fil['content'], mod_name)
    if add_global: inspect.currentframe().f_back.f_globals[mod_name] = module
    return module
