import ast
import dataclasses
from typing import Dict, List, Union

from . import enums
from .abstract import ApiClass, get_clean_function_source_code_for_agent, validate_constructor_arg_types


def validate_input_dict_param(dict_object, friendly_class_name, must_contain=[]):
    if not isinstance(dict_object, dict):
        raise ValueError(friendly_class_name, 'Invalid argument. Provided argument should be a dictionary.')
    if any(field not in dict_object for field in must_contain):
        raise ValueError(friendly_class_name, f'One or more keys are missing in the argument provided. Must contain keys - {must_contain}.')


@dataclasses.dataclass
class FieldDescriptor(ApiClass):
    """
    Configs for vector store indexing.

    Args:
        field (str): The field to be extracted. This will be used as the key in the response.
        description (str): The description of this field. If not included, the response_field will be used.
        example_extraction (Union[str, int, bool, float]): An example of this extracted field.
        type (FieldDescriptorType): The type of this field. If not provided, the default type is STRING.
    """
    field: str = dataclasses.field()
    description: str = dataclasses.field(default=None)
    example_extraction: Union[str, int, bool, float, list, dict] = dataclasses.field(default=None)
    type: enums.FieldDescriptorType = dataclasses.field(default=enums.FieldDescriptorType.STRING)


@dataclasses.dataclass
class JSONSchema:
    @classmethod
    def from_fields_list(cls, fields_list: List[str]):
        json_schema = {
            'type': 'object',
            'properties': {field: {'title': field, 'type': 'string'} for field in fields_list}
        }
        return cls(json_schema=json_schema)

    @classmethod
    def to_fields_list(cls, json_schema) -> List[str]:
        if 'properties' in json_schema:
            return list(json_schema['properties'].keys())
        return []


@validate_constructor_arg_types('input_mapping')
@dataclasses.dataclass
class WorkflowNodeInputMapping(ApiClass):
    """
    Represents a mapping of inputs to a workflow node.

    Args:
        name (str): The name of the input variable of the node function.
        variable_type (WorkflowNodeInputType): The type of the input.
        variable_source (str): The name of the node this variable is sourced from.
                               If the type is `WORKFLOW_VARIABLE`, the value given by the source node will be directly used.
                               If the type is `USER_INPUT`, the value given by the source node will be used as the default initial value before the user edits it.
                               Set to `None` if the type is `USER_INPUT` and the variable doesn't need a pre-filled initial value.
        is_required (bool): Indicates whether the input is required. Defaults to True.
    """
    name: str
    variable_type: enums.WorkflowNodeInputType
    variable_source: str = dataclasses.field(default=None)
    source_prop: str = dataclasses.field(default=None)
    is_required: bool = dataclasses.field(default=True)

    def to_dict(self):
        return {
            'name': self.name,
            'variable_type': self.variable_type.value,
            'variable_source': self.variable_source,
            'source_prop': self.source_prop or self.name,
            'is_required': self.is_required
        }

    @classmethod
    def from_dict(cls, mapping: dict):
        validate_input_dict_param(mapping, friendly_class_name='input_mapping', must_contain=['name', 'variable_type'])
        if mapping['variable_type'] not in enums.WorkflowNodeInputType.__members__:
            raise ValueError('input_mapping', f"Invalid enum argument {mapping['variable_type']}. Provided argument should be of enum type WorkflowNodeInputType.")
        return cls(
            name=mapping['name'],
            variable_type=enums.WorkflowNodeInputType(mapping['variable_type']),
            variable_source=mapping.get('variable_source'),
            source_prop=mapping.get('source_prop') or mapping['name'] if mapping.get('variable_source') else None,
            is_required=mapping.get('is_required', True)
        )


