"""
I commented this file by now because this is
not a good implementation of it, and I'm not
using it yet, so I prefer to remove uncomplete
and bad code to be able to have the concepts
more clear and clean. We were using it in
'yta-editor' but in a sh*tty way, so we can
safely omit it by now until we implement it
properly and it is a real graph adapted to the
new changes that are coming.
"""
# """
# Our awesome editor module in which we have
# all the classes that interact with it and
# make it possible.

# This is the nodes module, in which we have
# all the classes that make the concept work.
# """
# from yta_editor_nodes.timeline.serial import SerialTimelineNode
# from yta_editor_nodes.timeline.parallel import ParallelTimelineNode
# from yta_editor_frame.buffer import FrameBuffer
# from yta_editor_frame.decorators import force_framebuffer
# from yta_editor_time.evaluation_context import EvaluationContext
# from yta_validation import PythonValidator
# from typing import Union


# class TimelineGraph:
#     """
#     Graph of nodes (serie and parallel) to process
#     the input, handle it and generate an output.

#     The graph includes a starting (root) node that
#     will begin the process, and maybe other nodes
#     connected to it as output nodes.

#     The nodes will be interconnected, having a
#     `t_start` and `t_end`. The sequence of nodes
#     determines the order, and the time range if
#     they should modify the input or not.
#     """

#     def __init__(
#         self,
#         # TODO: Know 'timeline' associated to know fps (?)
#         root_node: Union[SerialTimelineNode, ParallelTimelineNode]
#     ):
#         self.root_node: Union[SerialTimelineNode, ParallelTimelineNode] = root_node
#         """
#         The root node in which everything starts.
#         """
#         self._last_node: Union[SerialTimelineNode, ParallelTimelineNode] = root_node
#         """
#         *For internal use only*

#         The last node we have and the one that will return
#         the output.
#         """
#         # TODO: This is meant to be cached and not re-resolve
#         # the specifications if it is not needed, but idk why
#         # it is not working
#         # self._last_context_signature: Union[tuple, None] = None
#         # """
#         # *For internal use only*

#         # A tuple including the `fps` and `total_frames` of
#         # the las `evaluation_context` that was used to 
#         # resolve the specifications of the nodes.

#         # This will force to re-resolve the specifications
#         # if it is `None` or some of the parameters in it
#         # are different than this one.
#         # """

#     def _connect_node_to_last(
#         self,
#         node: Union[SerialTimelineNode, ParallelTimelineNode]
#     ) -> 'TimelineGraph':
#         """
#         *For internal use only*

#         Connect the `node` provided to the last node of this
#         timeline, and set this new node as the last one.
#         """
#         self._last_node.connect_to(node)
#         self._last_node = node

#         return self

#     def add_node(
#         self,
#         node: Union[SerialTimelineNode, ParallelTimelineNode]
#     ) -> 'TimelineGraph':
#         """
#         Add the `node` provided to the nodes list, connecting
#         it (in serie mode) to the last one.
#         """
#         # TODO: Validate node (?)
#         return self._connect_node_to_last(node)

#     def add_parallel_node(
#         self,
#         nodes: list[SerialTimelineNode]
#     ):
#         """
#         Create a `ParallelTimelineNode` with the `nodes` provided
#         as parameter and connect it (in serie mode) to the last
#         one.
#         """
#         parallel_node = ParallelTimelineNode(
#             name = 'invented',
#             # TODO: Validate nodes (?)
#             nodes = nodes
#         )
        
#         return self._connect_node_to_last(parallel_node)
    
#     def _resolve_specifications(
#         self,
#         evaluation_context: EvaluationContext
#     ) -> 'TimelineGraph':
#         """
#         *For internal use only*

#         Resolve all the specifications of the nodes that are
#         attached to this `TimelineGraph` instance, to keep
#         the specifications resolved in each of those nodes to
#         be able to evaluate and render when active.

#         This method must be called each time we modify
#         something that affects to the `duration` or other
#         field that makes the specifications resolved be
#         different, by checking the internal variable
#         `self._last_evaluation_context_used_to_resolve`.
#         """
#         node = self.root_node

#         while node is not None:
#             node._resolve_specification(evaluation_context)
#             node = node.output_node

#         return self

#     # TODO: Maybe rename to 'process' (?)
#     @force_framebuffer('inputs')
#     def render(
#         self,
#         inputs: dict[str, Union['FrameBuffer', None]],
#         evaluation_context: EvaluationContext,
#         output_size: tuple[int, int],
#         do_use_gpu: bool = True
#     ) -> 'FrameBuffer':
#         """
#         Render the provided `inputs` with the given
#         `evaluation_context` to generate an output according
#         to the changes that the nodes that were active made.

#         The different nodes of this instance will be applied
#         in order and only if they are enabled and active for
#         that `evaluation_context`.
#         """
#         # TODO: I don't know exactly why this is not working
#         # current_signature = (
#         #     evaluation_context.fps,
#         #     evaluation_context.total_frames
#         # )

#         # do_resolve_specifications = False
#         # if self._last_context_signature != current_signature:
#         #     do_resolve_specifications = True
#         #     self._last_context_signature = current_signature

#         do_resolve_specifications = True

#         # TODO: Maybe a copy of inputs to not affect by
#         # reference (?)
#         # We obtain the first available input
#         output = next((input for input in inputs.values() if input is not None), None)
#         node = self.root_node

#         if node is None:
#             return output

#         while node is not None:
#             # TODO: Check if we need to do this always or not
#             if do_resolve_specifications:
#                 node._resolve_specification(evaluation_context)

#             if node.is_active_at(
#                 evaluation_context = evaluation_context
#             ):
#                 output = node.process(
#                     inputs = inputs,
#                     evaluation_context = evaluation_context,
#                     output_size = output_size,
#                     do_use_gpu = do_use_gpu
#                 )

#             node = node.output_node

#         # We return it as a 'FrameBuffer'
#         return FrameBuffer.from_frame(output)
    
# __all__ = [
#     'TimelineGraph',
#     'SerialTimelineNode',
#     'ParallelTimelineNode'
# ]
    

# """
# Note for the developer:

# A guide to the different types of nodes we will have
# when imitating DaVinci Resolve.

# TimelineNode (abstract)
# ├── ProcessorNode (process one single flow)
# │   ├── EffectNode (filtros, LUTs, shaders…)
# │   └── TransitionNode (mix 2 inputs)
# │
# ├── CompositeNode (combine multiple flows)
# │   ├── SerialTimelineNode (one after another)
# │   ├── ParallelTimelineNode (different branches simultaneously)
# │   └── LayerNode (layers with blending or masks)
# │
# └── GroupNode (has a subnetwork of nodes)

# """
