private static void SplitIRAndState(ControlInfoJson.Item control, string topParentName, int index, EditorStateStore stateStore, TemplateStore templateStore, Entropy entropy, out BlockNode controlIR)
        {
            // Bottom up, recursively process children
            var childrenWithZIndex = new List <KeyValuePair <BlockNode, double> >();
            var childIndex         = 0;

            foreach (var child in control.Children)
            {
                SplitIRAndState(child, topParentName, childIndex, stateStore, templateStore, entropy, out var childBlock);
                childrenWithZIndex.Add(new KeyValuePair <BlockNode, double>(childBlock, GetControlZIndex(child)));
                ++childIndex;
            }
            var children       = childrenWithZIndex.OrderBy(kvp => kvp.Value).Select(kvp => kvp.Key);
            var isComponentDef = control.Template.IsComponentDefinition ?? false;

            var customPropsToHide = new HashSet <string>();
            var functions         = new List <FunctionNode>();

            if (control.Template.CustomProperties?.Any() ?? false)
            {
                if (!isComponentDef)
                {
                    // Skip component property params on instances
                    customPropsToHide = new HashSet <string>(control.Template.CustomProperties
                                                             .Where(customProp => customProp.IsFunctionProperty)
                                                             .SelectMany(customProp =>
                                                                         customProp.PropertyScopeKey.PropertyScopeRulesKey
                                                                         .Select(propertyScopeRule => propertyScopeRule.Name)
                                                                         ));
                }
                else
                {
                    // Create FunctionNodes on def
                    foreach (var customProp in control.Template.CustomProperties.Where(prop => prop.IsFunctionProperty))
                    {
                        var name = customProp.Name;
                        customPropsToHide.Add(name);
                        var expression     = control.Rules.First(rule => rule.Property == name).InvariantScript;
                        var expressionNode = new ExpressionNode()
                        {
                            Expression = expression
                        };

                        var resultType = new TypeNode()
                        {
                            TypeName = customProp.PropertyDataTypeKey
                        };

                        var args        = new List <TypedNameNode>();
                        var argMetadata = new List <ArgMetadataBlockNode>();
                        foreach (var arg in customProp.PropertyScopeKey.PropertyScopeRulesKey)
                        {
                            customPropsToHide.Add(arg.Name);
                            args.Add(new TypedNameNode()
                            {
                                Identifier = arg.ScopeVariableInfo.ScopeVariableName,
                                Kind       = new TypeNode()
                                {
                                    TypeName = ((PropertyDataType)arg.ScopeVariableInfo.ScopePropertyDataType).ToString()
                                }
                            });

                            argMetadata.Add(new ArgMetadataBlockNode()
                            {
                                Identifier = arg.ScopeVariableInfo.ScopeVariableName,
                                Default    = new ExpressionNode()
                                {
                                    Expression = arg.ScopeVariableInfo.DefaultRule.Replace("\r\n", "\n").Replace("\r", "\n").TrimStart()
                                },
                            });

                            arg.ScopeVariableInfo.DefaultRule           = null;
                            arg.ScopeVariableInfo.ScopePropertyDataType = null;
                            arg.ScopeVariableInfo.ParameterIndex        = null;
                            arg.ScopeVariableInfo.ParentPropertyName    = null;
                        }

                        argMetadata.Add(new ArgMetadataBlockNode()
                        {
                            Identifier = PAConstants.ThisPropertyIdentifier,
                            Default    = new ExpressionNode()
                            {
                                Expression = expression.Replace("\r\n", "\n").Replace("\r", "\n").TrimStart(),
                            },
                        });

                        functions.Add(new FunctionNode()
                        {
                            Args       = args,
                            Metadata   = argMetadata,
                            Identifier = name
                        });
                    }
                }
            }

            var properties = new List <PropertyNode>();
            var propStates = new List <PropertyState>();

            foreach (var property in control.Rules)
            {
                var(prop, state) = SplitProperty(property);
                propStates.Add(state);

                if (customPropsToHide.Contains(property.Property))
                {
                    continue;
                }

                properties.Add(prop);
            }

            controlIR = new BlockNode()
            {
                Name = new TypedNameNode()
                {
                    Identifier = control.Name,
                    Kind       = new TypeNode()
                    {
                        TypeName        = control.Template.TemplateDisplayName ?? control.Template.Name,
                        OptionalVariant = string.IsNullOrEmpty(control.VariantName) ? null : control.VariantName
                    }
                },
                Children   = children.ToList(),
                Properties = properties,
                Functions  = functions,
            };


            if (templateStore.TryGetTemplate(control.Template.Name, out var templateState))
            {
                if (isComponentDef)
                {
                    templateState.IsComponentTemplate = true;
                    templateState.CustomProperties    = control.Template.CustomProperties;
                }
            }
            else
            {
                templateState = new CombinedTemplateState(control.Template);
                templateState.ComponentDefinitionInfo = null;
                var templateName = templateState.TemplateDisplayName ?? templateState.Name;
                templateStore.AddTemplate(templateName, templateState);
            }

            entropy.ControlUniqueIds.Add(control.Name, int.Parse(control.ControlUniqueId));
            var controlState = new ControlState()
            {
                Name = control.Name,
                PublishOrderIndex     = control.PublishOrderIndex,
                TopParentName         = topParentName,
                Properties            = propStates,
                StyleName             = control.StyleName,
                ExtensionData         = control.ExtensionData,
                ParentIndex           = index,
                IsComponentDefinition = control.Template.IsComponentDefinition,
            };

            stateStore.TryAddControl(controlState);
        }
        internal static void SplitIRAndState(SourceFile file, EditorStateStore stateStore, TemplateStore templateStore, Entropy entropy, out BlockNode topParentIR)
        {
            var topParentJson = file.Value.TopParent;

            SplitIRAndState(topParentJson, topParentJson.Name, 0, stateStore, templateStore, entropy, out topParentIR);
        }
        internal static SourceFile CombineIRAndState(BlockNode blockNode, ErrorContainer errors, EditorStateStore stateStore, TemplateStore templateStore, UniqueIdRestorer uniqueIdRestorer, Entropy entropy)
        {
            var topParentJson = CombineIRAndState(blockNode, errors, string.Empty, false, stateStore, templateStore, uniqueIdRestorer, entropy);

            return(SourceFile.New(new ControlInfoJson()
            {
                TopParent = topParentJson.item
            }));
        }
        // Returns pair of item and index (with respect to parent order)
        private static (ControlInfoJson.Item item, int index) CombineIRAndState(BlockNode blockNode, ErrorContainer errors, string parent, bool isInResponsiveLayout, EditorStateStore stateStore, TemplateStore templateStore, UniqueIdRestorer uniqueIdRestorer, Entropy entropy)
        {
            var controlName  = blockNode.Name.Identifier;
            var templateIR   = blockNode.Name.Kind;
            var templateName = templateIR.TypeName;
            var variantName  = templateIR.OptionalVariant;

            // Bottom up, merge children first
            var children = new List <(ControlInfoJson.Item item, int index)>();

            foreach (var childBlock in blockNode.Children)
            {
                children.Add(CombineIRAndState(childBlock, errors, controlName, DynamicProperties.AddsChildDynamicProperties(templateName, variantName), stateStore, templateStore, uniqueIdRestorer, entropy));
            }

            var orderedChildren = children.OrderBy(childPair => childPair.index).Select(pair => pair.item).ToArray();

            ControlInfoJson.Template template;
            if (!templateStore.TryGetTemplate(templateName, out var templateState))
            {
                template = ControlInfoJson.Template.CreateDefaultTemplate(templateName, null);
            }
            else
            {
                template = templateState.ToControlInfoTemplate();
            }

            RecombineCustomTemplates(entropy, template, controlName);

            var uniqueId = uniqueIdRestorer.GetControlId(controlName);

            ControlInfoJson.Item resultControlInfo;
            if (stateStore.TryGetControlState(controlName, out var state))
            {
                var properties        = new List <ControlInfoJson.RuleEntry>();
                var dynamicProperties = new List <ControlInfoJson.DynamicPropertyJson>();
                foreach (var propIR in blockNode.Properties)
                {
                    // Dynamic properties could be null for the galleryTemplateTemplate
                    if (isInResponsiveLayout && state.DynamicProperties != null && DynamicProperties.IsResponsiveLayoutProperty(propIR.Identifier))
                    {
                        dynamicProperties.Add(CombineDynamicPropertyIRAndState(propIR, state));
                    }
                    else
                    {
                        properties.Add(CombinePropertyIRAndState(propIR, errors, state));
                    }
                }

                if (isInResponsiveLayout && state.DynamicProperties != null)
                {
                    // Add dummy dynamic output props in the state at the end
                    foreach (var dynPropState in state.DynamicProperties.Where(propState => propState.Property == null))
                    {
                        dynamicProperties.Add(new ControlInfoJson.DynamicPropertyJson()
                        {
                            PropertyName = dynPropState.PropertyName
                        });
                    }

                    // Reorder to preserve roundtripping
                    dynamicProperties = dynamicProperties.OrderBy(prop => state.DynamicProperties.Select(propState => propState.PropertyName).ToList().IndexOf(prop.PropertyName)).ToList();
                }

                if (blockNode.Functions.Any())
                {
                    foreach (var func in blockNode.Functions)
                    {
                        var funcName          = func.Identifier;
                        var thisPropertyBlock = func.Metadata.FirstOrDefault(metadata => metadata.Identifier == PAConstants.ThisPropertyIdentifier);
                        if (thisPropertyBlock == default)
                        {
                            errors.ParseError(func.SourceSpan.GetValueOrDefault(), "Function definition missing ThisProperty block");
                            throw new DocumentException();
                        }
                        properties.Add(GetPropertyEntry(state, errors, funcName, thisPropertyBlock.Default.Expression));

                        foreach (var arg in func.Metadata)
                        {
                            if (arg.Identifier == PAConstants.ThisPropertyIdentifier)
                            {
                                continue;
                            }

                            properties.Add(GetPropertyEntry(state, errors, funcName + "_" + arg.Identifier, arg.Default.Expression));
                        }

                        RepopulateTemplateCustomProperties(func, templateState, errors);
                    }
                }
                else if (template.CustomProperties?.Any(prop => prop.IsFunctionProperty) ?? false)
                {
                    // For component uses, recreate the dummy props for function parameters
                    foreach (var hiddenScopeRule in template.CustomProperties.Where(prop => prop.IsFunctionProperty).SelectMany(prop => prop.PropertyScopeKey.PropertyScopeRulesKey))
                    {
                        properties.Add(GetPropertyEntry(state, errors, hiddenScopeRule.Name, hiddenScopeRule.ScopeVariableInfo.DefaultRule));
                    }
                }

                // Preserve ordering from serialized IR
                // Required for roundtrip checks
                properties        = properties.OrderBy(prop => state.Properties?.Select(propState => propState.PropertyName).ToList().IndexOf(prop.Property) ?? -1).ToList();
                resultControlInfo = new ControlInfoJson.Item()
                {
                    Parent               = parent,
                    Name                 = controlName,
                    ControlUniqueId      = uniqueId.ToString(),
                    VariantName          = variantName ?? string.Empty,
                    Rules                = properties.ToArray(),
                    DynamicProperties    = (isInResponsiveLayout && dynamicProperties.Any()) ? dynamicProperties.ToArray() : null,
                    HasDynamicProperties = state.HasDynamicProperties,
                    StyleName            = state.StyleName,
                    ExtensionData        = state.ExtensionData,
                    IsGroupControl       = state.IsGroupControl,
                    GroupedControlsKey   = state.GroupedControlsKey
                };

                if (state.IsComponentDefinition ?? false)
                {
                    templateState.ComponentDefinitionInfo = new ComponentDefinitionInfoJson(resultControlInfo, template.LastModifiedTimestamp, orderedChildren);
                    template = templateState.ToControlInfoTemplate();
                    template.IsComponentDefinition   = true;
                    template.ComponentDefinitionInfo = null;
                }
                else
                {
                    template.IsComponentDefinition = state.IsComponentDefinition;
                }
            }
            else
            {
                state                             = null;
                resultControlInfo                 = ControlInfoJson.Item.CreateDefaultControl();
                resultControlInfo.Name            = controlName;
                resultControlInfo.ControlUniqueId = uniqueId.ToString();
                resultControlInfo.Parent          = parent;
                resultControlInfo.VariantName     = variantName ?? string.Empty;
                resultControlInfo.StyleName       = $"default{templateName.FirstCharToUpper()}Style";
                var properties        = new List <ControlInfoJson.RuleEntry>();
                var dynamicProperties = new List <ControlInfoJson.DynamicPropertyJson>();
                foreach (var propIR in blockNode.Properties)
                {
                    if (isInResponsiveLayout && DynamicProperties.IsResponsiveLayoutProperty(propIR.Identifier))
                    {
                        dynamicProperties.Add(CombineDynamicPropertyIRAndState(propIR));
                    }
                    else
                    {
                        properties.Add(CombinePropertyIRAndState(propIR, errors));
                    }
                }
                resultControlInfo.Rules = properties.ToArray();
                bool hasDynamicProperties = isInResponsiveLayout && dynamicProperties.Any();
                resultControlInfo.DynamicProperties    = hasDynamicProperties ? dynamicProperties.ToArray() : null;
                resultControlInfo.HasDynamicProperties = hasDynamicProperties;
            }
            resultControlInfo.Template = template;
            resultControlInfo.Children = orderedChildren;

            return(resultControlInfo, state?.ParentIndex ?? -1);
        }
        private static void SplitIRAndState(ControlInfoJson.Item control, string topParentName, int index, EditorStateStore stateStore, TemplateStore templateStore, Entropy entropy, out BlockNode controlIR)
        {
            // Bottom up, recursively process children
            var children   = new List <BlockNode>();
            var childIndex = 0;

            foreach (var child in control.Children)
            {
                SplitIRAndState(child, topParentName, childIndex, stateStore, templateStore, entropy, out var childBlock);
                children.Add(childBlock);
                ++childIndex;
            }
            var isComponentDef = control.Template.IsComponentDefinition ?? false;

            var customPropsToHide = new HashSet <string>();
            var functions         = new List <FunctionNode>();

            if (control.Template.CustomProperties?.Any() ?? false)
            {
                if (!isComponentDef)
                {
                    // Skip component property params on instances
                    customPropsToHide = new HashSet <string>(control.Template.CustomProperties
                                                             .Where(customProp => customProp.IsFunctionProperty)
                                                             .SelectMany(customProp =>
                                                                         customProp.PropertyScopeKey.PropertyScopeRulesKey
                                                                         .Select(propertyScopeRule => propertyScopeRule.Name)
                                                                         ));
                }
                else
                {
                    // Create FunctionNodes on def
                    foreach (var customProp in control.Template.CustomProperties.Where(prop => prop.IsFunctionProperty))
                    {
                        var name = customProp.Name;
                        customPropsToHide.Add(name);
                        var rule = control.Rules.FirstOrDefault(rule => rule.Property == name);
                        if (rule == null)
                        {
                            // Control does not have a rule for the custom property.
                            continue;
                        }
                        var expression     = rule.InvariantScript;
                        var expressionNode = new ExpressionNode()
                        {
                            Expression = expression
                        };

                        var resultType = new TypeNode()
                        {
                            TypeName = customProp.PropertyDataTypeKey
                        };

                        var args        = new List <TypedNameNode>();
                        var argMetadata = new List <ArgMetadataBlockNode>();
                        foreach (var arg in customProp.PropertyScopeKey.PropertyScopeRulesKey)
                        {
                            customPropsToHide.Add(arg.Name);
                            args.Add(new TypedNameNode()
                            {
                                Identifier = arg.ScopeVariableInfo.ScopeVariableName,
                                Kind       = new TypeNode()
                                {
                                    TypeName = ((PropertyDataType)arg.ScopeVariableInfo.ScopePropertyDataType).ToString()
                                }
                            });

                            argMetadata.Add(new ArgMetadataBlockNode()
                            {
                                Identifier = arg.ScopeVariableInfo.ScopeVariableName,
                                Default    = new ExpressionNode()
                                {
                                    Expression = arg.ScopeVariableInfo.DefaultRule
                                },
                            });

                            arg.ScopeVariableInfo.DefaultRule           = null;
                            arg.ScopeVariableInfo.ScopePropertyDataType = null;
                            arg.ScopeVariableInfo.ParameterIndex        = null;
                            arg.ScopeVariableInfo.ParentPropertyName    = null;
                        }

                        argMetadata.Add(new ArgMetadataBlockNode()
                        {
                            Identifier = PAConstants.ThisPropertyIdentifier,
                            Default    = new ExpressionNode()
                            {
                                Expression = expression,
                            },
                        });

                        functions.Add(new FunctionNode()
                        {
                            Args       = args,
                            Metadata   = argMetadata,
                            Identifier = name
                        });
                    }
                }
            }

            var properties    = new List <PropertyNode>();
            var propStates    = new List <PropertyState>();
            var dynPropStates = new List <DynamicPropertyState>();

            foreach (var property in control.Rules)
            {
                var(prop, state) = SplitProperty(property);
                propStates.Add(state);

                if (customPropsToHide.Contains(property.Property))
                {
                    continue;
                }

                properties.Add(prop);
            }

            foreach (var property in control.DynamicProperties ?? Enumerable.Empty <ControlInfoJson.DynamicPropertyJson>())
            {
                if (property.Rule == null)
                {
                    dynPropStates.Add(new DynamicPropertyState()
                    {
                        PropertyName = property.PropertyName
                    });
                    continue;
                }

                var(prop, state) = SplitDynamicProperty(property);
                dynPropStates.Add(state);
                properties.Add(prop);
            }

            controlIR = new BlockNode()
            {
                Name = new TypedNameNode()
                {
                    Identifier = control.Name,
                    Kind       = new TypeNode()
                    {
                        TypeName        = control.Template.TemplateDisplayName ?? control.Template.Name,
                        OptionalVariant = string.IsNullOrEmpty(control.VariantName) ? null : control.VariantName
                    }
                },
                Children   = children.ToList(),
                Properties = properties,
                Functions  = functions,
            };


            if (templateStore.TryGetTemplate(control.Template.Name, out var templateState))
            {
                if (isComponentDef)
                {
                    templateState.IsComponentTemplate = true;
                    templateState.CustomProperties    = control.Template.CustomProperties;
                }
            }
            else
            {
                templateState = new CombinedTemplateState(control.Template);
                templateState.ComponentDefinitionInfo = null;
                var templateName = templateState.TemplateDisplayName ?? templateState.Name;
                templateStore.AddTemplate(templateName, templateState);
            }

            SplitCustomTemplates(entropy, control.Template, control.Name);

            entropy.ControlUniqueIds.Add(control.Name, int.Parse(control.ControlUniqueId));
            var controlState = new ControlState()
            {
                Name                  = control.Name,
                TopParentName         = topParentName,
                Properties            = propStates,
                DynamicProperties     = dynPropStates.Any() ? dynPropStates : null,
                HasDynamicProperties  = control.HasDynamicProperties,
                StyleName             = control.StyleName,
                IsGroupControl        = control.IsGroupControl,
                GroupedControlsKey    = control.GroupedControlsKey,
                ExtensionData         = control.ExtensionData,
                ParentIndex           = index,
                IsComponentDefinition = control.Template.IsComponentDefinition,
            };

            stateStore.TryAddControl(controlState);
        }