@validate_constructor_arg_types('input_schema')
@dataclasses.dataclass
class WorkflowNodeInputSchema(ApiClass, JSONSchema):
    """
    A schema conformant to react-jsonschema-form for workflow node input.

    To initialize a WorkflowNodeInputSchema dependent on another node's output, use the from_workflow_node method.

    Args:
        json_schema (dict): The JSON schema for the input, conformant to react-jsonschema-form specification. Must define keys like "title", "type", and "properties". Supported elements include Checkbox, Radio Button, Dropdown, Textarea, Number, Date, and file upload. Nested elements, arrays, and other complex types are not supported.
        ui_schema (dict): The UI schema for the input, conformant to react-jsonschema-form specification.
    """
    json_schema: dict
    ui_schema: dict = dataclasses.field(default_factory=dict)
    schema_source: str = dataclasses.field(default=None, init=False)
    schema_prop: str = dataclasses.field(default=None, init=False)
    runtime_schema: bool = dataclasses.field(default=False, init=False)

    def to_dict(self):
        if self.runtime_schema:
            return {
                'schema_source': self.schema_source,
                'schema_prop': self.schema_prop,
                'runtime_schema': self.runtime_schema
            }
        else:
            return {
                'json_schema': self.json_schema,
                'ui_schema': self.ui_schema,
                'runtime_schema': self.runtime_schema
            }

    @classmethod
    def from_dict(cls, schema: dict):
        validate_input_dict_param(schema, friendly_class_name='input_schema')
        if schema.get('runtime_schema', False):
            return cls.from_workflow_node(
                schema_source=schema.get('schema_source'),
                schema_prop=schema.get('schema_prop')
            )
        else:
            return cls(
                json_schema=schema.get('json_schema', schema),
                ui_schema=schema.get('ui_schema', {})
            )

    @classmethod
    def from_workflow_node(cls, schema_source: str, schema_prop: str):
        """
        Creates a WorkflowNodeInputSchema instance which references the schema generated by a WorkflowGraphNode.

        Args:
            schema_source (str): The name of the source WorkflowGraphNode.
            schema_prop (str): The name of the input schema parameter which source node outputs.
        """
        if not schema_source or not schema_prop:
            raise ValueError('input_schema', 'Valid schema_source and schema_prop must be provided for runtime schema.')
        if not isinstance(schema_source, str) or not isinstance(schema_prop, str):
            raise ValueError('input_schema', 'schema_source and schema_prop must be strings.')
        instance = cls(json_schema={})
        instance.schema_source = schema_source
        instance.schema_prop = schema_prop
        instance.runtime_schema = True
        return instance


@validate_constructor_arg_types('output_mapping')
@dataclasses.dataclass
class WorkflowNodeOutputMapping(ApiClass):
    """
    Represents a mapping of output from a workflow node.

    Args:
        name (str): The name of the output.
        variable_type (Union[WorkflowNodeOutputType, str]): The type of the output in the form of an enum or a string.
    """
    name: str
    variable_type: Union[enums.WorkflowNodeOutputType, str] = dataclasses.field(default=enums.WorkflowNodeOutputType.ANY)

    def __post_init__(self):
        if isinstance(self.variable_type, str):
            self.variable_type = enums.WorkflowNodeOutputType(self.variable_type)

    def to_dict(self):
        return {
            'name': self.name,
            'variable_type': self.variable_type.value
        }

    @classmethod
    def from_dict(cls, mapping: dict):
        validate_input_dict_param(mapping, friendly_class_name='output_mapping', must_contain=['name'])
        variable_type = mapping.get('variable_type', 'ANY')
        if variable_type not in enums.WorkflowNodeOutputType.__members__:
            raise ValueError('output_mapping', f'Invalid enum argument {variable_type}. Provided argument should be of enum type WorkflowNodeOutputType.')
        return cls(
            name=mapping['name'],
            variable_type=enums.WorkflowNodeOutputType(variable_type)
        )


