# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/08_showdoc.ipynb.

# %% ../nbs/08_showdoc.ipynb 2
from __future__ import annotations
from .doclinks import *
from .read import get_config

from fastcore.dispatch import TypeDispatch
from fastcore.docments import *
from fastcore.utils import *

from importlib import import_module
import inspect, sys
from inspect import Signature, Parameter
from collections import OrderedDict
from dataclasses import dataclass, is_dataclass
from textwrap import fill
from types import FunctionType

# %% auto 0
__all__ = ['DocmentTbl', 'ShowDocRenderer', 'BasicMarkdownRenderer', 'show_doc', 'doc', 'BasicHtmlRenderer', 'showdoc_nm',
           'colab_link']

# %% ../nbs/08_showdoc.ipynb 6
def _non_empty_keys(d:dict): return L([k for k,v in d.items() if v != inspect._empty])
def _bold(s): return f'**{s}**' if s.strip() else s

# %% ../nbs/08_showdoc.ipynb 7
def _escape_pipe(s): return re.sub(r'(\\)?\|', '\|', s)

# %% ../nbs/08_showdoc.ipynb 9
def _maybe_nm(o): 
    if (o == inspect._empty): return ''
    else: return o.__name__ if hasattr(o, '__name__') else _escape_pipe(str(o))

# %% ../nbs/08_showdoc.ipynb 11
def _list2row(l:list): return '| '+' | '.join([_maybe_nm(o) for o in l]) + ' |'

# %% ../nbs/08_showdoc.ipynb 13
class DocmentTbl:
    # this is the column order we want these items to appear
    _map = OrderedDict({'anno':'Type', 'default':'Default', 'docment':'Details'})
    
    def __init__(self, obj, verbose=True, returns=True):
        "Compute the docment table string"
        self.verbose = verbose
        self.returns = False if isdataclass(obj) else returns
        if isinstance_str(obj, 'property'): self.params = []
        else:
            try: self.params = L(signature_ex(obj, eval_str=True).parameters.keys())
            except (ValueError,TypeError): self.params=[]
        try: _dm = docments(obj, full=True, returns=returns)
        except: _dm = {}
        if 'self' in _dm: del _dm['self']
        for d in _dm.values(): d['docment'] = ifnone(d['docment'], inspect._empty)
        self.dm = _dm
    
    @property
    def _columns(self):
        "Compute the set of fields that have at least one non-empty value so we don't show tables empty columns"
        cols = set(flatten(L(self.dm.values()).filter().map(_non_empty_keys)))
        candidates = self._map if self.verbose else {'docment': 'Details'}
        return OrderedDict({k:v for k,v in candidates.items() if k in cols})
    
    @property
    def has_docment(self): return 'docment' in self._columns and self._row_list 

    @property
    def has_return(self): return self.returns and bool(_non_empty_keys(self.dm.get('return', {})))
    
    def _row(self, nm, props): 
        "unpack data for single row to correspond with column names."
        return [nm] + [props[c] for c in self._columns]
    
    @property
    def _row_list(self):
        "unpack data for all rows."
        ordered_params = [(p, self.dm[p]) for p in self.params if p != 'self' and p in self.dm]
        return L([self._row(nm, props) for nm,props in ordered_params])
    
    @property
    def _hdr_list(self): return ['  '] + [_bold(l) for l in L(self._columns.values())]

    @property
    def hdr_str(self):
        "The markdown string for the header portion of the table"
        md = _list2row(self._hdr_list)
        return md + '\n' + _list2row(['-' * len(l) for l in self._hdr_list])
    
    @property
    def params_str(self): 
        "The markdown string for the parameters portion of the table."
        return '\n'.join(self._row_list.map(_list2row))
    
    @property
    def return_str(self):
        "The markdown string for the returns portion of the table."
        return _list2row(['**Returns**']+[_bold(_maybe_nm(self.dm['return'][c])) for c in self._columns])
    
    def _repr_markdown_(self):
        if not self.has_docment: return ''
        _tbl = [self.hdr_str, self.params_str]
        if self.has_return: _tbl.append(self.return_str)
        return '\n'.join(_tbl)
    
    def __eq__(self,other): return self.__str__() == str(other).strip()

    def __str__(self): return self._repr_markdown_()
    __repr__ = basic_repr()

# %% ../nbs/08_showdoc.ipynb 28
class ShowDocRenderer:
    def __init__(self, sym, name:str|None=None, title_level:int|None=None):
        "Show documentation for `sym`"
        store_attr()
        self.nm = name or qual_name(sym)
        self.isfunc = inspect.isfunction(sym)
        self.isprop = isinstance_str(sym, 'property')
        if self.isprop: self.sig = None
        else:
            try: self.sig = signature_ex(sym, eval_str=True)
            except (ValueError,TypeError): self.sig = None
        self.docs = docstring(sym)
        self.dm = DocmentTbl(sym)
        
    __repr__ = basic_repr()

