private static void RepopulateTemplateCustomProperties(FunctionNode func, CombinedTemplateState templateState, ErrorContainer errors, Entropy entropy)
        {
            var funcName   = func.Identifier;
            var customProp = templateState.CustomProperties.FirstOrDefault(prop => prop.Name == funcName);

            if (customProp == default)
            {
                errors.ParseError(func.SourceSpan.GetValueOrDefault(), "Functions are not yet supported without corresponding custom properties in ControlTemplates.json");
                throw new DocumentException();
            }

            var scopeArgs = customProp.PropertyScopeKey.PropertyScopeRulesKey.ToDictionary(scope => scope.Name);
            var argTypes  = func.Args.ToDictionary(arg => arg.Identifier, arg => arg.Kind.TypeName);

            int i = 1;

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

                var propertyName = funcName + "_" + arg.Identifier;
                var defaultRule  = entropy.GetDefaultScript(propertyName, arg.Default.Expression);

                if (!scopeArgs.TryGetValue(propertyName, out var propScopeRule))
                {
                    errors.ParseError(func.SourceSpan.GetValueOrDefault(), "Functions are not yet supported without corresponding custom properties in ControlTemplates.json");
                    throw new DocumentException();
                }
                if (!argTypes.TryGetValue(arg.Identifier, out var propType) || !Enum.TryParse <PropertyDataType>(propType, out var propTypeEnum))
                {
                    errors.ParseError(func.SourceSpan.GetValueOrDefault(), "Function metadata blocks must correspond to a function parameter with a valid type");
                    throw new DocumentException();
                }

                propScopeRule.ScopeVariableInfo.DefaultRule           = defaultRule;
                propScopeRule.ScopeVariableInfo.ParameterIndex        = i;
                propScopeRule.ScopeVariableInfo.ParentPropertyName    = funcName;
                propScopeRule.ScopeVariableInfo.ScopePropertyDataType = (int)propTypeEnum;

                ++i;
            }
        }
        // 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;
                            }

                            var propName = funcName + "_" + arg.Identifier;
                            properties.Add(GetPropertyEntry(state, errors, propName, entropy.GetInvariantScript(propName, arg.Default.Expression)));
                        }

                        RepopulateTemplateCustomProperties(func, templateState, errors, entropy);
                    }
                }
                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))
                    {
                        if (!properties.Any(x => x.Property == hiddenScopeRule.Name))
                        {
                            var script = entropy.GetInvariantScript(hiddenScopeRule.Name, hiddenScopeRule.ScopeVariableInfo.DefaultRule);
                            properties.Add(GetPropertyEntry(state, errors, hiddenScopeRule.Name, script));
                        }
                    }
                }

                // 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,
                    AllowAccessToGlobals = (state.IsComponentDefinition ?? false) ? templateState?.ComponentManifest?.AllowAccessToGlobals : state.AllowAccessToGlobals,
                };

                if (state.IsComponentDefinition ?? false)
                {
                    // Before AllowAccessToGlobals added to ComponentDefinition in msapp, it is present in component manifest as well.
                    // So when reconstructing componentdefinition, we need to identify if it was ever present on component definition or not.
                    // For this, we use state IsAllowAccessToGlobalsPresent.
                    templateState.ComponentDefinitionInfo = new ComponentDefinitionInfoJson(resultControlInfo, template.LastModifiedTimestamp, orderedChildren, entropy.IsLegacyComponentAllowGlobalScopeCase ? null : templateState.ComponentManifest.AllowAccessToGlobals);
                    template = templateState.ToControlInfoTemplate();
                    template.IsComponentDefinition = true;

                    // Set it null so that it can be ignored. We have this information at other place.
                    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.AllowAccessToGlobals = templateState?.ComponentManifest?.AllowAccessToGlobals;
            }
            resultControlInfo.Template = template;
            resultControlInfo.Children = orderedChildren;

            return(resultControlInfo, state?.ParentIndex ?? -1);
        }
        // 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, EditorStateStore stateStore, TemplateStore templateStore, UniqueIdRestorer uniqueIdRestorer)
        {
            var controlName = blockNode.Name.Identifier;

            // 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, stateStore, templateStore, uniqueIdRestorer));
            }

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

            var templateIR   = blockNode.Name.Kind;
            var templateName = templateIR.TypeName;
            var variantName  = templateIR.OptionalVariant;

            ControlInfoJson.Template template;

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

            var uniqueId = uniqueIdRestorer.GetControlId(controlName);

            ControlInfoJson.Item resultControlInfo;
            if (stateStore.TryGetControlState(controlName, out var state))
            {
                var properties = new List <ControlInfoJson.RuleEntry>();
                foreach (var propIR in blockNode.Properties)
                {
                    properties.Add(CombinePropertyIRAndState(propIR, errors, state));
                }

                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)).ToList();
                resultControlInfo = new ControlInfoJson.Item()
                {
                    Parent            = parent,
                    Name              = controlName,
                    ControlUniqueId   = uniqueId.ToString(),
                    PublishOrderIndex = state.PublishOrderIndex,
                    VariantName       = variantName ?? string.Empty,
                    Rules             = properties.ToArray(),
                    StyleName         = state.StyleName,
                    ExtensionData     = state.ExtensionData,
                };

                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>();
                foreach (var propIR in blockNode.Properties)
                {
                    properties.Add(CombinePropertyIRAndState(propIR, errors));
                }
                resultControlInfo.Rules = properties.ToArray();
            }
            resultControlInfo.Template = template;
            resultControlInfo.Children = orderedChildren;

            return(resultControlInfo, state?.ParentIndex ?? -1);
        }