@validate_constructor_arg_types('output_schema')
@dataclasses.dataclass
class WorkflowNodeOutputSchema(ApiClass, JSONSchema):
    """
    A schema conformant to react-jsonschema-form for a workflow node output.

    Args:
        json_schema (dict): The JSON schema for the output, conformant to react-jsonschema-form specification.
    """
    json_schema: dict

    def to_dict(self):
        return {
            'json_schema': self.json_schema
        }

    @classmethod
    def from_dict(cls, schema: dict):
        validate_input_dict_param(schema, friendly_class_name='output_schema')
        return cls(
            json_schema=schema.get('json_schema', schema)
        )


@validate_constructor_arg_types('workflow_graph_node')
@dataclasses.dataclass
class WorkflowGraphNode(ApiClass):
    """
    Represents a node in an Agent workflow graph.

    Args:
        name (str): A unique name for the workflow node.
        input_mappings (List[WorkflowNodeInputMapping]): List of input mappings for the node. Each arg/kwarg of the node function should have a corresponding input mapping.
        output_mappings (List[WorkflowNodeOutputMapping]): List of output mappings for the node. Each field in the returned dict/AgentResponse must have a corresponding output mapping.
        function (callable): The callable node function reference.
        input_schema (WorkflowNodeInputSchema): The react json schema for the user input variables.
        output_schema (WorkflowNodeOutputSchema): The react json schema for the output to be shown on UI.

    Additional Attributes:
        function_name (str): The name of the function.
        source_code (str): The source code of the function.
    """

    def __init__(self, name: str, input_mappings: Union[Dict[str, WorkflowNodeInputMapping], List[WorkflowNodeInputMapping]] = None, output_mappings: Union[List[str], Dict[str, str], List[WorkflowNodeOutputMapping]] = None, function: callable = None, function_name: str = None, source_code: str = None, input_schema: Union[List[str], WorkflowNodeInputSchema] = None, output_schema: Union[List[str], WorkflowNodeOutputSchema] = None, template_metadata: dict = None):
        self.template_metadata = template_metadata
        if self.template_metadata and not self.template_metadata.get('initialized'):
            self.name = name
            self.function_name = None
            self.source_code = None
            self.input_mappings = input_mappings
            self.output_mappings = []
            self.input_schema = input_schema
            self.output_schema = output_schema
        else:
            if function:
                self.function = function
                self.function_name = function.__name__
                self.source_code = get_clean_function_source_code_for_agent(function)
            elif function_name and source_code:
                self.function_name = function_name
                self.source_code = source_code
            else:
                raise ValueError('workflow_graph_node', 'Either function or function_name and source_code must be provided.')

            self.name = name
            try:
                tree = ast.parse(self.source_code)
            except SyntaxError as e:
                raise ValueError('workflow_graph_node', f'SyntaxError: "{e}"')
            arg_defaults = {}
            function_found = False
            for node in ast.iter_child_nodes(tree):
                if isinstance(node, ast.FunctionDef) and node.name == self.function_name:
                    function_found = True
                    input_arguments = [arg.arg for arg in node.args.args]
                    defaults = [None] * (len(input_arguments) - len(node.args.defaults)) + node.args.defaults
                    arg_defaults = dict(zip(input_arguments, defaults))
            if not function_found:
                raise ValueError('workflow_graph_node', f'Function "{self.function_name}" not found in the provided source code.')

            is_shortform_input_mappings = False
            if input_mappings is None:
                input_mappings = []
            if isinstance(input_mappings, List) and all(isinstance(input, WorkflowNodeInputMapping) for input in input_mappings):
                self.input_mappings = input_mappings
                input_mapping_args = [input.name for input in input_mappings]
                for input_name in input_mapping_args:
                    if input_name not in arg_defaults:
                        raise ValueError('workflow_graph_node', f'Invalid input mapping. Argument "{input_name}" not found in function "{self.function_name}".')
                for arg, default in arg_defaults.items():
                    if arg not in input_mapping_args:
                        self.input_mappings.append(WorkflowNodeInputMapping(name=arg, variable_type=enums.WorkflowNodeInputType.USER_INPUT, is_required=default is None))
            elif isinstance(input_mappings, Dict) and all(isinstance(key, str) and isinstance(value, WorkflowNodeInputMapping) for key, value in input_mappings.items()):
                is_shortform_input_mappings = True
                self.input_mappings = [WorkflowNodeInputMapping(name=arg, variable_type=enums.WorkflowNodeInputType.USER_INPUT, is_required=default is None) for arg, default in arg_defaults.items() if arg not in input_mappings]
                for key, value in input_mappings.items():
                    if key not in arg_defaults:
                        raise ValueError('workflow_graph_node', f'Invalid input mapping. Argument "{key}" not found in function "{self.function_name}".')
                    self.input_mappings.append(WorkflowNodeInputMapping(name=key, variable_type=value.variable_type, variable_source=value.variable_source, source_prop=value.source_prop, is_required=arg_defaults.get(key) is None))
            else:
                raise ValueError('workflow_graph_node', 'Invalid input mappings. Must be a list of WorkflowNodeInputMapping or a dictionary of input mappings in the form {arg_name: node_name.outputs.prop_name}.')

            if output_mappings is None:
                output_mappings = []
            if isinstance(output_mappings, List):
                if all(isinstance(output, WorkflowNodeOutputMapping) for output in output_mappings):
                    self.output_mappings = output_mappings
                elif all(isinstance(output, str) for output in output_mappings):
                    self.output_mappings = [WorkflowNodeOutputMapping(name=output, variable_type=enums.WorkflowNodeOutputType.STRING) for output in output_mappings]
                else:
                    raise ValueError('workflow_graph_node', 'Invalid output mappings. Must be a list of WorkflowNodeOutputMapping or a list of output names or a dictionary of output mappings in the form {output_name: output_type}.')
            elif isinstance(output_mappings, Dict):
                self.output_mappings = [WorkflowNodeOutputMapping(name=output, variable_type=enums.WorkflowNodeOutputType.normalize_type(type)) for output, type in output_mappings.items()]
            else:
                raise ValueError('workflow_graph_node', 'Invalid output mappings. Must be a list of WorkflowNodeOutputMapping or a list of output names or a dictionary of output mappings in the form {output_name: output_type}.')

            if not input_schema:
                self.input_schema = WorkflowNodeInputSchema(json_schema={}, ui_schema={})
            elif isinstance(input_schema, WorkflowNodeInputSchema):
                self.input_schema = input_schema
            elif isinstance(input_schema, list) and all(isinstance(field, str) for field in input_schema):
                self.input_schema = WorkflowNodeInputSchema.from_fields_list(input_schema)
            else:
                raise ValueError('workflow_graph_node', 'Invalid input schema. Must be a WorkflowNodeInputSchema or a list of field names.')

            if is_shortform_input_mappings:
                user_input_fields = JSONSchema.to_fields_list(self.input_schema)
                for mapping in self.input_mappings:
                    if mapping.name in user_input_fields:
                        mapping.variable_type = enums.WorkflowNodeInputType.USER_INPUT

            if not output_schema:
                self.output_schema = WorkflowNodeOutputSchema({})
            elif isinstance(output_schema, WorkflowNodeOutputSchema):
                self.output_schema = output_schema
            elif isinstance(output_schema, list) and all(isinstance(field, str) for field in output_schema):
                self.output_schema = WorkflowNodeOutputSchema.from_fields_list(output_schema)
            else:
                raise ValueError('workflow_graph_node', 'Invalid output schema. Must be a WorkflowNodeOutputSchema or a list of output section names.')

    @classmethod
    def _raw_init(cls, name: str, input_mappings: List[WorkflowNodeInputMapping] = None, output_mappings: List[WorkflowNodeOutputMapping] = None, function: callable = None, function_name: str = None, source_code: str = None, input_schema: WorkflowNodeInputSchema = None, output_schema: WorkflowNodeOutputSchema = None, template_metadata: dict = None):
        workflow_node = cls.__new__(cls, name, input_mappings, output_mappings, input_schema, output_schema, template_metadata)
        workflow_node.name = name
        if function:
            workflow_node.function = function
            workflow_node.function_name = function.__name__
            workflow_node.source_code = get_clean_function_source_code_for_agent(function)
        elif function_name and source_code:
            workflow_node.function_name = function_name
            workflow_node.source_code = source_code
        else:
            raise ValueError('workflow_graph_node', 'Either function or function_name and source_code must be provided.')
        workflow_node.input_mappings = input_mappings
        workflow_node.output_mappings = output_mappings
        workflow_node.input_schema = input_schema
        workflow_node.output_schema = output_schema
        workflow_node.template_metadata = template_metadata
        return workflow_node

    @classmethod
    def from_template(cls, template_name: str, name: str, configs: dict = None, input_mappings: Union[Dict[str, WorkflowNodeInputMapping], List[WorkflowNodeInputMapping]] = None, input_schema: Union[List[str], WorkflowNodeInputSchema] = None, output_schema: Union[List[str], WorkflowNodeOutputSchema] = None):

        instance_input_mappings = []
        if isinstance(input_mappings, List) and all(isinstance(input, WorkflowNodeInputMapping) for input in input_mappings):
            instance_input_mappings = input_mappings
        elif isinstance(input_mappings, Dict) and all(isinstance(key, str) and isinstance(value, WorkflowNodeInputMapping) for key, value in input_mappings.items()):
            instance_input_mappings = [WorkflowNodeInputMapping(name=arg, variable_type=mapping.variable_type, variable_source=mapping.variable_source, source_prop=mapping.source_prop) for arg, mapping in input_mappings]
        elif input_mappings is None:
            instance_input_mappings = []
        else:
            raise ValueError('workflow_graph_node', 'Invalid input mappings. Must be a list of WorkflowNodeInputMapping or a dictionary of input mappings in the form {arg_name: node_name.outputs.prop_name}.')

        instance_input_schema = None
        if input_schema is None:
            instance_input_schema = WorkflowNodeInputSchema(json_schema={}, ui_schema={})
        elif isinstance(input_schema, WorkflowNodeInputSchema):
            instance_input_schema = input_schema
        elif isinstance(input_schema, list) and all(isinstance(field, str) for field in input_schema):
            instance_input_schema = JSONSchema.from_fields_list(input_schema)
        else:
            raise ValueError('workflow_graph_node', 'Invalid input schema. Must be a WorkflowNodeInputSchema or a list of field names.')

        instance_output_schema = None
        if output_schema is None:
            instance_output_schema = WorkflowNodeOutputSchema(json_schema={})
        elif isinstance(output_schema, WorkflowNodeOutputSchema):
            instance_output_schema = output_schema
        elif isinstance(output_schema, list) and all(isinstance(field, str) for field in output_schema):
            instance_output_schema = JSONSchema.from_fields_list(output_schema)
        else:
            raise ValueError('workflow_graph_node', 'Invalid output schema. Must be a WorkflowNodeOutputSchema or a list of output section names.')

        return cls(
            name=name,
            input_mappings=instance_input_mappings,
            input_schema=instance_input_schema,
            output_schema=instance_output_schema,
            template_metadata={
                'template_name': template_name,
                'configs': configs or {},
                'initialized': False
            }
        )

    def to_dict(self):
        return {
            'name': self.name,
            'function_name': self.function_name,
            'source_code': self.source_code,
            'input_mappings': [mapping.to_dict() for mapping in self.input_mappings],
            'output_mappings': [mapping.to_dict() for mapping in self.output_mappings],
            'input_schema': self.input_schema.to_dict(),
            'output_schema': self.output_schema.to_dict(),
            'template_metadata': self.template_metadata
        }

    @classmethod
    def from_dict(cls, node: dict):
        validate_input_dict_param(node, friendly_class_name='workflow_graph_node', must_contain=['name', 'function_name', 'source_code'])
        _cls = cls._raw_init if node.get('__return_filter') else cls
        instance = _cls(
            name=node['name'],
            function_name=node['function_name'],
            source_code=node['source_code'],
            input_mappings=[WorkflowNodeInputMapping.from_dict(mapping) for mapping in node.get('input_mappings', [])],
            output_mappings=[WorkflowNodeOutputMapping.from_dict(mapping) for mapping in node.get('output_mappings', [])],
            input_schema=WorkflowNodeInputSchema.from_dict(node.get('input_schema', {})),
            output_schema=WorkflowNodeOutputSchema.from_dict(node.get('output_schema', {})),
            template_metadata=node.get('template_metadata')
        )
        return instance

    def __setattr__(self, name, value):
        super().__setattr__(name, value)
        if name == 'function':
            if value:
                self.function_name = value.__name__
                self.source_code = get_clean_function_source_code_for_agent(value)

    def __getattribute__(self, name):
        if name == 'function':
            try:
                val = super().__getattribute__(name)
            except AttributeError:
                val = None
            if val is None and self.function_name and self.source_code:
                raise AttributeError("This WorkflowGraphNode object was not created using a callable `function`. Please refer to `function_name` and `source_code` attributes to get it's function's details.")
            return val
        return super().__getattribute__(name)

    class Outputs:
        def __init__(self, node: 'WorkflowGraphNode'):
            self.node = node

        def __getattr__(self, name):
            for mapping in self.node.output_mappings:
                if mapping.name == name:
                    return WorkflowNodeInputMapping(None, enums.WorkflowNodeInputType.WORKFLOW_VARIABLE, variable_source=self.node.name, source_prop=name)
            raise AttributeError(f'Output mapping "{name}" not found in node "{self.name}".')

    @property
    def outputs(self):
        return self.Outputs(self)