# %% ../nbs/08_showdoc.ipynb 29
def _f_name(o): return f'<function {o.__name__}>' if isinstance(o, FunctionType) else None
def _fmt_anno(o): return inspect.formatannotation(o).strip("'").replace(' ','')

def _show_param(param):
    "Like `Parameter.__str__` except removes: quotes in annos, spaces, ids in reprs"
    kind,res,anno,default = param.kind,param._name,param._annotation,param._default
    kind = '*' if kind==inspect._VAR_POSITIONAL else '**' if kind==inspect._VAR_KEYWORD else ''
    res = kind+res
    if anno is not inspect._empty: res += f':{_f_name(anno) or _fmt_anno(anno)}'
    if default is not inspect._empty: res += f'={_f_name(default) or repr(default)}'
    return res

# %% ../nbs/08_showdoc.ipynb 31
def _fmt_sig(sig):
    if sig is None: return ''
    p = {k:v for k,v in sig.parameters.items()}
    _params = [_show_param(p[k]).replace(' ','') for k in p.keys() if k != 'self']
    return "(" + ', '.join(_params)  + ")"

def _wrap_sig(s):
    "wrap a signature to appear on multiple lines if necessary."
    pad = '> ' + ' ' * 5
    indent = pad + ' ' * (s.find('(') + 1)
    return fill(s, width=80, initial_indent=pad, subsequent_indent=indent)

# %% ../nbs/08_showdoc.ipynb 33
class BasicMarkdownRenderer(ShowDocRenderer):
    def _repr_markdown_(self):
        doc = '---\n\n'
        if self.isfunc or self.isprop: doc += '#'
        sig = _wrap_sig(f"{self.nm} {_fmt_sig(self.sig)}") if self.sig else ''
        doc += f'### {self.nm}\n\n{sig}'
        if self.docs: doc += f"\n\n{self.docs}"
        if self.dm.has_docment: doc += f"\n\n{self.dm}"
        return doc

# %% ../nbs/08_showdoc.ipynb 34
def show_doc(sym, renderer=None, name:str|None=None, title_level:int|None=None):
    if renderer is None: renderer = get_config().get('renderer', None)
    if renderer is None: renderer=BasicMarkdownRenderer
    elif isinstance(renderer,str):
        p,m = renderer.rsplit('.', 1)
        renderer = getattr(import_module(p), m)
    if isinstance(sym, TypeDispatch): pass
    else:return renderer(sym or show_doc, name=name, title_level=title_level)

# %% ../nbs/08_showdoc.ipynb 37
def _fullname(o):
    module,name = o.__module__,qual_name(o)
    return name if module is None or module == 'builtins' else module + '.' + name

def doc(elt, show_all_docments:bool=False):
    "Show `show_doc` info along with link to docs"
    from IPython.display import display,Markdown
    md = BasicMarkdownRenderer(elt)._repr_markdown_()
    doc_link = NbdevLookup()[_fullname(elt)]
    if doc_link is not None:
        md += f'\n\n<a href="{doc_link}" target="_blank" rel="noreferrer noopener">Show in docs</a>'
    display(Markdown(md))

# %% ../nbs/08_showdoc.ipynb 52
class BasicHtmlRenderer(ShowDocRenderer):
    def _repr_html_(self):
        doc = '<hr/>\n'
        lvl = 4 if self.isfunc else 3
        doc += f'<h{lvl}>{self.nm}</h{lvl}>\n'
        doc += f'<blockquote><pre><code>{self.nm}{_fmt_sig(self.sig)}</code></pre></blockquote>'
        if self.docs: doc += f"<p>{self.docs}</p>"
        return doc

# %% ../nbs/08_showdoc.ipynb 57
def showdoc_nm(tree):
    "Get the fully qualified name for showdoc."
    return ifnone(get_patch_name(tree), tree.name)

# %% ../nbs/08_showdoc.ipynb 60
def colab_link(path):
    "Get a link to the notebook at `path` on Colab"
    from IPython.display import Markdown
    cfg = get_config()
    pre = 'https://colab.research.google.com/github/'
    res = f'{pre}{cfg.user}/{cfg.lib_name}/blob/{cfg.branch}/{cfg.path("nbs_path").name}/{path}.ipynb'
    display(Markdown(f'[Open `{path}` in Colab]({res})'))
