import jsonargparse
import inspect
from jsonargparse.parameter_resolvers import ParamData
from jsonargparse.signatures import get_signature_parameters
from jsonargparse.signatures import get_doc_short_description


class MyArgumentParser(jsonargparse.ArgumentParser):
    def _add_signature_arguments(
        self,
        function_or_class,
        method_name,
        nested_key,
        as_group: bool = True,
        as_positional: bool = False,
        skip=None,
        fail_untyped: bool = True,
        sub_configs: bool = False,
        instantiate: bool = True,
        linked_targets=None
    ) -> list[str]:
        ## Create group if requested ##
        doc_group = get_doc_short_description(function_or_class, method_name, self.logger)
        component = getattr(function_or_class, method_name) if method_name else function_or_class
        group = self._create_group_if_requested(component, nested_key, as_group, doc_group, instantiate=instantiate)

        params = get_signature_parameters(function_or_class, method_name, logger=self.logger)

        if hasattr(function_or_class, '__custom__'):
            # Hack to insert our method for explicit parameterization
            __custom__ = function_or_class.__custom__
            for key, info in __custom__.items():
                type = info.get('type', None)
                if type is None or not isinstance(type, type):
                    annotation = inspect._empty
                else:
                    annotation = type
                param = ParamData(
                    name=key,
                    annotation=annotation,
                    kind=inspect.Parameter.KEYWORD_ONLY,
                    default=info.get('value', None),
                    doc=info.get('help', None),
                    component=function_or_class.__init__,
                    parent=function_or_class,
                )
                params.append(param)

        ## Add parameter arguments ##
        added_args = []
        for param in params:
            self._add_signature_parameter(
                group,
                nested_key,
                param,
                added_args,
                skip,
                fail_untyped=fail_untyped,
                sub_configs=sub_configs,
                linked_targets=linked_targets,
                as_positional=as_positional,
            )
        # import ubelt as ub
        # print('added_args = {}'.format(ub.urepr(added_args, nl=1)))
        return added_args


class ClassA:
    def __init__(self, a=None):
        self.a = a


class ClassB(ClassA):
    __custom__ = {
        'b_name': {'value': [1, 2, 3], 'type': str, help: 'hello world'}
    }
    def __init__(self, b=3, **kwargs):
        self.b = b
        self.__dict__.update(kwargs)


class ClassC:
    __custom__ = {
        'c_name': {'type': str, help: 'hello world'}
    }
    def __init__(self, c=None, **kwargs):
        self.c = c
        self.__dict__.update(kwargs)


# Monkey patch jsonargparse so its subcommands use our extended functionality
MONKEY_PATCH = 1
if MONKEY_PATCH:
    jsonargparse.ArgumentParser = MyArgumentParser
    jsonargparse.core.ArgumentParser = MyArgumentParser


def main():
    parser = MyArgumentParser()
    parser.add_subclass_arguments(ClassA, nested_key='class_a', fail_untyped=False)
    parser.add_class_arguments(ClassC, nested_key='class_c', fail_untyped=False, sub_configs=True)
    config = parser.parse_args()
    instances = parser.instantiate_classes(config)
    if hasattr(instances, 'class_a'):
        print(f'{instances.class_a.__dict__=}')
    print(f'{instances.class_c.__dict__=}')


if __name__ == '__main__':
    """
    Ignore:
        pip install jsonargparse==4.14.1
        pip install jsonargparse -U

    CommandLine:
        python jsonargparse_subclass_mwe.py --help
        python jsonargparse_subclass_mwe.py --class_a.help ClassB
        python jsonargparse_subclass_mwe.py --class_a=ClassB --class_a.init_args.b_name=foo
        python jsonargparse_subclass_mwe.py --class_c.c_name=bar
        python jsonargparse_subclass_mwe.py
        --class_a=ClassA
    """
    main()
