"""A fast way to turn your python function into a script."""

# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/06_script.ipynb.

# %% auto #0
__all__ = ['SCRIPT_INFO', 'store_true', 'store_false', 'bool_arg', 'clean_type_str', 'Param', 'anno_parser', 'args_from_prog',
           'set_ctx', 'call_parse']

# %% ../nbs/06_script.ipynb #8a36db98
import inspect,argparse,shutil,types

from functools import wraps,partial
from .imports import *
from .utils import *
from .docments import docments
from typing import get_origin, get_args, Union

# %% ../nbs/06_script.ipynb #5bf7ac6c
def store_true():
    "Placeholder to pass to `Param` for `store_true` action"
    pass

# %% ../nbs/06_script.ipynb #e2798370
def store_false():
    "Placeholder to pass to `Param` for `store_false` action"
    pass

# %% ../nbs/06_script.ipynb #69d525bc
def bool_arg(v):
    "Use as `type` for `Param` to get `bool` behavior"
    return str2bool(v)

# %% ../nbs/06_script.ipynb #f1d4a6a3
def clean_type_str(x:str):
    x = str(x)
    x = re.sub(r"(enum |class|function|__main__\.|\ at.*)", '', x)
    x = re.sub(r"(<|>|'|\ )", '', x) # spl characters
    return x

# %% ../nbs/06_script.ipynb #6785a783
class Param:
    "A parameter in a function used in `anno_parser` or `call_parse`"
    def __init__(self, help="", type=None, opt=True, action=None, nargs=None, const=None,
                 choices=None, required=None, default=None, version=None):
        if type==store_true:  type,action,default=None,'store_true',False
        if type==store_false: type,action,default=None,'store_false',True
        if type and isinstance(type,typing.Type) and issubclass(type,enum.Enum) and not choices: choices=list(type)
        help = help or ""
        self.negated = False
        store_attr()

    def set_default(self, d):
        if self.action == "version":
            if d != inspect.Parameter.empty: self.version = d
            self.opt = True
            return
        if self.type==bool and self.action is None:
            self.type = None
            if d is True: self.action,self.default,self.negated = 'store_false',True,True
            else:         self.action,self.default = 'store_true',False
        elif self.default is None:
            if d == inspect.Parameter.empty: self.opt = False
            else: self.default = d
        if self.default is not None: self.help += f" (default: {self.default})"

    @property
    def pre(self): return '--' if self.opt else ''
    @property
    def kwargs(self): return {k:v for k,v in self.__dict__.items() if v is not None and k not in ('opt','negated') and k[0]!='_'}
    def __repr__(self):
        if not self.help and self.type is None: return ""
        if not self.help and self.type is not None: return f"{clean_type_str(self.type)}"
        if self.help and self.type is None: return f"<{self.help}>"
        if self.help and self.type is not None: return f"{clean_type_str(self.type)} <{self.help}>"

# %% ../nbs/06_script.ipynb #cceb4486
class _HelpFormatter(argparse.HelpFormatter):
    def __init__(self, prog, indent_increment=2):
        cols = shutil.get_terminal_size((120,30))[0]
        super().__init__(prog, max_help_position=cols//2, width=cols, indent_increment=indent_increment)
    def _expand_help(self, action): return self._get_help_string(action)

# %% ../nbs/06_script.ipynb #2259c093
def _is_union(t): return get_origin(t) in (Union, types.UnionType) if hasattr(types, 'UnionType') else get_origin(t) is Union

def _union_parser(types):
    "Return a parser that tries each type in sequence"
    def _parse(v):
        for t in types:
            if t is type(None): continue
            try: return t(v)
            except: pass
        raise ValueError(f"Could not parse {v!r} as any of {types}")
    return _parse

def _union_type(t):
    "Get parser for Union types, or None if not a Union"
    if not _is_union(t): return None
    return _union_parser(get_args(t))

# %% ../nbs/06_script.ipynb #5e5bea67
def anno_parser(func, prog:str=None):
    "Look at params (annotated with `Param`) in func and return an `ArgumentParser`"
    p = argparse.ArgumentParser(description=func.__doc__, prog=prog, formatter_class=_HelpFormatter)
    for k,v in docments(func, full=True, returns=False, eval_str=True).items():
        param = v.anno
        if not isinstance(param, Param):
            anno = _union_type(v.anno) or v.anno
            param = Param(v.docment, anno)
        param.set_default(v.default)
        name = f"no-{k}" if param.negated else k
        kw = param.kwargs
        if param.negated: kw['dest'] = k
        p.add_argument(f"{param.pre}{name}", **kw)
    p.add_argument(f"--pdb", help=argparse.SUPPRESS, action='store_true')
    p.add_argument(f"--xtra", help=argparse.SUPPRESS, type=str)
    return p

# %% ../nbs/06_script.ipynb #75e8a419
def args_from_prog(func, prog):
    "Extract args from `prog`"
    if prog is None or '#' not in prog: return {}
    if '##' in prog: _,prog = prog.split('##', 1)
    progsp = prog.split("#")
    args = {progsp[i]:progsp[i+1] for i in range(0, len(progsp), 2)}
    annos = type_hints(func)
    for k,v in args.items():
        anno = annos.get(k)
        t = getattr(anno, 'type', anno)
        if t in (bool, bool_arg): t = str2bool
        elif isinstance(anno, Param) and anno.action in ('store_true', 'store_false'): t = str2bool
        if t: args[k] = t(v)
    return args

# %% ../nbs/06_script.ipynb #f76b07f6
SCRIPT_INFO = SimpleNamespace(func=None)

# %% ../nbs/06_script.ipynb #42c8e85f
from contextvars import ContextVar
from contextlib import contextmanager

# %% ../nbs/06_script.ipynb #e4537112
@contextmanager
def set_ctx(cv, val=True):
    token = cv.set(val)
    try: yield
    finally: cv.reset(token)

# %% ../nbs/06_script.ipynb #fc816498
_in_call_parse = ContextVar('_in_call_parse', default=False)

def call_parse(func=None, nested=False):
    "Decorator to create a simple CLI from `func` using `anno_parser`"
    if func is None: return partial(call_parse, nested=nested)

    @wraps(func)
    def _f(*args, **kwargs):
        if args or kwargs or _in_call_parse.get(): return func(*args, **kwargs)
        with set_ctx(_in_call_parse):
            mod = inspect.getmodule(inspect.currentframe().f_back)
            if not mod: return func(*args, **kwargs)
            if not SCRIPT_INFO.func and mod.__name__=="__main__": SCRIPT_INFO.func = func.__name__
            if len(sys.argv)>1 and sys.argv[1]=='': sys.argv.pop(1)
            p = anno_parser(func)
            if nested: args, sys.argv[1:] = p.parse_known_args()
            else: args = p.parse_args()
            args = args.__dict__
            xtra = otherwise(args.pop('xtra', ''), eq(1), p.prog)
            tfunc = trace(func) if args.pop('pdb', False) else func
            return tfunc(**merge(args, args_from_prog(func, xtra)))

    mod = inspect.getmodule(inspect.currentframe().f_back)
    if getattr(mod, '__name__', '') =="__main__":
        setattr(mod, func.__name__, _f)
        SCRIPT_INFO.func = func.__name__
        return _f()
    else: return _f
