content: from collections import OrderedDict
  import copy
  import numbers
  import os
  import typing
  from textwrap import fill, dedent
  
  from typing_extensions import TypedDict, NotRequired, Literal
  from dash.development.base_component import _explicitize_args
  from dash.exceptions import NonExistentEventException
  from ._all_keywords import python_keywords
  from ._collect_nodes import collect_nodes, filter_base_nodes
  from ._py_prop_typing import (
      get_custom_ignore,
      get_custom_props,
      get_prop_typing,
      shapes,
      get_custom_imports,
  )
  from .base_component import Component, ComponentType
  
  import_string = """# AUTO GENERATED FILE - DO NOT EDIT
  
  import typing  # noqa: F401
  from typing_extensions import TypedDict, NotRequired, Literal # noqa: F401
  from dash.development.base_component import Component, _explicitize_args
  {custom_imports}
  ComponentSingleType = typing.Union[str, int, float, Component, None]
  ComponentType = typing.Union[
      ComponentSingleType,
      typing.Sequence[ComponentSingleType],
  ]
  
  NumberType = typing.Union[
      typing.SupportsFloat, typing.SupportsInt, typing.SupportsComplex
  ]
  
  
  """
  
  
  # pylint: disable=unused-argument,too-many-locals,too-many-branches
  def generate_class_string(
      typename,
      props,
      description,
      namespace,
      prop_reorder_exceptions=None,
      max_props=None,
      custom_typing_module=None,
  ):
      """Dynamically generate class strings to have nicely formatted docstrings,
      keyword arguments, and repr.
      Inspired by http://jameso.be/2013/08/06/namedtuple.html
      Parameters
      ----------
      typename
      props
      description
      namespace
      prop_reorder_exceptions
      Returns
      -------
      string
      """
      # TODO _prop_names, _type, _namespace, and available_properties
      # can be modified by a Dash JS developer via setattr
      # TODO - Tab out the repr for the repr of these components to make it
      # look more like a hierarchical tree
      # TODO - Include "description" "defaultValue" in the repr and docstring
      #
      # TODO - Handle "required"
      #
      # TODO - How to handle user-given `null` values? I want to include
      # an expanded docstring like Dropdown(value=None, id=None)
      # but by templating in those None values, I have no way of knowing
      # whether a property is None because the user explicitly wanted
      # it to be `null` or whether that was just the default value.
      # The solution might be to deal with default values better although
      # not all component authors will supply those.
      c = '''class {typename}(Component):
      """{docstring}"""
      _children_props: typing.List[str] = {children_props}
      _base_nodes = {base_nodes}
      _namespace = '{namespace}'
      _type = '{typename}'
  {shapes}
  
      def __init__(
          self,
          {default_argtext}
      ):
          self._prop_names = {list_of_valid_keys}
          self._valid_wildcard_attributes =\
              {list_of_valid_wildcard_attr_prefixes}
          self.available_properties = {list_of_valid_keys}
          self.available_wildcard_properties =\
              {list_of_valid_wildcard_attr_prefixes}
          _explicit_args = kwargs.pop('_explicit_args')
          _locals = locals()
          _locals.update(kwargs)  # For wildcard attrs and excess named props
          args = {args}
          {required_validation}
          super({typename}, self).__init__({argtext})
  
  setattr({typename}, "__init__", _explicitize_args({typename}.__init__))
  '''
  
      filtered_props = (
          filter_props(props)
          if (prop_reorder_exceptions is not None and typename in prop_reorder_exceptions)
          or (prop_reorder_exceptions is not None and "ALL" in prop_reorder_exceptions)
          else reorder_props(filter_props(props))
      )
      wildcard_prefixes = repr(parse_wildcards(props))
      list_of_valid_keys = repr(list(map(str, filtered_props.keys())))
      custom_ignore = get_custom_ignore(custom_typing_module)
      docstring = create_docstring(
          component_name=typename,
          props=filtered_props,
          description=description,
          prop_reorder_exceptions=prop_reorder_exceptions,
          ignored_props=custom_ignore,
      ).replace("\r\n", "\n")
      required_args = required_props(filtered_props)
      is_children_required = "children" in required_args
      required_args = [arg for arg in required_args if arg != "children"]
  
      prohibit_events(props)
  
      # pylint: disable=unused-variable
      prop_keys = list(props.keys())
      if "children" in props and "children" in list_of_valid_keys:
          prop_keys.remove("children")
          # TODO For dash 3.0, remove the Optional and = None for proper typing.
          #  Also add the other required props after children.
          default_argtext = f"children: typing.Optional[{get_prop_typing('node', '', '', {})}] = None,\n        "
          args = "{k: _locals[k] for k in _explicit_args if k != 'children'}"
          argtext = "children=children, **args"
      else:
          default_argtext = ""
          args = "{k: _locals[k] for k in _explicit_args}"
          argtext = "**args"
  
      if len(required_args) == 0:
          required_validation = ""
      else:
          required_validation = f"""
          for k in {required_args}:
              if k not in args:
                  raise TypeError(
                      'Required argument `' + k + '` was not specified.')
          """
  
      if is_children_required:
          required_validation += """
          if 'children' not in _explicit_args:
              raise TypeError('Required argument children was not specified.')
          """
  
      default_arglist = []
  
      for prop_key in prop_keys:
          prop = props[prop_key]
          if (
              prop_key.endswith("-*")
              or prop_key in python_keywords
              or prop_key == "setProps"
          ):
              continue
  
          type_info = prop.get("type")
  
          if not type_info:
              print(f"Invalid prop type for typing: {prop_key}")
              default_arglist.append(f"{prop_key} = None")
              continue
  
          type_name = type_info.get("name")
  
          custom_props = get_custom_props(custom_typing_module)
          typed = get_prop_typing(
              type_name,
              typename,
              prop_key,
              type_info,
              custom_props=custom_props,
              custom_ignore=custom_ignore,
          )
  
          arg_value = f"{prop_key}: typing.Optional[{typed}] = None"
  
          default_arglist.append(arg_value)
  
      if max_props:
          final_max_props = max_props - (1 if "children" in props else 0)
          if len(default_arglist) > final_max_props:
              default_arglist = default_arglist[:final_max_props]
              docstring += (
                  "\n\n"
                  "Note: due to the large number of props for this component,\n"
                  "not all of them appear in the constructor signature, but\n"
                  "they may still be used as keyword arguments."
              )
  
      default_argtext += ",\n        ".join(default_arglist + ["**kwargs"])
      nodes = collect_nodes({k: v for k, v in props.items() if k != "children"})
  
      return dedent(
          c.format(
              typename=typename,
              namespace=namespace,
              filtered_props=filtered_props,
              list_of_valid_wildcard_attr_prefixes=wildcard_prefixes,
              list_of_valid_keys=list_of_valid_keys,
              docstring=docstring,
              default_argtext=default_argtext,
              args=args,
              argtext=argtext,
              required_validation=required_validation,
              children_props=nodes,
              base_nodes=filter_base_nodes(nodes) + ["children"],
              shapes="\n".join(shapes.get(typename, {}).values()),
          )
      )
  
  
  def generate_class_file(
      typename,
      props,
      description,
      namespace,
      prop_reorder_exceptions=None,
      max_props=None,
      custom_typing_module="dash_prop_typing",
  ):
      """Generate a Python class file (.py) given a class string.
      Parameters
      ----------
      typename
      props
      description
      namespace
      prop_reorder_exceptions
      Returns
      -------
      """
  
      class_string = generate_class_string(
          typename,
          props,
          description,
          namespace,
          prop_reorder_exceptions,
          max_props,
          custom_typing_module,
      )
  
      custom_imp = get_custom_imports(custom_typing_module)
      custom_imp = custom_imp.get(typename) or custom_imp.get("*")
  
      if custom_imp:
          imports = import_string.format(
              custom_imports="\n" + "\n".join(custom_imp) + "\n\n"
          )
      else:
          imports = import_string.format(custom_imports="")
  
      file_name = f"{typename:s}.py"
  
      file_path = os.path.join(namespace, file_name)
      with open(file_path, "w", encoding="utf-8") as f:
          f.write(imports)
          f.write(class_string)
  
      print(f"Generated {file_name}")
  
  
  def generate_imports(project_shortname, components):
      with open(
          os.path.join(project_shortname, "_imports_.py"), "w", encoding="utf-8"
      ) as f:
          component_imports = "\n".join(f"from .{x} import {x}" for x in components)
          all_list = ",\n".join(f'    "{x}"' for x in components)
          imports_string = f"{component_imports}\n\n__all__ = [\n{all_list}\n]"
  
          f.write(imports_string)
  
  
  def generate_classes_files(project_shortname, metadata, *component_generators):
      components = []
      for component_path, component_data in metadata.items():
          component_name = component_path.split("/")[-1].split(".")[0]
          components.append(component_name)
  
          for generator in component_generators:
              generator(
                  component_name,
                  component_data["props"],
                  component_data["description"],
                  project_shortname,
              )
  
      return components
  
  
  def generate_class(
      typename, props, description, namespace, prop_reorder_exceptions=None
  ):
      """Generate a Python class object given a class string.
      Parameters
      ----------
      typename
      props
      description
      namespace
      Returns
      -------
      """
      string = generate_class_string(
          typename, props, description, namespace, prop_reorder_exceptions
      )
      scope = {
          "Component": Component,
          "ComponentType": ComponentType,
          "_explicitize_args": _explicitize_args,
          "typing": typing,
          "numbers": numbers,
          "TypedDict": TypedDict,
          "NotRequired": NotRequired,
          "Literal": Literal,
          "NumberType": typing.Union[
              typing.SupportsFloat, typing.SupportsComplex, typing.SupportsInt
          ],
      }
      # pylint: disable=exec-used
      exec(string, scope)
      result = scope[typename]
      return result
  
  
  def required_props(props):
      """Pull names of required props from the props object.
      Parameters
      ----------
      props: dict
      Returns
      -------
      list
          List of prop names (str) that are required for the Component
      """
      return [prop_name for prop_name, prop in list(props.items()) if prop["required"]]
  
  
  def create_docstring(
      component_name,
      props,
      description,
      prop_reorder_exceptions=None,
      ignored_props=tuple(),
  ):
      """Create the Dash component docstring.
      Parameters
      ----------
      component_name: str
          Component name
      props: dict
          Dictionary with {propName: propMetadata} structure
      description: str
          Component description
      Returns
      -------
      str
          Dash component docstring
      """
      # Ensure props are ordered with children first
      props = (
          props
          if (
              prop_reorder_exceptions is not None
              and component_name in prop_reorder_exceptions
          )
          or (prop_reorder_exceptions is not None and "ALL" in prop_reorder_exceptions)
          else reorder_props(props)
      )
  
      n = "n" if component_name[0].lower() in "aeiou" else ""
      args = "\n".join(
          create_prop_docstring(
              prop_name=p,
              type_object=prop["type"] if "type" in prop else prop["flowType"],
              required=prop["required"],
              description=prop["description"],
              default=prop.get("defaultValue"),
              indent_num=0,
              is_flow_type="flowType" in prop and "type" not in prop,
          )
          for p, prop in filter_props(props, ignored_props).items()
      )
  
      return (
          f"A{n} {component_name} component.\n{description}\n\nKeyword arguments:\n{args}"
      )
  
  
  def prohibit_events(props):
      """Events have been removed. Raise an error if we see dashEvents or
      fireEvents.
      Parameters
      ----------
      props: dict
          Dictionary with {propName: propMetadata} structure
      Raises
      -------
      ?
      """
      if "dashEvents" in props or "fireEvents" in props:
          raise NonExistentEventException(
              "Events are no longer supported by dash. Use properties instead, "
              "eg `n_clicks` instead of a `click` event."
          )
  
  
  def parse_wildcards(props):
      """Pull out the wildcard attributes from the Component props.
      Parameters
      ----------
      props: dict
          Dictionary with {propName: propMetadata} structure
      Returns
      -------
      list
          List of Dash valid wildcard prefixes
      """
      list_of_valid_wildcard_attr_prefixes = []
      for wildcard_attr in ["data-*", "aria-*"]:
          if wildcard_attr in props:
              list_of_valid_wildcard_attr_prefixes.append(wildcard_attr[:-1])
      return list_of_valid_wildcard_attr_prefixes
  
  
  def reorder_props(props):
      """If "children" is in props, then move it to the front to respect dash
      convention, then 'id', then the remaining props sorted by prop name
      Parameters
      ----------
      props: dict
          Dictionary with {propName: propMetadata} structure
      Returns
      -------
      dict
          Dictionary with {propName: propMetadata} structure
      """
  
      # Constructing an OrderedDict with duplicate keys, you get the order
      # from the first one but the value from the last.
      # Doing this to avoid mutating props, which can cause confusion.
      props1 = [("children", "")] if "children" in props else []
      props2 = [("id", "")] if "id" in props else []
      return OrderedDict(props1 + props2 + sorted(list(props.items())))
  
  
  def filter_props(props, ignored_props=tuple()):
      """Filter props from the Component arguments to exclude:
          - Those without a "type" or a "flowType" field
          - Those with arg.type.name in {'func', 'symbol', 'instanceOf'}
      Parameters
      ----------
      props: dict
          Dictionary with {propName: propMetadata} structure
      Returns
      -------
      dict
          Filtered dictionary with {propName: propMetadata} structure
      Examples
      --------
      ```python
      prop_args = {
          'prop1': {
              'type': {'name': 'bool'},
              'required': False,
              'description': 'A description',
              'flowType': {},
              'defaultValue': {'value': 'false', 'computed': False},
          },
          'prop2': {'description': 'A prop without a type'},
          'prop3': {
              'type': {'name': 'func'},
              'description': 'A function prop',
          },
      }
      # filtered_prop_args is now
      # {
      #    'prop1': {
      #        'type': {'name': 'bool'},
      #        'required': False,
      #        'description': 'A description',
      #        'flowType': {},
      #        'defaultValue': {'value': 'false', 'computed': False},
      #    },
      # }
      filtered_prop_args = filter_props(prop_args)
      ```
      """
      filtered_props = copy.deepcopy(props)
  
      for arg_name, arg in list(filtered_props.items()):
          if arg_name in ignored_props or ("type" not in arg and "flowType" not in arg):
              filtered_props.pop(arg_name)
              continue
  
          # Filter out functions and instances --
          # these cannot be passed from Python
          if "type" in arg:  # These come from PropTypes
              arg_type = arg["type"]["name"]
              if arg_type in {"func", "symbol", "instanceOf"}:
                  filtered_props.pop(arg_name)
          elif "flowType" in arg:  # These come from Flow & handled differently
              arg_type_name = arg["flowType"]["name"]
              if arg_type_name == "signature":
                  # This does the same as the PropTypes filter above, but "func"
                  # is under "type" if "name" is "signature" vs just in "name"
                  if "type" not in arg["flowType"] or arg["flowType"]["type"] != "object":
                      filtered_props.pop(arg_name)
          else:
              raise ValueError
  
      return filtered_props
  
  
  def fix_keywords(txt):
      """
      replaces javascript keywords true, false, null with Python keywords
      """
      fix_word = {"true": "True", "false": "False", "null": "None"}
      for js_keyword, python_keyword in fix_word.items():
          txt = txt.replace(js_keyword, python_keyword)
      return txt
  
  
  # pylint: disable=too-many-arguments
  # pylint: disable=too-many-locals
  def create_prop_docstring(
      prop_name,
      type_object,
      required,
      description,
      default,
      indent_num,
      is_flow_type=False,
  ):
      """Create the Dash component prop docstring.
      Parameters
      ----------
      prop_name: str
          Name of the Dash component prop
      type_object: dict
          react-docgen-generated prop type dictionary
      required: bool
          Component is required?
      description: str
          Dash component description
      default: dict
          Either None if a default value is not defined, or
          dict containing the key 'value' that defines a
          default value for the prop
      indent_num: int
          Number of indents to use for the context block
          (creates 2 spaces for every indent)
      is_flow_type: bool
          Does the prop use Flow types? Otherwise, uses PropTypes
      Returns
      -------
      str
          Dash component prop docstring
      """
      py_type_name = js_to_py_type(
          type_object=type_object, is_flow_type=is_flow_type, indent_num=indent_num
      )
      indent_spacing = "  " * indent_num
  
      default = default["value"] if default else ""
      default = fix_keywords(default)
  
      is_required = "optional"
      if required:
          is_required = "required"
      elif default and default not in ["None", "{}", "[]"]:
          is_required = "default " + default.replace("\n", "")
  
      # formats description
      period = "." if description else ""
      description = description.strip().strip(".").replace('"', r"\"") + period
      desc_indent = indent_spacing + "    "
      description = fill(
          description,
          initial_indent=desc_indent,
          subsequent_indent=desc_indent,
          break_long_words=False,
          break_on_hyphens=False,
      )
      description = f"\n{description}" if description else ""
      colon = ":" if description else ""
      description = fix_keywords(description)
  
      if "\n" in py_type_name:
          # corrects the type
          dict_or_list = "list of dicts" if py_type_name.startswith("list") else "dict"
  
          # format and rewrite the intro to the nested dicts
          intro1, intro2, dict_descr = py_type_name.partition("with keys:")
          intro = f"`{prop_name}` is a {intro1}{intro2}"
          intro = fill(
              intro,
              initial_indent=desc_indent,
              subsequent_indent=desc_indent,
              break_long_words=False,
              break_on_hyphens=False,
          )
  
          # captures optional nested dict description and puts the "or" condition on a new line
          if "| dict with keys:" in dict_descr:
              dict_part1, dict_part2 = dict_descr.split(" |", 1)
              dict_part2 = "".join([desc_indent, "Or", dict_part2])
              dict_descr = f"{dict_part1}\n\n  {dict_part2}"
  
          # ensures indent is correct if there is a second nested list of dicts
          current_indent = dict_descr.lstrip("\n").find("-")
          if current_indent == len(indent_spacing):
              dict_descr = "".join(
                  "\n\n    " + line for line in dict_descr.splitlines() if line != ""
              )
  
          return (
              f"\n{indent_spacing}- {prop_name} ({dict_or_list}; {is_required}){colon}"
              f"{description}"
              f"\n\n{intro}{dict_descr}"
          )
      tn = f"{py_type_name}; " if py_type_name else ""
      return f"\n{indent_spacing}- {prop_name} ({tn}{is_required}){colon}{description}"
  
  
  def map_js_to_py_types_prop_types(type_object, indent_num):
      """Mapping from the PropTypes js type object to the Python type."""
  
      def shape_or_exact():
          return "dict with keys:\n" + "\n".join(
              create_prop_docstring(
                  prop_name=prop_name,
                  type_object=prop,
                  required=prop["required"],
                  description=prop.get("description", ""),
                  default=prop.get("defaultValue"),
                  indent_num=indent_num + 2,
              )
              for prop_name, prop in type_object["value"].items()
          )
  
      def array_of():
          inner = js_to_py_type(type_object["value"])
          if inner:
              return "list of " + (
                  inner + "s"
                  if inner.split(" ")[0] != "dict"
                  else inner.replace("dict", "dicts", 1)
              )
          return "list"
  
      def tuple_of():
          elements = [js_to_py_type(element) for element in type_object["elements"]]
          return f"list of {len(elements)} elements: [{', '.join(elements)}]"
  
      return dict(
          array=lambda: "list",
          bool=lambda: "boolean",
          number=lambda: "number",
          string=lambda: "string",
          object=lambda: "dict",
          any=lambda: "boolean | number | string | dict | list",
          element=lambda: "dash component",
          node=lambda: "a list of or a singular dash component, string or number",
          # React's PropTypes.oneOf
          enum=lambda: (
              "a value equal to: "
              + ", ".join(str(t["value"]) for t in type_object["value"])
          ),
          # React's PropTypes.oneOfType
          union=lambda: " | ".join(
              js_to_py_type(subType)
              for subType in type_object["value"]
              if js_to_py_type(subType) != ""
          ),
          # React's PropTypes.arrayOf
          arrayOf=array_of,
          # React's PropTypes.objectOf
          objectOf=lambda: (
              "dict with strings as keys and values of type "
              + js_to_py_type(type_object["value"])
          ),
          # React's PropTypes.shape
          shape=shape_or_exact,
          # React's PropTypes.exact
          exact=shape_or_exact,
          tuple=tuple_of,
      )
  
  
  def map_js_to_py_types_flow_types(type_object):
      """Mapping from the Flow js types to the Python type."""
      return dict(
          array=lambda: "list",
          boolean=lambda: "boolean",
          number=lambda: "number",
          string=lambda: "string",
          Object=lambda: "dict",
          any=lambda: "bool | number | str | dict | list",
          Element=lambda: "dash component",
          Node=lambda: "a list of or a singular dash component, string or number",
          # React's PropTypes.oneOfType
          union=lambda: " | ".join(
              js_to_py_type(subType)
              for subType in type_object["elements"]
              if js_to_py_type(subType) != ""
          ),
          # Flow's Array type
          Array=lambda: "list"
          + (
              f' of {js_to_py_type(type_object["elements"][0])}s'
              if js_to_py_type(type_object["elements"][0]) != ""
              else ""
          ),
          # React's PropTypes.shape
          signature=lambda indent_num: (
              "dict with keys:\n"
              + "\n".join(
                  create_prop_docstring(
                      prop_name=prop["key"],
                      type_object=prop["value"],
                      required=prop["value"]["required"],
                      description=prop["value"].get("description", ""),
                      default=prop.get("defaultValue"),
                      indent_num=indent_num + 2,
                      is_flow_type=True,
                  )
                  for prop in type_object["signature"]["properties"]
              )
          ),
      )
  
  
  def js_to_py_type(type_object, is_flow_type=False, indent_num=0):
      """Convert JS types to Python types for the component definition.
      Parameters
      ----------
      type_object: dict
          react-docgen-generated prop type dictionary
      is_flow_type: bool
          Does the prop use Flow types? Otherwise, uses PropTypes
      indent_num: int
          Number of indents to use for the docstring for the prop
      Returns
      -------
      str
          Python type string
      """
  
      js_type_name = type_object["name"]
      js_to_py_types = (
          map_js_to_py_types_flow_types(type_object=type_object)
          if is_flow_type
          else map_js_to_py_types_prop_types(
              type_object=type_object, indent_num=indent_num
          )
      )
  
      if (
          "computed" in type_object
          and type_object["computed"]
          or type_object.get("type", "") == "function"
      ):
          return ""
      if js_type_name in js_to_py_types:
          if js_type_name == "signature":  # This is a Flow object w/ signature
              return js_to_py_types[js_type_name](indent_num)  # type: ignore[reportCallIssue]
          # All other types
          return js_to_py_types[js_type_name]()  # type: ignore[reportCallIssue]
      return ""
  