<commit_before>﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using Sdl.Web.Common;
using Sdl.Web.Common.Configuration;
using Sdl.Web.Common.Extensions;
using Sdl.Web.Common.Interfaces;
using Sdl.Web.Common.Logging;
using Sdl.Web.Common.Mapping;
using Sdl.Web.Common.Models;
using Sdl.Web.Common.Models.Navigation;
using Sdl.Web.DataModel;
using Sdl.Web.Tridion.ContentManager;
using MvcData = Sdl.Web.Common.Models.MvcData;

namespace Sdl.Web.Tridion.Mapping
{
    /// <summary>
    /// Default Page and Entity Model Builder implementation (based on DXA R2 Data Model).
    /// </summary>
    public class DefaultModelBuilder : IPageModelBuilder, IEntityModelBuilder, IDataModelExtension
    {
        protected class Validation
        {
            public SemanticSchema MainSchema { get; set; }
            public List<SemanticSchema> InheritedSchemas { set; get;  }
        }

        protected class MappingData
        {
            public string ModelId { get; set; }
            public Type ModelType { get; set; }
            public SemanticSchemaField EmbeddedSemanticSchemaField { get; set; }
            public Validation PropertyValidation { get; set; }
            public int EmbedLevel { get; set; }
            public ViewModelData SourceViewModel { get; set; }
            public ContentModelData Fields { get; set; }
            public ContentModelData MetadataFields { get; set; }
            public string ContextXPath { get; set; }
            public ILocalization Localization { get; set; }
        }

        public virtual Type ResolveDataModelType(string assemblyName, string typeName)
        {
            // perform type remapping as the type names returned from model-service do not match so we
            // remap here to correctly deserialize.
            switch (typeName)
            {
                case "TaxonomyNodeModelData":
                    return typeof(TaxonomyNode);
                case "SitemapItemModelData":
                    return typeof(SitemapItem);
            }
            // check for any extensions
            return Type.GetType($"Sdl.Web.DataModel.Extension.{typeName}");
        }

        /// <summary>
        /// Builds a strongly typed Page Model from a given DXA R2 Data Model.
        /// </summary>
        /// <param name="pageModel">The strongly typed Page Model to build. Is <c>null</c> for the first Page Model Builder in the pipeline.</param>
        /// <param name="pageModelData">The DXA R2 Data Model.</param>
        /// <param name="includePageRegions">Indicates whether Include Page Regions should be included.</param>
        /// <param name="localization">The context <see cref="ILocalization"/>.</param>
        public virtual void BuildPageModel(ref PageModel pageModel, PageModelData pageModelData, bool includePageRegions, ILocalization localization)
        {
            using (new Tracer(pageModel, pageModelData, includePageRegions, localization))
            {
                MvcData mvcData = CreateMvcData(pageModelData.MvcData, "Page");
                Type modelType = ModelTypeRegistry.GetViewModelType(mvcData);

                if (modelType == typeof(PageModel))
                {
                    // Standard Page Model.
                    pageModel = new PageModel(pageModelData.Id)
                    {
                        ExtensionData = pageModelData.ExtensionData,
                        HtmlClasses = pageModelData.HtmlClasses,
                        XpmMetadata = localization.IsXpmEnabled ? pageModelData.XpmMetadata : null,
                    };
                }
                else if (pageModelData.SchemaId == null)
                {
                    // Custom Page Model, but no custom metadata.
                    pageModel = (PageModel) modelType.CreateInstance(pageModelData.Id);
                    pageModel.ExtensionData = pageModelData.ExtensionData;
                    pageModel.HtmlClasses = pageModelData.HtmlClasses;
                    pageModel.XpmMetadata = localization.IsXpmEnabled ? pageModelData.XpmMetadata : null;
                }
                else
                {
                    // Custom Page Model with custom metadata; do full-blown model mapping.
                    MappingData mappingData = new MappingData
                    {
                        SourceViewModel = pageModelData,
                        ModelId = pageModelData.Id,
                        ModelType = modelType,
                        PropertyValidation = new Validation
                        {
                            MainSchema = SemanticMapping.GetSchema(pageModelData.SchemaId, localization),
                            InheritedSchemas = GetInheritedSemanticSchemas(pageModelData, localization)
                        },
                        MetadataFields = pageModelData.Metadata,
                        Localization = localization
                    };
                    pageModel = (PageModel) CreateViewModel(mappingData);
                }

                pageModel.MvcData = mvcData;
                pageModel.Meta = pageModelData.Meta ?? new Dictionary<string, string>();
                pageModel.Title = PostProcessPageTitle(pageModelData, localization); // TODO TSI-2210: This should eventually be done in Model Service.
                pageModel.Url = pageModelData.UrlPath;
                if (pageModelData.Regions != null)
                {
                    IEnumerable<RegionModelData> regions = includePageRegions ? pageModelData.Regions : pageModelData.Regions.Where(r => r.IncludePageId == null);
                    pageModel.Regions.UnionWith(regions.Select(data => CreateRegionModel(data, localization)));
                    pageModel.IsVolatile |= pageModel.Regions.Any(region => region.IsVolatile);
                }
            }      
        }