@validate_constructor_arg_types('workflow_graph_edge')
@dataclasses.dataclass
class WorkflowGraphEdge(ApiClass):
    """
    Represents an edge in an Agent workflow graph.

    To make an edge conditional, provide {'EXECUTION_CONDITION': '<condition>'} key-value in the details dictionary.
    The condition should be a Pythonic expression string that evaluates to a boolean value and only depends on the outputs of the source node of the edge.

    Args:
        source (str): The name of the source node of the edge.
        target (str): The name of the target node of the edge.
        details (dict): Additional details about the edge. Like the condition for edge execution.
    """
    source: Union[str, WorkflowGraphNode]
    target: Union[str, WorkflowGraphNode]
    details: dict = dataclasses.field(default_factory=dict)

    def __init__(self, source: Union[str, WorkflowGraphNode], target: Union[str, WorkflowGraphNode], details: dict = None):
        self.source = source.name if isinstance(source, WorkflowGraphNode) else source
        self.target = target.name if isinstance(target, WorkflowGraphNode) else target
        self.details = details or {}

    def to_nx_edge(self):
        return [self.source, self.target, self.details]


@validate_constructor_arg_types('workflow_graph')
@dataclasses.dataclass
class WorkflowGraph(ApiClass):
    """
    Represents an Agent workflow graph.

    The edges define the node invocation order.

    Args:
        nodes (List[WorkflowGraphNode]): A list of nodes in the workflow graph.
        edges (List[WorkflowGraphEdge]): A list of edges in the workflow graph, where each edge is a tuple of source, target, and details.
        primary_start_node (Union[str, WorkflowGraphNode]): The primary node to start the workflow from.
    """
    nodes: List[WorkflowGraphNode] = dataclasses.field(default_factory=list)
    edges: List[WorkflowGraphEdge] = dataclasses.field(default_factory=list)
    primary_start_node: Union[str, WorkflowGraphNode] = dataclasses.field(default=None)
    common_source_code: str = dataclasses.field(default=None)

    def to_dict(self):
        return {
            'nodes': [node.to_dict() for node in self.nodes],
            'edges': [edge.to_dict() for edge in self.edges],
            'primary_start_node': self.primary_start_node.name if isinstance(self.primary_start_node, WorkflowGraphNode) else self.primary_start_node,
            'common_source_code': self.common_source_code
        }

    @classmethod
    def from_dict(cls, graph: dict):
        validate_input_dict_param(graph, friendly_class_name='workflow_graph')
        if graph.get('__return_filter'):
            for node in graph.get('nodes', []):
                node['__return_filter'] = True
        nodes = [WorkflowGraphNode.from_dict(node) for node in graph.get('nodes', [])]
        edges = [WorkflowGraphEdge.from_dict(edge) for edge in graph.get('edges', [])]
        if graph.get('primary_start_node') is None:
            non_primary_nodes = set()
            for edge in edges:
                non_primary_nodes.add(edge.target)
            primary_nodes = set([node.name for node in nodes]) - non_primary_nodes
            graph['primary_start_node'] = primary_nodes.pop() if primary_nodes else None

        return cls(
            nodes=nodes,
            edges=edges,
            primary_start_node=graph.get('primary_start_node', None),
            common_source_code=graph.get('common_source_code', None)
        )


