private async Task LoadLayout(ProcessorModel model, WorkItemTypeModel wit, AzureDevOpsEndpoint endpoint, string processId)
 {
     wit.Layout = (await endpoint.GetApiDefinitionAsync <WorkItemLayout>(new object[] { processId, wit.WorkItemType.ReferenceName }, queryForDetails: false));
     foreach (var page in wit.Layout.Pages)
     {
         var pageKey = $"{wit.WorkItemType.ReferenceName}::{page.Label}";
         if (model.WorkItemPages.ContainsKey(pageKey))
         {
             model.WorkItemPages[pageKey] = page;
         }
         else
         {
             model.WorkItemPages.Add(pageKey, page);
         }
         foreach (var section in page.Sections)
         {
             foreach (var group in section.Groups)
             {
                 var groupKey = $"{wit.WorkItemType.ReferenceName}::{page.Label}::{section.Id}::{group.Label}";
                 if (model.WorkItemGroups.ContainsKey(groupKey))
                 {
                     model.WorkItemGroups[groupKey] = group;
                 }
                 else
                 {
                     model.WorkItemGroups.Add(groupKey, group);
                 }
                 foreach (var control in group.Controls)
                 {
                     var controlKey = $"{wit.WorkItemType.ReferenceName}::{page.Label}::{section.Id}::{group.Label}::{control.Id}";
                     if (model.WorkItemControls.ContainsKey(controlKey))
                     {
                         model.WorkItemControls[controlKey] = control;
                     }
                     else
                     {
                         model.WorkItemControls.Add(controlKey, control);
                     }
                 }
             }
         }
     }
 }
        private async Task SyncWorkItemType(WorkItemTypeModel sourceWit, string processId)
        {
            var targetWit = TargetModel.WorkItemTypes.ContainsKey(sourceWit.WorkItemType.Id) ? TargetModel.WorkItemTypes[sourceWit.WorkItemType.Id] : new();

            targetWit.WorkItemType = await Target.SyncDefinition(sourceWit.WorkItemType, targetWit.WorkItemType, processId);

            foreach (var state in sourceWit.States.Where(x => x.CustomizationType == "custom"))
            {
                if (state.StateCategory == "Completed")
                {
                    Log.LogWarning("Cannot modify [Completed] category on work item state [{0}] on wit type [{1}].", state.Name, sourceWit.WorkItemType.ReferenceName);
                }
                else
                {
                    await SyncDefinitionType <WorkItemState>(
                        TargetModel.WorkItemStates, state,
                        TargetModel.WorkItemStates.Values.FirstOrDefault(x => x.Name == state.Name),
                        processId, targetWit.WorkItemType.ReferenceName);
                }
            }
            foreach (var field in sourceWit.Fields)
            {
                var existingField = TargetModel.WorkItemFields.Values.FirstOrDefault(x => x.ReferenceName == field.ReferenceName);
                //if (existingField == null || (existingField != null && field.Customization != "system")) // I don't think you can modify
                //{
                await SyncDefinitionType <WorkItemTypeField>(
                    TargetModel.WorkItemFields,
                    field,
                    existingField,
                    processId, targetWit.WorkItemType.ReferenceName);

                //}
            }
            foreach (var rule in sourceWit.Rules)
            {
                await SyncDefinitionType <WorkItemRule>(
                    TargetModel.WorkItemRules,
                    rule,
                    TargetModel.WorkItemRules.Values.FirstOrDefault(x => x.Name == rule.Name),
                    processId, targetWit.WorkItemType.ReferenceName);
            }
            foreach (var behavior in sourceWit.Behaviors)
            {
                await SyncDefinitionType <WorkItemTypeBehavior>(
                    TargetModel.WorkItemTypeBehaviors,
                    behavior,
                    targetWit.Behaviors.FirstOrDefault(x => x.Id == behavior.Id),
                    processId, targetWit.WorkItemType.ReferenceName);
            }

            #region Sync Pages ...
            // Making sure the pages themselves are in sync is not too hard.. let's do them first
            foreach (var page in sourceWit.Layout.Pages)
            {
                var targetPage = targetWit.Layout.Pages
                                 .FirstOrDefault(x => x.Label.Equals(page.Label, StringComparison.OrdinalIgnoreCase));

                await SyncDefinitionType <WorkItemPage>(
                    TargetModel.WorkItemPages,
                    page, targetPage,
                    processId, targetWit.WorkItemType.ReferenceName);
            }
            #endregion

            #region Sync Sections and Groups ...
            // Now that we know all the target pages are present we can iterate over what resides on pages..
            // A page's constituent parts is a little more difficult because you need to support an existing
            // group or control that has moved pages, sections or groups

            foreach (var sourcePage in sourceWit.Layout.Pages)
            {
                var targetPage = targetWit.Layout.Pages
                                 .FirstOrDefault(x => x.Label.Equals(sourcePage.Label, StringComparison.OrdinalIgnoreCase));

                foreach (var sourceSection in sourcePage.Sections)
                {
                    foreach (var sourceGroup in sourceSection.Groups)
                    {
                        var sourceGroupKey = $"{sourceWit.WorkItemType.ReferenceName}::{sourcePage.Label}::{sourceSection.Id}::{sourceGroup.Label}";
                        // first let's see if the target has any inherited group for this source group ..
                        // It will have a group.inherits != null/"" if it is inherited.. you can edit "system" groups
                        if (!sourceGroup.Inherited) // It's a custom group
                        {
                            // look for the group on the flattened set of groups.. remember flat groups are keyed on $"{wit.ReferenceName}::{page.Label}::{section.Id}::{group.Label}"
                            var existingGroup = TargetModel.WorkItemGroups.Select(x => new { x.Key, x.Value })
                                                .FirstOrDefault(x =>
                                                                x.Key.StartsWith($"{targetWit.WorkItemType.ReferenceName}::") &&
                                                                x.Value.Label.Equals(sourceGroup.Label, StringComparison.OrdinalIgnoreCase));

                            if (existingGroup != null)
                            {
                                WorkItemGroup finalTargetGroup = null;
                                WorkItemPage  finalTargetPage  = null;
                                // here we know the group exists.. we need to check if its on the same page
                                if (sourceGroupKey.Equals(existingGroup.Key, StringComparison.OrdinalIgnoreCase))
                                {
                                    // It's on the same page/section.. no need to move
                                    Log.LogInformation("Target group [{0}:{1}] located on same page/section. Skipping group location sync..", targetPage.Label, existingGroup.Value.Label);
                                    finalTargetPage  = targetPage;
                                    finalTargetGroup = existingGroup.Value;
                                }
                                else
                                {
                                    // It's on a different page or section.. we need to move it .. but how can we tell
                                    // if it moved to a different page or just a different section? We split and compare
                                    var sourceSplit   = sourceGroupKey.Split("::".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                                    var existingSplit = existingGroup.Key.Split("::".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                                    var existingPage  = targetWit.Layout.Pages.FirstOrDefault(p => p.Label.Equals(existingSplit[1]));

                                    if (sourceSplit[1].Equals(existingSplit[1], StringComparison.OrdinalIgnoreCase))
                                    {
                                        // Its on the same page.. it must be section change.. lets move it to the new section
                                        var tempTargetGroup = existingGroup.Value.CloneAsNew();
                                        tempTargetGroup.Id = existingGroup.Value.Id;
                                        if (await Target.MoveWorkItemGroupWithinPage(
                                                tempTargetGroup, processId, sourceWit.WorkItemType.ReferenceName,
                                                targetPage.Id, sourceSplit[2], existingSplit[2]))
                                        {
                                            Log.LogInformation("Target group [{0}] located on same page but different section. Moved from [{1}] to [{2}] ..", sourceGroup.Label, sourceSplit[2], existingSplit[2]);
                                        }
                                        else
                                        {
                                            Log.LogError("Target group [{0}] located on same page but different section. Unable to move from [{1}] to [{2}] ..", sourceGroup.Label, sourceSplit[2], existingSplit[2]);
                                        }
                                        finalTargetPage  = existingPage;
                                        finalTargetGroup = tempTargetGroup;
                                    }
                                    else
                                    {
                                        // Its on a different page .. lets move pages
                                        var tempTargetGroup = existingGroup.Value.CloneAsNew();
                                        tempTargetGroup.Id = existingGroup.Value.Id;
                                        if (await Target.MoveWorkItemGroupToNewPage(
                                                tempTargetGroup, processId, targetWit.WorkItemType.ReferenceName,
                                                targetPage.Id, sourceSplit[2], existingPage.Id, existingSplit[2]))
                                        {
                                            Log.LogInformation("Target group located on different page. Moved from [{0}:{1}] to [{2}:{3}] ..", sourceSplit[1], sourceSplit[2], existingSplit[1], existingSplit[2]);
                                        }
                                        else
                                        {
                                            Log.LogError("Target group located on different page. Unable to move from [{0}:{1}] to [{2}:{3}]!", existingSplit[1], existingSplit[2], targetPage.Label, sourceSplit[2]);
                                        }
                                        finalTargetPage  = existingPage;
                                        finalTargetGroup = tempTargetGroup;
                                    }
                                }

                                // TODO Finish this!
                                // Iterate through the source controls and make sure the target has all the controls from source
                                foreach (var sourceControl in sourceGroup.Controls)
                                {
                                    if (sourceControl.ControlType == "HtmlFieldControl")
                                    {
                                        Log.LogWarning("Skipped HTML control sync [{0}] as it should have already been migrated as part of the group sync.", sourceControl.Label);
                                    }
                                    else
                                    {
                                        // Let's see if we can't find the control already present in the "final target"
                                        var targetControl = finalTargetGroup.Controls.FirstOrDefault(ctl => ctl.Id.Equals(sourceControl.Id, StringComparison.OrdinalIgnoreCase));

                                        if (targetControl == null)
                                        {
                                            // Let's see if its in another group perhaps.. if so we might want to move it.. that would imply that the group it was in is no longer
                                            WorkItemGroup oldGroup = null;
                                            foreach (var tempSection in finalTargetPage.Sections)
                                            {
                                                oldGroup = tempSection.Groups.FirstOrDefault(g => g.Controls.Any(c => c.Id.Equals(sourceControl.Id, StringComparison.OrdinalIgnoreCase)));
                                                if (oldGroup != null)
                                                {
                                                    break;
                                                }
                                            }

                                            if (oldGroup == null) // It must be a new control
                                            {
                                                if (await Target.AddWorkItemControlToGroup(sourceControl.CloneAsNew(), processId, sourceWit.WorkItemType.ReferenceName, finalTargetGroup.Id, sourceControl.Id))
                                                {
                                                    Log.LogInformation("Attached control [{0}] to group [{1}].", sourceControl.Label, finalTargetGroup.Label);
                                                }
                                                else
                                                {
                                                    Log.LogError("Failed to attach control [{0}] to new group [{1}]!", sourceControl.Label, finalTargetGroup.Label);
                                                }
                                            }
                                            else
                                            {
                                                // It must be control movement between groups
                                                if (await Target.MoveWorkItemControlToOtherGroup(sourceControl.CloneAsNew(), processId, sourceWit.WorkItemType.ReferenceName, finalTargetGroup.Id, sourceControl.Id, oldGroup.Id))
                                                {
                                                    Log.LogInformation("Moved control [{0}] from [{1}] to existing group [{2}].", sourceControl.Id, oldGroup.Label, finalTargetGroup.Label);
                                                }
                                                else
                                                {
                                                    Log.LogError("Failed to move control [{0}] from [{1}] to existing group [{2}].", sourceControl.Id, oldGroup.Label, finalTargetGroup.Label);
                                                }
                                            }
                                        }
                                        else
                                        {
                                            {
                                                Log.LogInformation("Target already contains control [{0}] in proper group [{1}].", sourceControl.Label, finalTargetGroup.Label);
                                            }
                                        }
                                    }
                                }
                            }
                            else
                            {
                                // Target doesn't have the group at all
                                WorkItemGroup newGroup = await SyncDefinitionType <WorkItemGroup>(TargetModel.WorkItemGroups, sourceGroup,
                                                                                                  null, processId, sourceWit.WorkItemType.ReferenceName, targetPage.Id, sourceSection.Id);

                                // Add all the controls
                                foreach (var sourceControl in sourceGroup.Controls.Where(c => !c.ControlType.Equals("HtmlFieldControl", StringComparison.OrdinalIgnoreCase)))
                                {
                                    if (await Target.AddWorkItemControlToGroup(sourceControl.CloneAsNew(), processId, sourceWit.WorkItemType.ReferenceName, newGroup.Id, sourceControl.Id))
                                    {
                                        Log.LogInformation("Attached control [{0}] to new group [{1}].", sourceControl.Label, newGroup.Label);
                                    }
                                    else
                                    {
                                        Log.LogError("Failed to attach control [{0}] to new group [{1}]!", sourceControl.Label, newGroup.Label);
                                    }
                                }
                            }
                        }
                    }
                }
            }

            #endregion

            #region Sync Controls ...
            // Let's get a fresh layout from the target, now that we know pages and groups are aligned.
            await LoadLayout(TargetModel, targetWit, Target, processId);

            // At this point all Pages, Sections and Groups should be aligned.. lets sync the controls
            foreach (var sourcePage in sourceWit.Layout.Pages)
            {
                foreach (var sourceSection in sourcePage.Sections)
                {
                    foreach (var sourceGroup in sourceSection.Groups)
                    {
                        foreach (var sourceControl in sourceGroup.Controls)
                        {
                        }
                    }
                }
            }
            #endregion
            Log.LogInformation($"Completed sync of work item type [{Source.Options.Name}::{sourceWit.WorkItemType.Name}] in [{Target.Options.Name}::{targetWit.WorkItemType.Name}].");
        }