        protected virtual List<SemanticSchema> GetInheritedSemanticSchemas(ViewModelData pageModelData, ILocalization localization)
        {
            List<SemanticSchema> schemas = new List<SemanticSchema>();
            if (pageModelData.ExtensionData != null && pageModelData.ExtensionData.ContainsKey("Schemas"))
            {
                string[] ids = pageModelData.ExtensionData["Schemas"] as string[];
                if (ids != null && ids.Length > 0)
                {
                    foreach (string inheritedSchemaId in ids)
                    {
                        SemanticSchema schema = SemanticMapping.GetSchema(inheritedSchemaId, localization);<commit_msg>CRQ-12170 Ensure that full-blown model mapping is also used if Page has no metadata Schema, but there is inherited metadata.<commit_after>﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using Sdl.Web.Common;
using Sdl.Web.Common.Configuration;
using Sdl.Web.Common.Extensions;
using Sdl.Web.Common.Interfaces;
using Sdl.Web.Common.Logging;
using Sdl.Web.Common.Mapping;
using Sdl.Web.Common.Models;
using Sdl.Web.Common.Models.Navigation;
using Sdl.Web.DataModel;
using Sdl.Web.Tridion.ContentManager;
using MvcData = Sdl.Web.Common.Models.MvcData;

namespace Sdl.Web.Tridion.Mapping
{
    /// <summary>
    /// Default Page and Entity Model Builder implementation (based on DXA R2 Data Model).
    /// </summary>
    public class DefaultModelBuilder : IPageModelBuilder, IEntityModelBuilder, IDataModelExtension
    {
        protected class Validation
        {
            public SemanticSchema MainSchema { get; set; }
            public List<SemanticSchema> InheritedSchemas { set; get;  }
        }

        protected class MappingData
        {
            public string ModelId { get; set; }
            public Type ModelType { get; set; }
            public SemanticSchemaField EmbeddedSemanticSchemaField { get; set; }
            public Validation PropertyValidation { get; set; }
            public int EmbedLevel { get; set; }
            public ViewModelData SourceViewModel { get; set; }
            public ContentModelData Fields { get; set; }
            public ContentModelData MetadataFields { get; set; }
            public string ContextXPath { get; set; }
            public ILocalization Localization { get; set; }
        }

        public virtual Type ResolveDataModelType(string assemblyName, string typeName)
        {
            // perform type remapping as the type names returned from model-service do not match so we
            // remap here to correctly deserialize.
            switch (typeName)
            {
                case "TaxonomyNodeModelData":
                    return typeof(TaxonomyNode);
                case "SitemapItemModelData":
                    return typeof(SitemapItem);
            }
            // check for any extensions
            return Type.GetType($"Sdl.Web.DataModel.Extension.{typeName}");
        }

        /// <summary>
        /// Builds a strongly typed Page Model from a given DXA R2 Data Model.
        /// </summary>
        /// <param name="pageModel">The strongly typed Page Model to build. Is <c>null</c> for the first Page Model Builder in the pipeline.</param>
        /// <param name="pageModelData">The DXA R2 Data Model.</param>
        /// <param name="includePageRegions">Indicates whether Include Page Regions should be included.</param>
        /// <param name="localization">The context <see cref="ILocalization"/>.</param>
        public virtual void BuildPageModel(ref PageModel pageModel, PageModelData pageModelData, bool includePageRegions, ILocalization localization)
        {
            using (new Tracer(pageModel, pageModelData, includePageRegions, localization))
            {
                MvcData mvcData = CreateMvcData(pageModelData.MvcData, "Page");
                Type modelType = ModelTypeRegistry.GetViewModelType(mvcData);

                if (modelType == typeof(PageModel))
                {
                    // Standard Page Model.
                    pageModel = new PageModel(pageModelData.Id)
                    {
                        ExtensionData = pageModelData.ExtensionData,
                        HtmlClasses = pageModelData.HtmlClasses,
                        XpmMetadata = localization.IsXpmEnabled ? pageModelData.XpmMetadata : null,
                    };
                }
                else 
                {
                    // Custom Page Model
                    List<SemanticSchema> inheritedSemanticSchemas = GetInheritedSemanticSchemas(pageModelData, localization);
                    SemanticSchema mainSemanticSchema = (pageModelData.SchemaId != null) ? SemanticMapping.GetSchema(pageModelData.SchemaId, localization) : inheritedSemanticSchemas.FirstOrDefault();

                    if (mainSemanticSchema == null)
                    {
                        // Custom Page Model, but no custom metadata.
                        pageModel = (PageModel)modelType.CreateInstance(pageModelData.Id);
                        pageModel.ExtensionData = pageModelData.ExtensionData;
                        pageModel.HtmlClasses = pageModelData.HtmlClasses;
                        pageModel.XpmMetadata = localization.IsXpmEnabled ? pageModelData.XpmMetadata : null;
                    }
                    else
                    {
                        // Custom Page Model with custom metadata; do full-blown model mapping.
                        MappingData mappingData = new MappingData
                        {
                            SourceViewModel = pageModelData,
                            ModelId = pageModelData.Id,
                            ModelType = modelType,
                            PropertyValidation = new Validation
                            {
                                MainSchema = mainSemanticSchema,
                                InheritedSchemas = inheritedSemanticSchemas
                            },
                            MetadataFields = pageModelData.Metadata,
                            Localization = localization
                        };
                        pageModel = (PageModel)CreateViewModel(mappingData);
                    }
                }

                pageModel.MvcData = mvcData;
                pageModel.Meta = pageModelData.Meta ?? new Dictionary<string, string>();
                pageModel.Title = PostProcessPageTitle(pageModelData, localization); // TODO TSI-2210: This should eventually be done in Model Service.
                pageModel.Url = pageModelData.UrlPath;
                if (pageModelData.Regions != null)
                {
                    IEnumerable<RegionModelData> regions = includePageRegions ? pageModelData.Regions : pageModelData.Regions.Where(r => r.IncludePageId == null);
                    pageModel.Regions.UnionWith(regions.Select(data => CreateRegionModel(data, localization)));
                    pageModel.IsVolatile |= pageModel.Regions.Any(region => region.IsVolatile);
                }
            }      
        }

        protected virtual List<SemanticSchema> GetInheritedSemanticSchemas(ViewModelData pageModelData, ILocalization localization)
        {
            List<SemanticSchema> schemas = new List<SemanticSchema>();
            if (pageModelData.ExtensionData != null && pageModelData.ExtensionData.ContainsKey("Schemas"))
            {
                string[] ids = pageModelData.ExtensionData["Schemas"] as string[];
                if (ids != null && ids.Length > 0)
                {
                    foreach (string inheritedSchemaId in ids)
                    {
                        SemanticSchema schema = SemanticMapping.GetSchema(inheritedSchemaId, localization);
{'fraction_non_alphanumeric': 0.07773519415720587, 'fraction_numerical': 0.001518547978884952, 'mean_word_length': 1.8363412633305989, 'pattern_counts': {'":': 4, '<': 46, '<?xml version=': 0, '>': 54, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3824481', 'n_tokens_mistral': 3381, 'n_tokens_neox': 3199, 'n_words': 816}