@dataclasses.dataclass
class AgentConversationMessage(ApiClass):
    """
    Message format for agent conversation

    Args:
        is_user (bool): Whether the message is from the user.
        text (str): The message's text.
        document_contents (dict): Dict of document name to document text in case of any document present.
    """
    is_user: bool = dataclasses.field(default=None)
    text: str = dataclasses.field(default=None)
    document_contents: dict = dataclasses.field(default=None)

    def to_dict(self):
        return {
            'is_user': self.is_user,
            'text': self.text,
            'document_contents': self.document_contents
        }


@dataclasses.dataclass
class WorkflowNodeTemplateConfig(ApiClass):
    """
    Represents a WorkflowNode template config.

    Args:
        name (str): A unique name of the config.
        description (str): The description of this config.
        default_value (str): Default value of the config to be used if value is not provided during node initialization.
        is_required (bool): Indicates whether the config is required. Defaults to False.
    """
    name: str
    description: str = dataclasses.field(default=None)
    default_value: str = dataclasses.field(default=None)
    is_required: bool = dataclasses.field(default=False)

    def to_dict(self):
        return {
            'name': self.name,
            'description': self.description,
            'default_value': self.default_value,
            'is_required': self.is_required
        }

    @classmethod
    def from_dict(cls, mapping: dict):
        return cls(
            name=mapping['name'],
            description=mapping.get('description'),
            default_value=mapping.get('default_value'),
            is_required=mapping.get('is_required', False)
        )


@dataclasses.dataclass
class WorkflowNodeTemplateInput(ApiClass):
    """
    Represents an input to the workflow node generated using template.

    Args:
        name (str): A unique name of the input.
        is_required (bool): Indicates whether the input is required. Defaults to False.
        description (str): The description of this input.
    """
    name: str
    is_required: bool = dataclasses.field(default=False)
    description: str = dataclasses.field(default='')

    def to_dict(self):
        return {
            'name': self.name,
            'is_required': self.is_required,
            'description': self.description
        }

    @classmethod
    def from_dict(cls, mapping: dict):
        return cls(
            name=mapping['name'],
            is_required=mapping.get('is_required', False),
            description=mapping.get('description', '')
        )


@dataclasses.dataclass
class WorkflowNodeTemplateOutput(ApiClass):
    """
    Represents an output returned by the workflow node generated using template.

    Args:
        name (str): The name of the output.
        variable_type (WorkflowNodeOutputType): The type of the output.
        description (str): The description of this output.
    """
    name: str
    variable_type: enums.WorkflowNodeOutputType = dataclasses.field(default=enums.WorkflowNodeOutputType.ANY)
    description: str = dataclasses.field(default='')

    def to_dict(self):
        return {
            'name': self.name,
            'variable_type': self.variable_type.value,
            'description': self.description
        }

    @classmethod
    def from_dict(cls, mapping: dict):
        return cls(
            name=mapping['name'],
            variable_type=enums.WorkflowNodeOutputType(mapping.get('variable_type', 'ANY')),
            description=mapping.get('description', '')
        )
