public static async System.Threading.Tasks.Task ShowDiffViewAsync(string filePath, string originalCode, string optimizedCode)
        {
            string extension = System.IO.Path.GetExtension(filePath).TrimStart('.');

            string tempFolder = System.IO.Path.GetTempPath();
            string tempFilePath1 = System.IO.Path.Combine(tempFolder, $"Original.{extension}");
            string tempFilePath2 = System.IO.Path.Combine(tempFolder, $"Optimized.{extension}");

            System.IO.File.WriteAllText(tempFilePath1, originalCode);
            System.IO.File.WriteAllText(tempFilePath2, optimizedCode);

            DTE dte = await VS.GetServiceAsync<DTE, DTE>();

            await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();

            dte.ExecuteCommand("Tools.DiffFiles", $"\"{tempFilePath1}\" \"{tempFilePath2}\"");
        }


public static T AddItemAndRet<T>(this ResourceFolder folder, T item) where T : BaseResource {
        folder.AddItem(item);
        return item;
    }


public new TaskAwaiter<T> GetAwaiter() => this.Task.GetAwaiter();


public static int Hash<T>(T[] array) {
        if (array == null)
            return 0;

        IEqualityComparer<T> comparer = EqualityComparer<T>.Default;
        int result = 1;
        foreach (T t in array) {
            result = 31 * result + comparer.GetHashCode(t);
        }

        return result;
    }

// Using IEqualityComparer + generic functions is easier than having


public static bool instanceof(this Type left, Type right) {
        return right.IsAssignableFrom(left);
    }


public T? GetEffectiveValue(Type key) {
        return this.GetOrCreateEntryInternal(key).inheritedItem;
    }


public static ObservableItemProcessorSimple<T> MakeSimple<T>(IObservableList<T> list, Action<T>? onItemAdded, Action<T>? onItemRemoved) {
        return new ObservableItemProcessorSimple<T>(list, onItemAdded, onItemRemoved);
    }


public void InvokeAsync(T param) {
        _ = Interlocked.Exchange(ref this.currentValue, new ObjectWrapper(param));
        base.InvokeAsyncCore();
    }

// An object wrapper is required in order to permit InvokeAsync being called with a null value.


public async Task<FileStreamResult> ExportAsync<T>(object input, string fileName, bool isDynamicExcelColumn = true) where T : class
    {

        var path = ImportExportUtil.GetFileDir(ref fileName);

        fileName = CommonUtils.GetSingleId() + fileName;
        var filePath = Path.Combine(path, fileName);
        using (FileStream fs = new(filePath, FileMode.Create))
        {
            await fs.ExportExcel<T>(input, isDynamicExcelColumn).ConfigureAwait(false);
        }
        var result = _fileService.GetFileStreamResult(filePath, fileName);
        return result;
    }


public async Task<QueryData<SessionOutput>> PageAsync(QueryPageOptions option)
    {
        var ret = new QueryData<SessionOutput>()
        {
            IsSorted = option.SortOrder != SortOrder.Unset,
            IsFiltered = option.Filters.Count > 0,
            IsAdvanceSearch = option.AdvanceSearches.Count > 0 || option.CustomerSearches.Count > 0,
            IsSearch = option.Searches.Count > 0
        };
        var dataScope = await SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);

        using var db = GetDB();
        var query = db.GetQuery<SysUser>(option)
            .WhereIF(dataScope != null && dataScope?.Count > 0, u => dataScope.Contains(u.OrgId))//在指定机构列表查询
            .WhereIF(dataScope?.Count == 0, u => u.CreateUserId == UserManager.UserId)
            .WhereIF(!option.SearchText.IsNullOrWhiteSpace(), a => a.Account.Contains(option.SearchText!));

        if (option.IsPage)
        {
            RefAsync<int> totalCount = 0;

            var items = await query.ToPageListAsync(option.PageIndex, option.PageItems, totalCount).ConfigureAwait(false);

            var verificatInfoDicts = _verificatInfoService.GetListByUserIds(items.Select(a => a.Id).ToList()).GroupBy(a => a.UserId).ToDictionary(a => a.Key, a => a.ToList());

            var r = items.Select((it) =>
            {
                var reuslt = it.Adapt<SessionOutput>();
                if (verificatInfoDicts.TryGetValue(it.Id, out var verificatInfos))
                {
                    SessionService.GetTokenInfos(verificatInfos);//获取剩余时间
                    reuslt.VerificatCount = verificatInfos.Count;//令牌数量
                    reuslt.VerificatSignList = verificatInfos;//令牌列表

                    //如果有mqtt客户端ID就是在线
                    reuslt.Online = verificatInfos.Any(it => it.ClientIds.Count > 0);
                }

                return reuslt;
            }).ToList();

            ret.TotalCount = totalCount;
            ret.Items = r;
        }
        else if (option.IsVirtualScroll)
        {
            RefAsync<int> totalCount = 0;

            var items = await query.ToPageListAsync(option.StartIndex, option.PageItems, totalCount).ConfigureAwait(false);
            var verificatInfoDicts = _verificatInfoService.GetListByUserIds(items.Select(a => a.Id).ToList()).GroupBy(a => a.UserId).ToDictionary(a => a.Key, a => a.ToList());

            var r = items.Select((it) =>
            {
                var reuslt = it.Adapt<SessionOutput>();
                if (verificatInfoDicts.TryGetValue(it.Id, out var verificatInfos))
                {
                    SessionService.GetTokenInfos(verificatInfos);//获取剩余时间
                    reuslt.VerificatCount = verificatInfos.Count;//令牌数量
                    reuslt.VerificatSignList = verificatInfos;//令牌列表

                    //如果有mqtt客户端ID就是在线
                    reuslt.Online = verificatInfos.Any(it => it.ClientIds.Count > 0);
                }

                return reuslt;
            }).ToList();
            ret.TotalCount = totalCount;
            ret.Items = r;
        }
        else
        {
            var items = await query.ToListAsync().ConfigureAwait(false);

            var verificatInfoDicts = _verificatInfoService.GetListByUserIds(items.Select(a => a.Id).ToList()).GroupBy(a => a.UserId).ToDictionary(a => a.Key, a => a.ToList());

            var r = items.Select((it) =>
            {
                var reuslt = it.Adapt<SessionOutput>();
                if (verificatInfoDicts.TryGetValue(it.Id, out var verificatInfos))
                {
                    SessionService.GetTokenInfos(verificatInfos);//获取剩余时间
                    reuslt.VerificatCount = verificatInfos.Count;//令牌数量
                    reuslt.VerificatSignList = verificatInfos;//令牌列表

                    //如果有mqtt客户端ID就是在线
                    reuslt.Online = verificatInfos.Any(it => it.ClientIds.Count > 0);
                }

                return reuslt;
            }).ToList();
            ret.TotalCount = items.Count;
            ret.Items = r;
        }
        return ret;
    }


public static IServiceCollection AddConfigurableOptions<TOptions>(this IServiceCollection services)
        where TOptions : class, IConfigurableOptions
    {
        var optionsType = typeof(TOptions);

        // 获取选项配置
        var (optionsSettings, path) = Penetrates.GetOptionsConfiguration(optionsType);

        // 配置选项（含验证信息）
        var configurationRoot = App.Configuration;
        var optionsConfiguration = configurationRoot.GetSection(path);

        // 配置选项监听
        if (typeof(IConfigurableOptionsListener<TOptions>).IsAssignableFrom(optionsType))
        {
            var onListenerMethod = optionsType.GetMethod(nameof(IConfigurableOptionsListener<TOptions>.OnListener));
            if (onListenerMethod != null)
            {
                // 监听全局配置改变，目前该方式存在触发两次的 bug：https://github.com/dotnet/aspnetcore/issues/2542
                ChangeToken.OnChange(() => configurationRoot.GetReloadToken(), ((Action)(() =>
                {
                    var options = optionsConfiguration.Get<TOptions>();
                    if (options != null) onListenerMethod.Invoke(options, new object[] { options, optionsConfiguration });
                })).Debounce());
            }
        }

        var optionsConfigure = services.AddOptions<TOptions>()
              .Bind(optionsConfiguration, options =>
              {
                  options.BindNonPublicProperties = true; // 绑定私有变量
              })
              .ValidateDataAnnotations()
              .ValidateOnStart();

        // 实现 Key 映射
        services.PostConfigureAll<TOptions>(options =>
        {
            // 查找所有贴了 MapSettings 的键值对
            var remapKeys = optionsType.GetProperties(BindingFlags.Instance | BindingFlags.Public)
                                                           .Where(u => u.IsDefined(typeof(MapSettingsAttribute), true));
            if (!remapKeys.Any()) return;

            foreach (var prop in remapKeys)
            {
                var propType = prop.PropertyType;
                var realKey = prop.GetCustomAttribute<MapSettingsAttribute>(true).Path;
                var realValue = configurationRoot.GetValue(propType, $"{path}:{realKey}");
                prop.SetValue(options, realValue);
            }
        });

        // 配置复杂验证后后期配置
        var validateInterface = optionsType.GetInterfaces()
            .FirstOrDefault(u => u.IsGenericType && typeof(IConfigurableOptions).IsAssignableFrom(u.GetGenericTypeDefinition()));
        if (validateInterface != null)
        {
            var genericArguments = validateInterface.GenericTypeArguments;

            // 配置复杂验证
            if (genericArguments.Length > 1)
            {
                services.TryAddEnumerable(ServiceDescriptor.Singleton(typeof(IValidateOptions<TOptions>), genericArguments.Last()));
            }

            // 配置后期配置
            var postConfigureMethod = optionsType.GetMethod(nameof(IConfigurableOptions<TOptions>.PostConfigure));
            if (postConfigureMethod != null)
            {
                if (optionsSettings?.PostConfigureAll != true)
                    optionsConfigure.PostConfigure(options => postConfigureMethod.Invoke(options, new object[] { options, optionsConfiguration }));
                else
                    services.PostConfigureAll<TOptions>(options => postConfigureMethod.Invoke(options, new object[] { options, optionsConfiguration }));
            }
        }

        return services;
    }


public static bool Compare(string text, string hash, bool uppercase = false, bool is16 = false)
    {
        return Compare(Encoding.UTF8.GetBytes(text), hash, uppercase, is16);
    }


public static void Create(Action<IServiceScopeFactory, IServiceScope> handler, IServiceScopeFactory scopeFactory = default)
    {
        CreateAsync(async (fac, scope) =>
        {
            handler(fac, scope);
            await Task.CompletedTask.ConfigureAwait(false);
        }, scopeFactory).GetAwaiter().GetResult();
    }


public static IList<JsonConverter> AddDateTimeTypeConverters(this IList<JsonConverter> converters, string outputFormat = "yyyy-MM-dd HH:mm:ss", bool localized = false)
    {
        converters.Add(new NewtonsoftJsonDateTimeJsonConverter(outputFormat, localized));
        converters.Add(new NewtonsoftNullableJsonDateTimeJsonConverter(outputFormat, localized));

        converters.Add(new NewtonsoftJsonDateTimeOffsetJsonConverter(outputFormat, localized));
        converters.Add(new NewtonsoftJsonNullableDateTimeOffsetJsonConverter(outputFormat, localized));

        return converters;
    }


public static IMvcBuilder AddSpecificationDocuments(this IMvcBuilder mvcBuilder, Action<SwaggerGenOptions> configure = default)
    {
        mvcBuilder.Services.AddSpecificationDocuments(configure);

        return mvcBuilder;
    }


public static IMvcBuilder AddUnifyResult(this IMvcBuilder mvcBuilder)
    {
        mvcBuilder.Services.AddUnifyResult<RESTfulResultProvider>();

        return mvcBuilder;
    }


internal static Assembly? GetAssembly(this object? obj) => obj?.GetType().Assembly;


public void Extract(HttpRequestBuilder httpRequestBuilder, HttpDeclarativeExtractorContext context)
    {
        // 尝试解析单个 Action<HttpMultipartFormDataBuilder> 类型参数
        if (context.Args.SingleOrDefault(u => u is Action<HttpMultipartFormDataBuilder>) is not
            Action<HttpMultipartFormDataBuilder> multipartFormDataBuilderAction)
        {
            return;
        }

        // 处理和 [Multipart] 特性冲突问题
        if (httpRequestBuilder.MultipartFormDataBuilder is not null)
        {
            multipartFormDataBuilderAction.Invoke(httpRequestBuilder.MultipartFormDataBuilder);
        }
        else
        {
            // 设置多部分表单内容
            httpRequestBuilder.SetMultipartContent(multipartFormDataBuilderAction);
        }
    }


public static string ReadFile(string Path, Encoding? encoding = default)
    {
        encoding ??= Encoding.UTF8;
        if (!File.Exists(Path))
        {
            return null;
        }

        StreamReader streamReader = new StreamReader(Path, encoding);
        string result = streamReader.ReadToEnd();
        streamReader.Close();
        streamReader.Dispose();
        return result;
    }


public override void Init(String value)
    {
        // 加上默认后缀
        if (!value.IsNullOrEmpty() && Path.GetExtension(value).IsNullOrEmpty()) value += ".ini";

        base.Init(value);
    }


public String Hash(String password) => password.MD5();

/// <summary>对密码进行散列处理，此处可以加盐，结果保存在数据库</summary>


public void Initialize(GeneratorInitializationContext context)
    {
        //Debugger.Launch();
        context.RegisterForPostInitialization(a =>
        {
            a.AddSource(nameof(m_generatorVariableAttribute), m_generatorVariableAttribute);
        });
        context.RegisterForSyntaxNotifications(() => new VariableSyntaxReceiver());
    }


[HttpGet("channelList")]
    [DisplayName("获取通道信息")]
    public async Task<SqlSugarPagedList<ChannelRuntime>> GetChannelListAsync(ChannelPageInput input)
    {

        var channelRuntimes = await GlobalData.GetCurrentUserChannels().ConfigureAwait(false);

        var data = channelRuntimes
         .Select(a => a.Value)
         .WhereIF(!string.IsNullOrEmpty(input.Name), u => u.Name.Contains(input.Name))
         .WhereIF(!string.IsNullOrEmpty(input.PluginName), u => u.PluginName == input.PluginName)
         .WhereIF(input.PluginType != null, u => u.PluginType == input.PluginType)
         .ToPagedList(input);
        return data;
    }


protected override void OnInitialized()
    {
        base.OnInitialized();
        HeaderText = ManagementLocalizer[nameof(HeaderText)];
    }


public static UpdateModel Deserialize(string json)
    {
        var obj = JsonSerializer.Deserialize(json, UpdateModelJsonContext.Default.UpdateModel);

        return obj;
    }


public static async Task<Result> RequestUserStatsAsync(SteamId steamid)
        {
            var r = await Internal.RequestUserStats(steamid);
            if (!r.HasValue) return Result.Fail;
            return r.Value.Result;
        }


public static bool DiskCacheContains(string url) => File.Exists(DiskCachePath(url));


static void InjectGuardParameters(MethodDefinition md, ILProcessor worker, Instruction top)
        {
            int offset = md.Resolve().IsStatic ? 0 : 1;
            for (int index = 0; index < md.Parameters.Count; index++)
            {
                ParameterDefinition param = md.Parameters[index];
                if (param.IsOut)
                {
                    TypeReference elementType = param.ParameterType.GetElementType();

                    md.Body.Variables.Add(new VariableDefinition(elementType));
                    md.Body.InitLocals = true;

                    worker.InsertBefore(top, worker.Create(OpCodes.Ldarg, index + offset));
                    worker.InsertBefore(top, worker.Create(OpCodes.Ldloca_S, (byte)(md.Body.Variables.Count - 1)));
                    worker.InsertBefore(top, worker.Create(OpCodes.Initobj, elementType));
                    worker.InsertBefore(top, worker.Create(OpCodes.Ldloc, md.Body.Variables.Count - 1));
                    worker.InsertBefore(top, worker.Create(OpCodes.Stobj, elementType));
                }
            }
        }

// this is required to early-out from a function with "ref" or "out" parameters


public override void OnRoomServerSceneChanged(string sceneName)
        {
            // spawn the initial batch of Rewards
            if (sceneName == GameplayScene)
            {
                Spawner.InitialSpawn();
            }
        }


public static new async Task<BiquadFilterNode> CreateAsync(IJSRuntime jSRuntime, IJSObjectReference jSReference)
    {
        return await CreateAsync(jSRuntime, jSReference, new());
    }


public static new async Task<ChannelSplitterNode> CreateAsync(IJSRuntime jSRuntime, IJSObjectReference jSReference)
    {
        return await CreateAsync(jSRuntime, jSReference, new());
    }


public static async Task<AudioParamMap> CreateAsync(IJSRuntime jSRuntime, IJSObjectReference jSReference)
    {
        return await CreateAsync(jSRuntime, jSReference, new());
    }


public static void CreatePasswordHash(string password, out byte[] passwordHash, out byte[] passwordSalt)
    {
        using HMACSHA512 hmac = new();

        passwordSalt = hmac.Key;
        passwordHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(password));
    }


public IEnumerator GetEnumerator() => new TestEnumerator(_list);

//IEnumerator<KeyValuePair<string, string>> IEnumerable<KeyValuePair<string, string>>.GetEnumerator()


public override void DrawProperties()
		{
			base.DrawProperties();
			m_perPixel = EditorGUILayoutToggleLeft( PerPixelLabelStr, m_perPixel );
		}


private static void CreateShaderTemplate(string templatePath, string defaultFileName)
    {
        ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0,
            ScriptableObject.CreateInstance<ShaderAsset>(),
            GetSelectedPathOrFallback() + "/" + defaultFileName,
            null,
            templatePath);
    }

// 通用方法，用于从给定的模板路径创建Shader


public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor)
		{
			return _height;
		}


public static FBO Create()
        {
            FBO fbo = new FBO();
            fbo.m_width  = 0;
            fbo.m_height = 0;
            return fbo;
        }


public static void Think(AIKnowledge knowledge, AIDecision decision)
		{
			var conf = ConfigAIDecisionTreeCategory.Instance.Get(knowledge.DecisionArchetype);
			if (conf != null)
			{
				if (knowledge.CombatComponent != null && knowledge.CombatComponent.IsInCombat)
				{
					if (conf.CombatNode != null)
						Handler(knowledge, decision, conf.CombatNode);
				}
				else
				{
					if (conf.Node != null)
						Handler(knowledge, decision, conf.Node);
				}
			}
		}


public bool IsProcessRunning()
        {
            return this.loadingOp.Count > 0;
        }


[MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteCommonVal<T>(T val) =>
            Serializer.Serialize(typeof(T), val, this, option, false);


public static unsafe string[] Split(this ReadOnlySpan<char> str, char separator)
        {
            if (str.IsEmpty)
            {
                return Array.Empty<string>();
            }

            var indexes = ObjectPool<ExtensibleBuffer<int>>.Request();
            var index = 0;
            int i = 0;
            int max = str.Length;
            fixed (char* ptr = &str.GetPinnableReference())
            {
                var cPtr = ptr;
                while (i < max)
                {
                    if (*cPtr++ == separator)
                    {
                        indexes[index++] = i;
                    }

                    i++;
                }

                string[] ret = new string[index + 1];
                var retSpan = ret.AsSpan();

                int start = 0;

                for (i = 0; i < index; i++)
                {
                    ref int end = ref indexes.Data[i];
                    if(start >= max || start == end)
                    {
                        retSpan[i] = string.Empty;
                    }
                    else
                    {
                        retSpan[i] = new string(ptr, start, end - start);
                    }
                    start = end + 1;
                }

                if (start < max)
                {
                    retSpan[index] = new string(ptr, start, max - start);
                }
                else
                {
                    retSpan[index] = string.Empty;
                }

                ObjectPool<ExtensibleBuffer<int>>.Return(indexes);
                return ret;
            }
        }


private void CopyBuildinFilesToStreaming(BuildParametersContext buildParametersContext, ManifestContext manifestContext)
		{
			ECopyBuildinFileOption option = buildParametersContext.Parameters.CopyBuildinFileOption;
			string packageOutputDirectory = buildParametersContext.GetPackageOutputDirectory();
			string streamingAssetsDirectory = buildParametersContext.GetStreamingAssetsDirectory();
			string buildPackageName = buildParametersContext.Parameters.PackageName;
			string buildPackageVersion = buildParametersContext.Parameters.PackageVersion;

			// 加载补丁清单
			PackageManifest manifest = manifestContext.Manifest;

			// 清空流目录
			if (option == ECopyBuildinFileOption.ClearAndCopyAll || option == ECopyBuildinFileOption.ClearAndCopyByTags)
			{
				EditorTools.ClearFolder(streamingAssetsDirectory);
			}

			bool copy = false;
			// 拷贝文件列表（所有文件）
			if (option == ECopyBuildinFileOption.ClearAndCopyAll || option == ECopyBuildinFileOption.OnlyCopyAll)
			{
				copy = true;
				foreach (var packageBundle in manifest.BundleList)
				{
					string sourcePath = $"{packageOutputDirectory}/{packageBundle.FileName}";
					string destPath = $"{streamingAssetsDirectory}/{packageBundle.FileName}";
					EditorTools.CopyFile(sourcePath, destPath, true);
				}
			}

			// 拷贝文件列表（带标签的文件）
			if (option == ECopyBuildinFileOption.ClearAndCopyByTags || option == ECopyBuildinFileOption.OnlyCopyByTags)
			{
				string[] tags = buildParametersContext.Parameters.CopyBuildinFileTags.Split(';');
				foreach (var packageBundle in manifest.BundleList)
				{
					if (packageBundle.HasTag(tags) == false)
						continue;
					copy = true;
					string sourcePath = $"{packageOutputDirectory}/{packageBundle.FileName}";
					string destPath = $"{streamingAssetsDirectory}/{packageBundle.FileName}";
					EditorTools.CopyFile(sourcePath, destPath, true);
				}
			}

			if (copy)
			{
				// 拷贝补丁清单文件
				{
					string fileName = YooAssetSettingsData.GetManifestBinaryFileName(buildPackageName, buildPackageVersion);
					string sourcePath = $"{packageOutputDirectory}/{fileName}";
					string destPath = $"{streamingAssetsDirectory}/{fileName}";
					EditorTools.CopyFile(sourcePath, destPath, true);
				}

				// 拷贝补丁清单哈希文件
				{
					string fileName = YooAssetSettingsData.GetPackageHashFileName(buildPackageName, buildPackageVersion);
					string sourcePath = $"{packageOutputDirectory}/{fileName}";
					string destPath = $"{streamingAssetsDirectory}/{fileName}";
					EditorTools.CopyFile(sourcePath, destPath, true);
				}

				// 拷贝补丁清单版本文件
				{
					string fileName = YooAssetSettingsData.GetPackageVersionFileName(buildPackageName);
					string sourcePath = $"{packageOutputDirectory}/{fileName}";
					string destPath = $"{streamingAssetsDirectory}/{fileName}";
					EditorTools.CopyFile(sourcePath, destPath, true);
				}
			}

			// 刷新目录
			AssetDatabase.Refresh();
			BuildLogger.Log($"内置文件拷贝完成：{streamingAssetsDirectory}");
		}


public override void Update()
		{
			if (_steps == ESteps.Done)
				return;

			if (_steps == ESteps.None)
			{
				if (MainBundleInfo.LoadMode == BundleInfo.ELoadMode.LoadFromRemote)
				{
					_steps = ESteps.Download;
					FileLoadPath = MainBundleInfo.Bundle.CachedDataFilePath;
				}
				else if (MainBundleInfo.LoadMode == BundleInfo.ELoadMode.LoadFromStreaming)
				{
					_steps = ESteps.Website;
					FileLoadPath = MainBundleInfo.Bundle.CachedDataFilePath;
				}
				else
				{
					throw new System.NotImplementedException(MainBundleInfo.LoadMode.ToString());
				}
			}

			// 1. 下载远端文件
			if (_steps == ESteps.Download)
			{
				int failedTryAgain = Impl.DownloadFailedTryAgain;
				_downloader = DownloadSystem.CreateDownload(MainBundleInfo, failedTryAgain);
				_downloader.SendRequest();
				_steps = ESteps.CheckDownload;
			}

			// 2. 检测下载结果
			if (_steps == ESteps.CheckDownload)
			{
				DownloadProgress = _downloader.DownloadProgress;
				DownloadedBytes = _downloader.DownloadedBytes;
				if (_downloader.IsDone() == false)
					return;

				if (_downloader.HasError())
				{
					_steps = ESteps.Done;
					Status = EStatus.Failed;
					LastError = _downloader.GetLastError();
				}
				else
				{
					_steps = ESteps.CheckFile;
				}
			}

			// 3. 从站点下载
			if (_steps == ESteps.Website)
			{
				int failedTryAgain = Impl.DownloadFailedTryAgain;
				var bundleInfo = ManifestTools.ConvertToUnpackInfo(MainBundleInfo.Bundle);
				_website = DownloadSystem.CreateDownload(bundleInfo, failedTryAgain);
				_website.SendRequest();
				_steps = ESteps.CheckWebsite;
			}

			// 4. 检测站点下载
			if (_steps == ESteps.CheckWebsite)
			{
				DownloadProgress = _website.DownloadProgress;
				DownloadedBytes = _website.DownloadedBytes;
				if (_website.IsDone() == false)
					return;

				if (_website.HasError())
				{
					_steps = ESteps.Done;
					Status = EStatus.Failed;
					LastError = _website.GetLastError();
				}
				else
				{
					_steps = ESteps.CheckFile;
				}
			}

			// 5. 检测结果
			if (_steps == ESteps.CheckFile)
			{
				// 设置下载进度
				DownloadProgress = 1f;
				DownloadedBytes = (ulong)MainBundleInfo.Bundle.FileSize;

				_steps = ESteps.Done;
				if (File.Exists(FileLoadPath))
				{
					Status = EStatus.Succeed;
				}
				else
				{
					Status = EStatus.Failed;
					LastError = $"Raw file not found : {FileLoadPath}";
				}
			}
		}


private void OnHandleEventMessage(IEventMessage message)
	{
		if(message is SceneEventDefine.ChangeToHomeScene)
		{
			_machine.ChangeState<FsmSceneHome>();
		}
		else if(message is SceneEventDefine.ChangeToBattleScene)
		{
			_machine.ChangeState<FsmSceneBattle>();
		}
	}


public static void Init()
	{
		if (_isInit == false)
		{
			_isInit = true;
			var manifest = Resources.Load<BuildinFileManifest>("BuildinFileManifest");
			if (manifest != null)
			{
				foreach (string fileName in manifest.BuildinFiles)
				{
					_cacheData.Add(fileName);
				}
			}
		}
	}


public void PopulateGenericMenu(InspectorProperty property, GenericMenu genericMenu)
        {
            double3 value = (double3)property.ValueEntry.WeakSmartValue;
            var vec = new Vector3((float)value.x, (float)value.y, (float)value.z);

            if (genericMenu.GetItemCount() > 0)
            {
                genericMenu.AddSeparator("");
            }
            genericMenu.AddItem(new GUIContent("Normalize"), Mathf.Approximately(vec.magnitude, 1f), () => NormalizeEntries(property));
            genericMenu.AddItem(new GUIContent("Zero", "Set the vector to (0, 0, 0)"), vec == Vector3.zero, () => SetVector(property, Vector3.zero));
            genericMenu.AddItem(new GUIContent("One", "Set the vector to (1, 1, 1)"), vec == Vector3.one, () => SetVector(property, Vector3.one));
            genericMenu.AddSeparator("");
            genericMenu.AddItem(new GUIContent("Right", "Set the vector to (1, 0, 0)"), vec == Vector3.right, () => SetVector(property, Vector3.right));
            genericMenu.AddItem(new GUIContent("Left", "Set the vector to (-1, 0, 0)"), vec == Vector3.left, () => SetVector(property, Vector3.left));
            genericMenu.AddItem(new GUIContent("Up", "Set the vector to (0, 1, 0)"), vec == Vector3.up, () => SetVector(property, Vector3.up));
            genericMenu.AddItem(new GUIContent("Down", "Set the vector to (0, -1, 0)"), vec == Vector3.down, () => SetVector(property, Vector3.down));
            genericMenu.AddItem(new GUIContent("Forward", "Set the vector property to (0, 0, 1)"), vec == Vector3.forward, () => SetVector(property, Vector3.forward));
            genericMenu.AddItem(new GUIContent("Back", "Set the vector property to (0, 0, -1)"), vec == Vector3.back, () => SetVector(property, Vector3.back));
        }


void DisplayPageNavigation(ref int currentPage, int arraySize, int itemsPerPage)
        {
            Rect pagePos = EditorGUILayout.GetControlRect(false, 20);
            pagePos.width /= 3;

            int shiftMultiplier = Event.current.shift ? 10 : 1; // Page + Shift goes 10 page forward

            // Previous Page
            GUI.enabled = currentPage > 0;

            if (GUI.Button(pagePos, "Previous Page"))
            {
                currentPage -= 1 * shiftMultiplier;
                //m_isNewPage = true;
            }

            // Page Counter
            GUI.enabled = true;
            pagePos.x += pagePos.width;
            int totalPages = (int)(arraySize / (float)itemsPerPage + 0.999f);
            GUI.Label(pagePos, "Page " + (currentPage + 1) + " / " + totalPages, TMP_UIStyleManager.centeredLabel);

            // Next Page
            pagePos.x += pagePos.width;
            GUI.enabled = itemsPerPage * (currentPage + 1) < arraySize;

            if (GUI.Button(pagePos, "Next Page"))
            {
                currentPage += 1 * shiftMultiplier;
                //m_isNewPage = true;
            }

            // Clamp page range
            currentPage = Mathf.Clamp(currentPage, 0, arraySize / itemsPerPage);

            GUI.enabled = true;
        }


public static Material GetStencilMaterial(Material baseMaterial, int stencilID)
        {
            // Check if Material supports masking
            if (!baseMaterial.HasProperty(ShaderUtilities.ID_StencilID))
            {
                Debug.LogWarning("Selected Shader does not support Stencil Masking. Please select the Distance Field or Mobile Distance Field Shader.");
                return baseMaterial;
            }

            int baseMaterialID = baseMaterial.GetInstanceID();

            // If baseMaterial already has a corresponding masking material, return it.
            for (int i = 0; i < m_materialList.Count; i++)
            {
                if (m_materialList[i].baseMaterial.GetInstanceID() == baseMaterialID && m_materialList[i].stencilID == stencilID)
                {
                    m_materialList[i].count += 1;

                    #if TMP_DEBUG_MODE
                    ListMaterials();
                    #endif

                    return m_materialList[i].stencilMaterial;
                }
            }

            // No matching masking material found. Create and return a new one.

            Material stencilMaterial;

            //Create new Masking Material Instance for this Base Material
            stencilMaterial = new Material(baseMaterial);
            stencilMaterial.hideFlags = HideFlags.HideAndDontSave;

            #if UNITY_EDITOR
                stencilMaterial.name += " Masking ID:" + stencilID;
            #endif

            stencilMaterial.shaderKeywords = baseMaterial.shaderKeywords;

            // Set Stencil Properties
            ShaderUtilities.GetShaderPropertyIDs();
            stencilMaterial.SetFloat(ShaderUtilities.ID_StencilID, stencilID);
            //stencilMaterial.SetFloat(ShaderUtilities.ID_StencilOp, 0);
            stencilMaterial.SetFloat(ShaderUtilities.ID_StencilComp, 4);
            //stencilMaterial.SetFloat(ShaderUtilities.ID_StencilReadMask, stencilID);
            //stencilMaterial.SetFloat(ShaderUtilities.ID_StencilWriteMask, 0);

            MaskingMaterial temp = new MaskingMaterial();
            temp.baseMaterial = baseMaterial;
            temp.stencilMaterial = stencilMaterial;
            temp.stencilID = stencilID;
            temp.count = 1;

            m_materialList.Add(temp);

            #if TMP_DEBUG_MODE
            ListMaterials();
            #endif

            return stencilMaterial;
        }


public abstract bool Execute(ActionContext context);


public abstract bool Execute(IEnumerable<TrackAsset> tracks);


public override ClipDrawOptions GetClipOptions(TimelineClip clip)
        {
            var clipOptions = base.GetClipOptions(clip);
            var asset = clip.asset as AnimationPlayableAsset;

            if (asset != null)
                clipOptions.errorText = GetErrorText(asset, clip.GetParentTrack() as AnimationTrack, clipOptions.errorText);

            if (clip.recordable)
                clipOptions.highlightColor = DirectorStyles.Instance.customSkin.colorAnimationRecorded;

            return clipOptions;
        }


protected virtual void OnBeforeTrackSerialize() { }


public static IAsyncDisposable Wrap(object? resource) => TryWrap(resource) ?? NoopDisposable.Instance;


public static T Apply<T>(ref T value, Func<T, T> transformation)
        where T : class
    {
        _ = transformation ?? throw new ArgumentNullException(nameof(transformation));

        while (true)
        {
            var localValue = Interlocked.CompareExchange(ref value, null!, null!);
            var modified = transformation(localValue);
            if (Interlocked.CompareExchange(ref value, modified, localValue) == localValue)
                return modified;
        }
    }


private static string GetStackTrace()
        {
            // 通过反射获取ConsoleWindow类
            var consoleWindowType = typeof(EditorWindow).Assembly.GetType("UnityEditor.ConsoleWindow");
            // 获取窗口实例
            var fieldInfo = consoleWindowType.GetField("ms_ConsoleWindow",
                BindingFlags.Static |
                BindingFlags.NonPublic);
            if (fieldInfo != null)
            {
                var consoleInstance = fieldInfo.GetValue(null);
                if (consoleInstance != null)
                    if (EditorWindow.focusedWindow == (EditorWindow)consoleInstance)
                    {
                        // 获取m_ActiveText成员
                        fieldInfo = consoleWindowType.GetField("m_ActiveText",
                            BindingFlags.Instance |
                            BindingFlags.NonPublic);
                        // 获取m_ActiveText的值
                        if (fieldInfo != null)
                        {
                            var activeText = fieldInfo.GetValue(consoleInstance).ToString();
                            return activeText;
                        }
                    }
            }

            return null;
        }


private static IEncryptionServices CreateEncryptionInstance(string packageName, EBuildPipeline buildPipeline)
        {
            var encryptionClassName = AssetBundleBuilderSetting.GetPackageEncyptionClassName(packageName, buildPipeline);
            var encryptionClassTypes = EditorTools.GetAssignableTypes(typeof(IEncryptionServices));
            var classType = encryptionClassTypes.Find(x => x.FullName != null && x.FullName.Equals(encryptionClassName));
            if (classType != null)
            {
                Debug.Log($"Use Encryption {classType}");
                return (IEncryptionServices)Activator.CreateInstance(classType);
            }
            else
            {
                return null;
            }
        }


public static void Entrance(object[] objects)
    {
        s_HotfixAssembly = (List<Assembly>)objects[0];
        Log.Warning("======= 看到此条日志代表你成功运行了热更新代码 =======");
        Log.Warning("======= Entrance GameApp =======");
        Instance.InitSystem();
        Instance.Start();
        Utility.Unity.AddUpdateListener(Instance.Update);
        Utility.Unity.AddFixedUpdateListener(Instance.FixedUpdate);
        Utility.Unity.AddLateUpdateListener(Instance.LateUpdate);
        Utility.Unity.AddDestroyListener(Instance.OnDestroy);
        Utility.Unity.AddOnDrawGizmosListener(Instance.OnDrawGizmos);
        Utility.Unity.AddOnApplicationPauseListener(Instance.OnApplicationPause);
        Instance.StartGameLogic();
    }


private static TAggregateId Create(TId id) =>
        (
            Activator.CreateInstance(
                typeof(TAggregateId),
                BindingFlags.Instance | BindingFlags.NonPublic,
                null,
                new object?[] { id },
                null,
                null
            ) as TAggregateId
        )!;

// instantiate AggregateId and pass id to its protected or private constructor


public void SetRadius(float topLeft, float topRight, float bottomRight, float bottomLeft)
        {
            mRadii[0] = topLeft;
            mRadii[1] = topLeft;

            mRadii[2] = topRight;
            mRadii[3] = topRight;

            mRadii[4] = bottomRight;
            mRadii[5] = bottomRight;

            mRadii[6] = bottomLeft;
            mRadii[7] = bottomLeft;
            Invalidate();
        }


private void SwitchState(State newState, bool callExit = true)
    {
        if (callExit)
        {
            _curState.Exit();
        }

        _curState = newState;
        _curState.Enter();
    }


public static InfoBarSeverity ToInfoBarSeverity(this GalStatusSyncResult result)
    {
        switch (result)
        {
            case GalStatusSyncResult.Ok:
                return InfoBarSeverity.Success;
            case GalStatusSyncResult.UnAuthorized:
            case GalStatusSyncResult.NoId:
            case GalStatusSyncResult.Other:
                return InfoBarSeverity.Error;
            case GalStatusSyncResult.NotSupported:
                return InfoBarSeverity.Warning;
            default:
                return InfoBarSeverity.Error;
        }
    }


public static void NavigateToHomePage(INavigationService navigationService, IFilterService? filterService = null,
        IEnumerable<FilterBase>? filters = null)
    {
        Debug.Assert(!(filterService is null ^ filters is null)); // 同时为null或同时不为null
        if (filterService is not null && filters is not null)
        {
            filterService.ClearFilters();
            foreach (FilterBase filter in filters)
            {
                filterService.AddFilter(filter);
                if (filter is CategoryFilter c)
                    c.Category.LastClicked = DateTime.Now;
                if (filter is SourceFilter s)
                    s.Source.LastClicked = DateTime.Now;

            }
        }
        navigationService.NavigateTo(typeof(HomeViewModel).FullName!);
    }


public static string Convert(int value)
    {
        var timeAsHour = App.GetService<ILocalSettingsService>().ReadSettingAsync<bool>(KeyValues.TimeAsHour).Result;
        if (timeAsHour)
            return value > 60 ? $"{value / 60}h{value % 60}m" : $"{value}m";
        return $"{value} {"Minute".GetLocalized()}";
    }


public static void SyncCollection<T>(this ObservableCollection<T> collection, IList<T> other, bool sort = false)
        where T : notnull
    {
        // var delta = other.Count - collection.Count;
        // for (var i = 0; i < delta; i++)
        //     collection.Add(other[0]); //内容不总要，只是要填充到对应的总数
        // for (var i = delta; i < 0; i++)
        //     collection.RemoveAt(collection.Count - 1);
        //
        // for (var i = 0; i < other.Count; i++)
        //     collection[i] = other[i];

        HashSet<T> toRemove = new(collection.Where(obj => !other.Contains(obj)));
        HashSet<T> toAdd = new(other.Where(obj => !collection.Contains(obj)));
        foreach (T obj in toRemove)
            collection.Remove(obj);
        foreach (T obj in toAdd)
            collection.Add(obj);

        if (!sort) return;
        Dictionary<T, int> index = new();
        for (var i = 0; i < other.Count; i++)
            index[other[i]] = i;
        collection.Sort((a, b) => index[a].CompareTo(index[b]));
    }


public int Similarity(GalgameUid? rhs)
    {
        if (rhs is null) return 0;
        var result = 0;
        result += !PvnId.IsNullOrEmpty() && PvnId == rhs.PvnId ? 1 : 0;
        result += !BangumiId.IsNullOrEmpty() && BangumiId == rhs.BangumiId ? 1 : 0;
        result += !VndbId.IsNullOrEmpty() && VndbId == rhs.VndbId ? 1 : 0;
        result += !CnName.IsNullOrEmpty() && CnName == rhs.CnName ? 1 : 0;
        result += Name == rhs.Name ? 1 : 0;
        return result;
    }


static void Main(string[] args)
	{
		// if you want to use a different Application Delegate class from "AppDelegate"
		// you can specify it here.
		UIApplication.Main(args, null, typeof(AppDelegate));
	}

// This is the main entry point of the application.


public static void ViReplaceLine(ConsoleKeyInfo? key = null, object arg = null)
        {
            _singleton._groupUndoHelper.StartGroup(ViReplaceLine, arg);
            DeleteLine(key, arg);
            ViInsertMode(key, arg);
        }


private async Task<string> RegenerateAsync()
    {
        ArgumentPlaceholder ap = _agent.ArgPlaceholder;

        // We are doing the replacement locally, but want to fake the regeneration.
        await Task.Delay(2000, Shell.CancellationToken);

        ResponseData data = ap.ResponseData;
        _agent.ReplaceKnownPlaceholders(data);

        if (data.PlaceholderSet is null)
        {
            _agent.ResetArgumentPlaceholder();
        }

        return _agent.GenerateAnswer(data, Shell);
    }


public static void CreateComponent<T>(this RenderTreeBuilder builder) where T : IComponent
    {
        var sequence = 0;
        CreateComponent<T>(builder, ref sequence);
    }


public static IServiceCollection AddLoginModuleCore(this IServiceCollection services)
    {
        return services
                .AddScoped<IFeature, Feature>()
                .AddOptions()
                .AddAuthorizationCore()
                .AddScoped<AuthenticatingApiHttpMessageHandler>()
                .AddScoped<AuthenticationStateProvider, AccessTokenAuthenticationStateProvider>()
                .AddScoped<IUnauthorizedComponentProvider, RedirectToLoginUnauthorizedComponentProvider>()
                .AddScoped<ICredentialsValidator, DefaultCredentialsValidator>()
            ;
    }


public static CheckList GetCheckList(this InputDescriptor descriptor)
    {
        var specifications = descriptor.UISpecifications;
        var props = specifications != null ? specifications.TryGetValue("checklist", out var propsValue) ? propsValue is JsonElement value ? value : default : default : default;

        if (props.ValueKind == JsonValueKind.Undefined)
            return new CheckList(Array.Empty<CheckListItem>());

        var serializerOptions = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        };

        var checkListProps = props.Deserialize<CheckListProps>(serializerOptions);
        return checkListProps?.CheckList ?? new CheckList(Array.Empty<CheckListItem>());
    }


public async Task<IEnumerable<LogPersistenceStrategyDescriptor>> GetLogPersistenceStrategiesAsync(CancellationToken cancellationToken = default)
    {
        if (_descriptors == null)
        {
            var api = await backendApiClientProvider.GetApiAsync<ILogPersistenceStrategiesApi>(cancellationToken);
            var response = await api.ListAsync(cancellationToken);
            _descriptors = response.Items;
        }

        return _descriptors;
    }


public async Task<PagedListResponse<WorkflowDefinitionSummary>> ListAsync(ListWorkflowDefinitionsRequest request, VersionOptions? versionOptions = null, CancellationToken cancellationToken = default)
    {
        var api = await GetApiAsync(cancellationToken);
        return await api.ListAsync(request, versionOptions, cancellationToken);
    }


public static bool IsAspNetStaticRequest(this HttpRequest request) =>
			(request is not null) &&
			request.Headers.TryGetValue(HeaderNames.UserAgent, out var ua) &&
			ua.Contains(Consts.AspNetStatic);


public static void GenerateStaticContent(
			this IHost host,
			string destinationRoot,
			bool exitWhenDone = default,
			bool alwaysDefaultFile = default,
			bool dontUpdateLinks = default,
			bool dontOptimizeContent = default,
			TimeSpan? regenerationInterval = default,
			ulong httpTimeoutSeconds = c_DefaultHttpTimeoutSeconds)
		{
			Throw.IfNull(host);
			Throw.IfNullOrWhitespace(destinationRoot);

			var fileSystem = host.Services.GetService<IFileSystem>() ?? new FileSystem();

			Throw.DirectoryNotFoundWhen(
				() => !fileSystem.Directory.Exists(destinationRoot),
				SR.Err_InvalidDestinationRoot);

			var loggerFactory = host.Services.GetRequiredService<ILoggerFactory>();
			var logger = loggerFactory.CreateLogger(nameof(StaticGeneratorHostExtension));

			var pageUrlProvider = host.Services.GetRequiredService<IStaticResourcesInfoProvider>();

			if (!pageUrlProvider.PageResources.Any())
			{
				logger.NoPagesToProcess();
				return;
			}

			var optimizerSelector = GetOptimizerSelector(host.Services, dontOptimizeContent);

			var lifetime = host.Services.GetRequiredService<IHostApplicationLifetime>();

			lifetime.ApplicationStopping.Register(_appShutdown.Cancel);

			lifetime.ApplicationStarted.Register(
				async () =>
				{
					try
					{
						_httpClient.BaseAddress = new Uri(GetBaseUri(host));
						_httpClient.Timeout = TimeSpan.FromSeconds(httpTimeoutSeconds);
						_httpClient.DefaultRequestHeaders.Add(HeaderNames.UserAgent, Consts.AspNetStatic);

						var generatorConfig =
							new StaticGeneratorConfig(
								pageUrlProvider.Resources,
								destinationRoot,
								alwaysDefaultFile,
								!dontUpdateLinks,
								pageUrlProvider.DefaultFileName,
								pageUrlProvider.PageFileExtension.EnsureStartsWith('.'),
								pageUrlProvider.DefaultFileExclusions,
								!dontOptimizeContent, optimizerSelector,
								pageUrlProvider.SkipPageResources,
								pageUrlProvider.SkipCssResources,
								pageUrlProvider.SkipJsResources,
								pageUrlProvider.SkipBinResources);

						logger.RegenerationConfig(regenerationInterval);
						var doPeriodicRefresh = regenerationInterval is not null;

#if USE_PERIODIC_TIMER
						if (!_appShutdown.IsCancellationRequested)
						{
							if (doPeriodicRefresh) _timer = new(regenerationInterval!.Value);
							do
							{
								await StaticGenerator.Execute(
									generatorConfig,
									_httpClient, fileSystem,
									loggerFactory, _appShutdown.Token)
								.ConfigureAwait(false);
							}
							while (doPeriodicRefresh && await _timer.WaitForNextTickAsync(_appShutdown.Token).ConfigureAwait(false));
						}

						if (exitWhenDone && !doPeriodicRefresh && !_appShutdown.IsCancellationRequested)
						{
							logger.Exiting();
							await Task.Delay(500).ConfigureAwait(false);
							await host.StopAsync().ConfigureAwait(false);
						}
#else
						await StaticPageGenerator.Execute(
							generatorConfig,
							_httpClient, fileSystem,
							loggerFactory, _appShutdown.Token)
							.ConfigureAwait(false);

						if (doPeriodicRefresh && !_appShutdown.IsCancellationRequested)
						{
							_timer.Interval = regenerationInterval!.Value.TotalMilliseconds;
							_timer.Elapsed +=
								async (s, e) =>
								{
									await StaticPageGenerator.Execute(
										generatorConfig,
										_httpClient, fileSystem,
										loggerFactory, _appShutdown.Token)
										.ConfigureAwait(false);

									if (_appShutdown.IsCancellationRequested)
									{
										_timer.Stop();
									}
								};
							_timer.Start();
						}

						if (_appShutdown.IsCancellationRequested)
						{
							_timer.Stop();
						}
						else if (exitWhenDone && !doPeriodicRefresh)
						{
							logger.Exiting();
							await Task.Delay(500).ConfigureAwait(false);
							await host.StopAsync().ConfigureAwait(false);
						}
#endif
					}
					catch (OperationCanceledException) { }
					catch (Exception ex)
					{
						logger.Exception(ex);
					}
				});
		}


internal static string ToCitationUrl(this string fileName, string baseUrl)
    {
        var builder = new UriBuilder(baseUrl);
        builder.Path += $"/{fileName}";
        builder.Fragment = "view-fitV";

        return builder.Uri.AbsoluteUri;
    }


[TestMethod]
    [TestCategory("LiveData")]
    public async Task LoginUI_ControllerPATLoginTest_Success()
    {
        // Create DataRows during Runtime since these need Env vars
        RuntimeDataRow[] dataRows =
                                {
                                    new()
                                    {
                                        InitialState = "EnterpriseServerPATPage",
                                        Actions = LoginUITestData.ConnectButtonAction,
                                        Inputs = LoginUITestData.GoodPATEnterpriseServerPATInput,
                                        FinalState = "LoginSucceededPage",
                                        HostAddress = Environment.GetEnvironmentVariable("DEV_HOME_TEST_GITHUB_ENTERPRISE_SERVER"),
                                    },
                                    new()
                                    {
                                        InitialState = "EnterpriseServerPATPage",
                                        Actions = LoginUITestData.ConnectButtonAction,
                                        Inputs = LoginUITestData.GoodPATGithubComPATInput,
                                        FinalState = "LoginSucceededPage",
                                        HostAddress = "https://api.github.com",
                                    },
                                }
            ;

        foreach (RuntimeDataRow dataRow in dataRows)
        {
            var testExtensionAdaptiveCard = new MockExtensionAdaptiveCard(string.Empty, string.Empty, string.Empty);
            Assert.AreEqual(0, testExtensionAdaptiveCard.UpdateCount);

            // Create a LoginUIController and initialize it with the testExtensionAdaptiveCard.
            var controller = new LoginUIController(MockDeveloperIdProvider.GetInstance());
            Assert.AreEqual(ProviderOperationStatus.Success, controller.Initialize(testExtensionAdaptiveCard).Status);
            Assert.AreEqual(1, testExtensionAdaptiveCard.UpdateCount);

            // Set the initial state.
            testExtensionAdaptiveCard.State = dataRow.InitialState ?? string.Empty;
            Assert.AreEqual(dataRow.InitialState, testExtensionAdaptiveCard.State);

            // Set HostAddress for EnterpriseServerPATPage to make this a valid state
            if (dataRow.InitialState == "EnterpriseServerPATPage")
            {
                controller.HostAddress = new Uri(dataRow.HostAddress ?? string.Empty);
                Assert.AreEqual(dataRow.HostAddress, controller.HostAddress.OriginalString);
            }

            // Call OnAction() with the actions and inputs.
            Assert.AreEqual(ProviderOperationStatus.Success, (await controller.OnAction(dataRow.Actions ?? string.Empty, dataRow.Inputs ?? string.Empty)).Status);

            // Verify the final state
            Assert.AreEqual(dataRow.FinalState, testExtensionAdaptiveCard.State);
            Assert.AreEqual(2, testExtensionAdaptiveCard.UpdateCount);

            controller.Dispose();
        }
    }

/* This test requires the following environment variables to be set:
     * DEV_HOME_TEST_GITHUB_ENTERPRISE_SERVER : The host address of the GitHub Enterprise Server to test against
     * DEV_HOME_TEST_GITHUB_COM_PAT : A valid Personal Access Token for GitHub.com (with at least repo_public permissions)
     * DEV_HOME_TEST_GITHUB_ENTERPRISE_SERVER_PAT : A valid Personal Access Token for the GitHub Enterprise Server set in DEV_HOME_TEST_GITHUB_ENTERPRISE_SERVER (with at least repo_public permissions)
     */


public override bool IsMDim() { return false; }


void RegisterDragDropEvents()
    {
        var manager = CoreDragDropManager.GetForCurrentView();
        manager.TargetRequested += TargetRequested;
    }


public override void Interp(int from, int to, float t)
        {
            // Int snapping interpolation. Don't use this for enums as they don't necessarily have
            // contiguous values. Use the default interpolator instead (same as bool).
            value = (int)(from + (to - from) * t);
        }


public Sequence<Statement?> ParseSqlStatements(string sql, IEnumerable<Dialect> dialects, bool unescape = false, ParserOptions? options = null)
    {
        options ??= new ParserOptions { Unescape = unescape };
        return OneOfIdenticalResults(dialect =>
        {
            options.TrailingCommas |= dialect.SupportsTrailingCommas;
            return new Parser().ParseSql(sql, dialect, options);
        }, dialects)!;
    }

// Ensures that `sql` parses as a single statement and returns it.


function fictioneer_build_customize_css( $context = null ) {
  // --- Setup -----------------------------------------------------------------

  $file_path = fictioneer_get_theme_cache_dir( 'build_customize_css' ) . '/customize.css';
  $site_width = (int) get_theme_mod( 'site_width', FICTIONEER_DEFAULT_SITE_WIDTH );
  $header_image_style = get_theme_mod( 'header_image_style', 'default' );
  $header_style = get_theme_mod( 'header_style', 'default' );
  $content_list_style = get_theme_mod( 'content_list_style', 'default' );
  $page_style = get_theme_mod( 'page_style', 'default' );
  $card_style = get_theme_mod( 'card_style', 'default' );
  $card_frame = get_theme_mod( 'card_frame', 'default' );
  $footer_style = get_theme_mod( 'footer_style', 'default' );
  $sidebar_style = get_theme_mod( 'sidebar_style', 'none' );
  $css = '';

  if ( $context === 'preview' ) {
    $file_path = fictioneer_get_theme_cache_dir( 'preview' ) . '/customize-preview.css';
  }

  // --- Assets ----------------------------------------------------------------

  if (
    $header_image_style === 'polygon-battered' ||
    in_array( $page_style, ['polygon-battered', 'polygon-mask-image-battered-ringbook'] )
  ) {
    $css .= fictioneer_get_customizer_css_snippet( 'polygon-battered' );
  }

  // --- Header image style ----------------------------------------------------

  if ( $header_image_style === 'polygon-battered' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'header-image-style-battered' );
  }

  if ( $header_image_style === 'polygon-chamfered' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'header-image-style-chamfered' );
  }

  if ( $header_image_style === 'mask-grunge-frame-a-large' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'header-image-style-grunge-frame-a-large' );
  }

  if ( $header_image_style === 'mask-grunge-frame-a-small' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'header-image-style-grunge-frame-a-small' );
  }

  // --- Fading header image ---------------------------------------------------

  $header_image_fading_start = fictioneer_sanitize_integer( get_theme_mod( 'header_image_fading_start', 0 ), 0, 0, 99 );
  $header_image_fading_breakpoint = (int) get_theme_mod( 'header_image_fading_breakpoint', 0 );

  if ( $header_image_fading_start > 0 ) {
    if ( $header_image_fading_breakpoint > 320 ) {
      $css .= "@media only screen and (min-width: {$header_image_fading_breakpoint}px) {
        :root {
          --header-fading-mask-image: " . fictioneer_get_fading_gradient( 100, $header_image_fading_start, 100, 'var(--header-fading-mask-image-rotation, 180deg)' ) . ";
        }
      }";
    } else {
      $css .= ":root {
        --header-fading-mask-image: " . fictioneer_get_fading_gradient( 100, $header_image_fading_start, 100, 'var(--header-fading-mask-image-rotation, 180deg)' ) . ";
      }";
    }

    $css .= '@media only screen and (min-width: 1024px) {
      .inset-header-image .header-background._style-default._fading-bottom._shadow .header-background__wrapper {
        margin-left: 4px;
        margin-right: 4px;
      }
    }';
  }

  // --- Inset header image ----------------------------------------------------

  if ( get_theme_mod( 'inset_header_image' ) ) {
    $css .= fictioneer_get_customizer_css_snippet( 'inset-header-image' );
  }

  // --- Base layout -----------------------------------------------------------

  $hue_offset_dark = (int) get_theme_mod( 'hue_offset', 0 );
  $saturation_offset_dark = (int) get_theme_mod( 'saturation_offset', 0 );
  $lightness_offset_dark = (int) get_theme_mod( 'lightness_offset', 0 );
  $font_saturation_offset_dark = (int) get_theme_mod( 'font_saturation_offset', 0 );
  $font_lightness_offset_dark = (int) get_theme_mod( 'font_lightness_offset', 0 );
  $hue_offset_light = (int) get_theme_mod( 'hue_offset_light', 0 );
  $saturation_offset_light = (int) get_theme_mod( 'saturation_offset_light', 0 );
  $lightness_offset_light = (int) get_theme_mod( 'lightness_offset_light', 0 );
  $font_saturation_offset_light = (int) get_theme_mod( 'font_saturation_offset_light', 0 );
  $font_lightness_offset_light = (int) get_theme_mod( 'font_lightness_offset_light', 0 );
  $site_width = (int) get_theme_mod( 'site_width', FICTIONEER_DEFAULT_SITE_WIDTH );
  $main_offset = (int) get_theme_mod( 'main_offset', 0 );
  $sidebar_width = (int) get_theme_mod( 'sidebar_width', 256 );
  $sidebar_gap = (int) get_theme_mod( 'sidebar_gap', 48 );
  $logo_min_height = (int) get_theme_mod( 'logo_min_height', 210 );
  $logo_max_height = (int) get_theme_mod( 'logo_height', 210 );
  $title_min = (int) get_theme_mod( 'site_title_font_size_min', 32 );
  $title_max = (int) get_theme_mod( 'site_title_font_size_max', 60 );
  $tagline_min = (int) get_theme_mod( 'site_tagline_font_size_min', 13 );
  $tagline_max = (int) get_theme_mod( 'site_tagline_font_size_max', 18 );
  $header_image_min = (int) get_theme_mod( 'header_image_height_min', 210 );
  $header_image_max = (int) get_theme_mod( 'header_image_height_max', 480 );
  $header_bg_color_light = fictioneer_get_theme_color( 'header_color_light', '' );
  $header_bg_color_dark = fictioneer_get_theme_color( 'header_color_dark', '' );
  $header_min = (int) get_theme_mod( 'header_height_min', 190 );
  $header_max = (int) get_theme_mod( 'header_height_max', 380 );
  $story_cover_width_offset = (int) get_theme_mod( 'story_cover_width_offset', 0 );
  $story_cover_box_shadow = get_theme_mod( 'story_cover_shadow', 'var(--box-shadow-xl)' );
  $card_grid_column_min = (int) get_theme_mod( 'card_grid_column_min', 308 );
  $card_cover_width_mod = get_theme_mod( 'card_cover_width_mod', 1 );
  $card_grid_column_gap_mod = get_theme_mod( 'card_grid_column_gap_mod', 1 );
  $card_grid_row_gap_mod = get_theme_mod( 'card_grid_row_gap_mod', 1 );
  $card_font_size_min_mod = get_theme_mod( 'card_font_size_min_mod', 0 );
  $card_font_size_grow_mod = get_theme_mod( 'card_font_size_grow_mod', 0 );
  $card_font_size_max_mod = get_theme_mod( 'card_font_size_max_mod', 0 );
  $card_box_shadow = get_theme_mod( 'card_shadow', 'var(--box-shadow-m)' );
  $font_primary = fictioneer_get_custom_font( 'primary_font_family_value', 'var(--ff-system)', 'Open Sans' );
  $font_secondary = fictioneer_get_custom_font( 'secondary_font_family_value', 'var(--ff-base)', 'Lato' );
  $font_heading = fictioneer_get_custom_font( 'heading_font_family_value', 'var(--ff-base)', 'Open Sans' );
  $font_site_title = fictioneer_get_custom_font( 'site_title_font_family_value', 'var(--ff-heading)', 'default' );
  $font_nav_item = fictioneer_get_custom_font( 'nav_item_font_family_value', 'var(--ff-base)', 'default' );
  $font_story_title = fictioneer_get_custom_font( 'story_title_font_family_value', 'var(--ff-heading)', 'default' );
  $font_chapter_title = fictioneer_get_custom_font( 'chapter_title_font_family_value', 'var(--ff-heading)', 'default' );
  $font_chapter_list_title = fictioneer_get_custom_font( 'chapter_list_title_font_family_value', 'var(--ff-base)', 'default' );
  $font_card_title = fictioneer_get_custom_font( 'card_title_font_family_value', 'var(--ff-heading)', 'default' );
  $font_card_body = fictioneer_get_custom_font( 'card_body_font_family_value', 'var(--ff-note)', 'default' );
  $font_card_list_link = fictioneer_get_custom_font( 'card_list_link_font_family_value', 'var(--ff-note)', 'default' );

  $dark_shade = fictioneer_hex_to_rgb( get_theme_mod( 'dark_shade', '000000' ) );
  $dark_shade = is_array( $dark_shade ) ? $dark_shade : [0, 0, 0];

  if ( $logo_min_height < $logo_max_height ) {
    $logo_height = fictioneer_get_css_clamp( $logo_min_height, $logo_max_height, 320, $site_width );
  } else {
    $logo_height = $logo_max_height . 'px';
  }

  $css .= ":root {
    --site-width: {$site_width}px;
    --main-offset: {$main_offset}px;
    --sidebar-width: {$sidebar_width}px;
    --sidebar-gap: {$sidebar_gap}px;
    --hue-offset: {$hue_offset_dark}deg;
    --saturation-offset: " . $saturation_offset_dark / 100 . ";
    --lightness-offset: " . $lightness_offset_dark / 100 . ";
    --font-saturation-offset: " . $font_saturation_offset_dark / 100 . ";
    --font-lightness-offset: " . $font_lightness_offset_dark / 100 . ";
    --header-image-height: " . fictioneer_get_css_clamp( $header_image_min, $header_image_max, 320, $site_width ) . ";
    --header-height: calc(" . fictioneer_get_css_clamp( $header_min, $header_max, 320, $site_width ) . " - var(--page-inset-top, 0px));
    --header-logo-height: {$logo_height};
    --header-logo-min-height: {$logo_min_height};
    --header-logo-max-height: {$logo_max_height};
    --site-title-font-size: " . fictioneer_get_css_clamp( $title_min, $title_max, 320, $site_width ) . ";
    --site-title-tagline-font-size: " . fictioneer_get_css_clamp( $tagline_min, $tagline_max, 320, $site_width ) . ";
    --grid-columns-min: {$card_grid_column_min}px;
    --grid-columns-row-gap-multiplier: {$card_grid_row_gap_mod};
    --grid-columns-col-gap-multiplier: {$card_grid_column_gap_mod};
    --card-font-size-min-mod: {$card_font_size_min_mod}px;
    --card-font-size-grow-mod: {$card_font_size_grow_mod}px;
    --card-font-size-max-mod: {$card_font_size_max_mod}px;
    --ff-base: {$font_primary};
    --ff-note: {$font_secondary};
    --ff-heading: {$font_heading};
    --ff-site-title: {$font_site_title};
    --ff-story-title: {$font_story_title};
    --ff-chapter-title: {$font_chapter_title};
    --ff-chapter-list-title: {$font_chapter_list_title};
    --ff-card-title: {$font_card_title};
    --ff-card-body: {$font_card_body};
    --ff-card-list-link: {$font_card_list_link};
    --ff-nav-item: {$font_nav_item};
    --card-cover-width-mod: {$card_cover_width_mod};
    --card-box-shadow: {$card_box_shadow};
    --card-drop-shadow: " . str_replace( 'box-', 'drop-', $card_box_shadow ) . ";
    --story-cover-box-shadow: {$story_cover_box_shadow};
    --recommendation-cover-box-shadow: {$story_cover_box_shadow};
    --floating-cover-image-width: " . fictioneer_get_css_clamp( 56, 200 + $story_cover_width_offset, 320, 768 ) . ";
    --in-content-cover-image-width: " . fictioneer_get_css_clamp( 100, 200 + $story_cover_width_offset, 375, 768 ) . ";
  }";

  if ( $card_box_shadow === 'none' ) {
    $css .= ".card{box-shadow:none!important;}";
  }

  // Only light mode
  $css .= ":root[data-mode=light] {
    --hue-offset: {$hue_offset_light}deg;
    --saturation-offset: " . $saturation_offset_light / 100 . ";
    --lightness-offset: " . $lightness_offset_light / 100 . ";
    --font-saturation-offset: " . $font_saturation_offset_light / 100 . ";
    --font-lightness-offset: " . $font_lightness_offset_light / 100 . ";
  }";

  // --- Custom layout ---------------------------------------------------------

  if ( $sidebar_style !== 'none' ) {
    $css .= ":root, :root[data-theme=base] {
      --layout-spacing-horizontal: " . fictioneer_get_css_clamp( 20, 48, 480, $site_width ) . ";
      --layout-spacing-horizontal-small: " . fictioneer_get_css_clamp( 10, 20, 320, 400 ) . ";
    }";
  }

  if ( get_theme_mod( 'use_custom_layout', false ) ) {
    $vertical_min = (int) get_theme_mod( 'vertical_spacing_min', 24 );
    $vertical_max = (int) get_theme_mod( 'vertical_spacing_max', 48 );
    $horizontal_min = (int) get_theme_mod( 'horizontal_spacing_min', 20 );
    $horizontal_max = (int) get_theme_mod( 'horizontal_spacing_max', 80 );
    $horizontal_small_min = (int) get_theme_mod( 'horizontal_spacing_small_min', 10 );
    $horizontal_small_max = (int) get_theme_mod( 'horizontal_spacing_small_max', 20 );
    $large_border_radius = (int) get_theme_mod( 'large_border_radius', 4 );
    $small_border_radius = (int) get_theme_mod( 'small_border_radius', 2 );
    $nested_border_radius_multiplier = max( 0, get_theme_mod( 'nested_border_radius_multiplier', 1 ) );
    $content_list_gap = (int) get_theme_mod( 'content_list_gap', 4 );

    $css .= ":root, :root[data-theme=base] {
      --layout-spacing-vertical: " . fictioneer_get_css_clamp( $vertical_min, $vertical_max, 480, $site_width ) . ";
      --layout-spacing-horizontal: " . fictioneer_get_css_clamp( $horizontal_min, $horizontal_max, 480, $site_width, '%' ) . ";
      --layout-spacing-horizontal-small: " . fictioneer_get_css_clamp( $horizontal_small_min, $horizontal_small_max, 320, 400, '%' ) . ";
      --layout-border-radius-large: {$large_border_radius}px;
      --layout-border-radius-small: {$small_border_radius}px;
      --layout-nested-border-radius-multiplier: {$nested_border_radius_multiplier};
      --chapter-list-gap: {$content_list_gap}px;
      --content-list-gap: {$content_list_gap}px;
    }";

    if ( $sidebar_style !== 'none' ) {
      $css .= ".has-sidebar {
        --layout-spacing-horizontal: " . fictioneer_get_css_clamp( $horizontal_min, $horizontal_max, 480, $site_width ) . ";
        --layout-spacing-horizontal-small: " . fictioneer_get_css_clamp( $horizontal_small_min, $horizontal_small_max, 320, 400 ) . ";
      }";
    }
  }

  // --- Dark mode font weight adjustment --------------------------------------

  if ( get_theme_mod( 'dark_mode_font_weight', 'adjusted' ) === 'normal' ) {
    $css .= ":root[data-font-weight=default]:is(html) {
      --font-smoothing-webkit: subpixel-antialiased;
      --font-smoothing-moz: auto;
      --font-weight-normal: 400;
      --font-weight-semi-strong: 600;
      --font-weight-strong: 600;
      --font-weight-medium: 500;
      --font-weight-heading: 700;
      --font-weight-badge: 600;
      --font-weight-post-meta: 400;
      --font-weight-read-ribbon: 700;
      --font-weight-card-label: 600;
      --font-weight-navigation: 400;
      --font-letter-spacing-base: 0em;
    }";
  }

  // --- Dark mode colors ------------------------------------------------------

  $css .= ":root {
    --site-title-heading-color: " . fictioneer_hsl_font_code( fictioneer_get_theme_color( 'dark_header_title_color' ) ) . ";
    --site-title-tagline-color: " . fictioneer_hsl_font_code( fictioneer_get_theme_color( 'dark_header_tagline_color' ) ) . ";
  }";

  if ( $header_bg_color_dark ) {
    $css .= ":root {
      --header-background-color: " . fictioneer_hsl_code( $header_bg_color_dark ) . ";
    }";
  }

  if ( get_theme_mod( 'use_custom_dark_mode', false ) ) {
    $css .= ":root, :root[data-theme=base] {"
      .
      implode( '', array_map( function( $prop ) {
        return "--bg-{$prop}-free: " . fictioneer_hsl_code( fictioneer_get_theme_color( "dark_bg_{$prop}" ), 'free' ) . ';';
      }, ['50', '100', '200', '300', '400', '500', '600', '700', '800', '900', '950'] ) )
      .
      "
      --card-frame-border-color: " . fictioneer_hsl_code( fictioneer_get_theme_color( 'dark_card_frame' ) ) . ";
      --dark-shade-rgb:" . implode( ' ', $dark_shade ) . ";
      --primary-400: " . fictioneer_get_theme_color( 'dark_primary_400' ) . ";
      --primary-500: " . fictioneer_get_theme_color( 'dark_primary_500' ) . ";
      --primary-600: " . fictioneer_get_theme_color( 'dark_primary_600' ) . ";
      --red-400: " . fictioneer_get_theme_color( 'dark_red_400' ) . ";
      --red-500: " . fictioneer_get_theme_color( 'dark_red_500' ) . ";
      --red-600: " . fictioneer_get_theme_color( 'dark_red_600' ) . ";
      --green-400: " . fictioneer_get_theme_color( 'dark_green_400' ) . ";
      --green-500: " . fictioneer_get_theme_color( 'dark_green_500' ) . ";
      --green-600: " . fictioneer_get_theme_color( 'dark_green_600' ) . ";
      --theme-color-base: " . fictioneer_hsl_code( fictioneer_get_theme_color( 'dark_theme_color_base' ), 'values' ) . ";
      --navigation-background: " . fictioneer_hsl_code( fictioneer_get_theme_color( 'dark_navigation_background_sticky' ) ) . ";
      --bookmark-color-alpha: " . fictioneer_get_theme_color( 'dark_bookmark_color_alpha' ) . ";
      --bookmark-color-beta: " . fictioneer_get_theme_color( 'dark_bookmark_color_beta' ) . ";
      --bookmark-color-gamma: " . fictioneer_get_theme_color( 'dark_bookmark_color_gamma' ) . ";
      --bookmark-color-delta: " . fictioneer_get_theme_color( 'dark_bookmark_color_delta' ) . ";
      --bookmark-line: " . fictioneer_get_theme_color( 'dark_bookmark_line_color' ) . ";
      --ins-background: " . fictioneer_get_theme_color( 'dark_ins_background' ) . ";
      --del-background: " . fictioneer_get_theme_color( 'dark_del_background' ) . ";"
      .
      implode( '', array_map( function( $prop ) {
        return "--badge-{$prop}-background: " . fictioneer_get_theme_color( "dark_badge_{$prop}_background" ) . ';';
      }, ['generic', 'moderator', 'admin', 'author', 'supporter', 'override'] ) )
      .
    "}
    :root, :root[data-theme=base], :root .chapter-formatting, :root[data-theme=base] .chapter-formatting {"
      .
      implode( '', array_map( function( $prop ) {
        return "--fg-{$prop}: " . fictioneer_hsl_font_code( fictioneer_get_theme_color( "dark_fg_{$prop}" ) ) . ';';
      }, ['100', '200', '300', '400', '500', '600', '700', '800', '900', '950', 'tinted', 'inverted'] ) )
      .
    "}";
  }

  // --- Light mode colors -----------------------------------------------------

  $css .= ":root[data-mode=light] {
    --site-title-heading-color: " . fictioneer_hsl_font_code( fictioneer_get_theme_color( 'light_header_title_color' ) ) . ";
    --site-title-tagline-color: " . fictioneer_hsl_font_code( fictioneer_get_theme_color( 'light_header_tagline_color' ) ) . ";
  }";

  if ( $header_bg_color_light ) {
    $css .= ":root[data-mode=light] {
      --header-background-color: " . fictioneer_hsl_code( $header_bg_color_light ) . ";
    }";
  }

  if ( get_theme_mod( 'use_custom_light_mode', false ) ) {
    $css .= ":root[data-mode=light] {"
      .
      implode( '', array_map( function( $prop ) {
        return "--bg-{$prop}-free: " . fictioneer_hsl_code( fictioneer_get_theme_color( "light_bg_{$prop}" ), 'free' ) . ';';
      }, ['50', '100', '200', '300', '400', '500', '600', '700', '800', '900', '950'] ) )
      .
      "
      --card-frame-border-color: " . fictioneer_hsl_code( fictioneer_get_theme_color( 'light_card_frame' ) ) . ";
      --primary-400: " . fictioneer_get_theme_color( 'light_primary_400' ) . ";
      --primary-500: " . fictioneer_get_theme_color( 'light_primary_500' ) . ";
      --primary-600: " . fictioneer_get_theme_color( 'light_primary_600' ) . ";
      --red-400: " . fictioneer_get_theme_color( 'light_red_400' ) . ";
      --red-500: " . fictioneer_get_theme_color( 'light_red_500' ) . ";
      --red-600: " . fictioneer_get_theme_color( 'light_red_600' ) . ";
      --green-400: " . fictioneer_get_theme_color( 'light_green_400' ) . ";
      --green-500: " . fictioneer_get_theme_color( 'light_green_500' ) . ";
      --green-600: " . fictioneer_get_theme_color( 'light_green_600' ) . ";
      --theme-color-base: " . fictioneer_hsl_code( fictioneer_get_theme_color( 'light_theme_color_base' ), 'values' ) . ";
      --navigation-background: " . fictioneer_hsl_code( fictioneer_get_theme_color( 'light_navigation_background_sticky' ) ) . ";
      --bookmark-color-alpha: " . fictioneer_get_theme_color( 'light_bookmark_color_alpha' ) . ";
      --bookmark-color-beta: " . fictioneer_get_theme_color( 'light_bookmark_color_beta' ) . ";
      --bookmark-color-gamma: " . fictioneer_get_theme_color( 'light_bookmark_color_gamma' ) . ";
      --bookmark-color-delta: " . fictioneer_get_theme_color( 'light_bookmark_color_delta' ) . ";
      --bookmark-line: " . fictioneer_get_theme_color( 'light_bookmark_line_color' ) . ";
      --ins-background: " . fictioneer_get_theme_color( 'light_ins_background' ) . ";
      --del-background: " . fictioneer_get_theme_color( 'light_del_background' ) . ";"
      .
      implode( '', array_map( function( $prop ) {
        return "--badge-{$prop}-background: " . fictioneer_get_theme_color( "light_badge_{$prop}_background" ) . ';';
      }, ['generic', 'moderator', 'admin', 'author', 'supporter', 'override'] ) )
      .
    "}
    :root[data-mode=light], :root[data-mode=light] .chapter-formatting {"
      .
      implode( '', array_map( function( $prop ) {
        return "--fg-{$prop}: " . fictioneer_hsl_font_code( fictioneer_get_theme_color( "light_fg_{$prop}" ) ) . ';';
      }, ['100', '200', '300', '400', '500', '600', '700', '800', '900', '950', 'tinted', 'inverted'] ) )
      .
    "}";
  }

  // --- Header styles ---------------------------------------------------------

  if ( in_array( $header_style, ['top', 'split'] ) ) {
    $css .= fictioneer_get_customizer_css_snippet( 'header-style-top-split' );
  }

  if ( $header_style === 'wide' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'header-style-wide' );
  }

  if ( $header_style === 'text_center' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'header-style-text-center' );
  }

  if ( $header_style === 'post_content' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'header-style-post-content' );
  }

  // --- Page styles -----------------------------------------------------------

  if ( $page_style === 'polygon-mask-image-battered-ringbook' || $page_style === 'polygon-battered' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'page-style-battered' );
  }

  if ( $page_style === 'polygon-mask-image-battered-ringbook' || $page_style === 'mask-image-ringbook' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'page-style-ringbook' );
  }

  if ( $page_style === 'polygon-chamfered' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'page-style-chamfered' );
  }

  if ( $page_style === 'polygon-interface-a' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'page-style-interface-a' );
  }

  if ( $page_style === 'mask-image-wave-a' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'page-style-wave-a' );
  }

  if ( $page_style === 'mask-image-layered-steps-a' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'page-style-layered-steps-a' );
  }

  if ( $page_style === 'mask-image-layered-peaks-a' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'page-style-layered-peaks-a' );
  }

  if ( $page_style === 'mask-image-grunge-a' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'page-style-grunge-a' );
  }

  if ( $page_style === 'none' ) {
    $css .= '.main__background { display:none !important; }';
  }

  // --- Page shadow -----------------------------------------------------------

  if ( ! get_theme_mod( 'page_shadow', true ) ) {
    $css .= ':root.no-page-shadow {
      --minimal-page-box-shadow: none;
      --page-box-shadow: none;
      --page-drop-shadow: none;
    }';
  }

  // --- Card styles -----------------------------------------------------------

  if ( in_array( $card_style, ['unfolded', 'combined'] ) ) {
    $css .= fictioneer_get_customizer_css_snippet( 'card-style-unfolded-combined' );
  }

  if ( $card_style === 'combined' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'card-style-combined' );
  }

  // --- Card frames -----------------------------------------------------------

  if ( $card_frame !== 'default' ) {
    $css .= ':root:not(.minimal) .card{filter:var(--card-drop-shadow);}';
  }

  if ( in_array( $card_frame, ['stacked_right', 'stacked_left', 'stacked_random'] ) ) {
    $css .= fictioneer_get_customizer_css_snippet( 'card-frame-stacked' );

    if ( $card_frame === 'stacked_left' ) {
      $css .= '.card{--this-rotation-mod:-1;}';
    }
  }

  if ( $card_frame === 'border_2px' ) {
    $css .= ':root:not(.minimal) .card{--card-style-border-width: 2px;box-shadow: 0 0 0 var(--card-frame-border-thickness, 2px) var(--card-frame-border-color);}';
  }

  if ( $card_frame === 'border_3px' ) {
    $css .= ':root:not(.minimal) .card{--card-style-border-width: 3px;box-shadow: 0 0 0 var(--card-frame-border-thickness, 3px) var(--card-frame-border-color);}';
  }

  if ( $card_frame === 'chamfered' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'card-frame-chamfered' );
  }

  if ( $card_frame === 'battered' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'card-frame-battered' );
  }

  // --- Content list style ----------------------------------------------------

  if ( $content_list_style === 'full' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'content-list-style-full' );
  }

  if ( $content_list_style === 'free' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'content-list-style-free' );
  }

  if ( $content_list_style === 'lines' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'content-list-style-lines' );
  }

  // --- Footer style ----------------------------------------------------------

  if ( $footer_style === 'isolated' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'footer-style-isolated' );
  }

  // --- Filters ---------------------------------------------------------------

  $css = apply_filters( 'fictioneer_filter_pre_build_customize_css', $css );

  // --- Minify ----------------------------------------------------------------

  $css = fictioneer_minify_css( $css );

  // --- Update options --------------------------------------------------------

  if ( $context !== 'preview' ) {
    update_option( 'fictioneer_customize_css_timestamp', time(), true );
  }

  // --- Save ------------------------------------------------------------------

  file_put_contents( $file_path, $css );
}

/**
 * Builds customization stylesheet
 *
 * @since 5.11.0
 *
 * @param string|null $context  Optional. In which context the stylesheet created,
 *                              for example 'preview' for the Customizer.
 */


function fictioneer_save_word_count( $post_id ) {
  // Prevent multi-fire
  if ( fictioneer_multi_save_guard( $post_id ) ) {
    return;
  }

  // Count
  $word_count = fictioneer_count_words( $post_id );

  // Save
  update_post_meta( $post_id, '_word_count', $word_count );
}


function fictioneer_maintenance_mode() {
  if ( get_option( 'fictioneer_enable_maintenance_mode' ) && ! is_customize_preview() ) {
    if ( ! current_user_can( 'edit_themes' ) || ! is_user_logged_in() ) {
      $note = get_option( 'fictioneer_phrase_maintenance' );
      $note = ! empty( $note ) ? $note : __( 'Website under planned maintenance. Please check back later.', 'fictioneer' );

      wp_die( __( '
Under Maintenance

', 'fictioneer' ) . $note );
    }
  }
}


function fictioneer_is_commenting_disabled( $post_id = null ) {
    // Setup
    $post_id = $post_id ?? get_the_ID();

    // Return immediately if...
    if (
      get_option( 'fictioneer_disable_commenting' ) ||
      get_post_meta( $post_id, 'fictioneer_disable_commenting', true )
    ) {
      return true;
    }

    // Check parent story if chapter...
    if ( get_post_type( $post_id ) === 'fcn_chapter' ) {
      $story_id = fictioneer_get_chapter_story_id( $post_id );

      if ( $story_id ) {
        return get_post_meta( $story_id, 'fictioneer_disable_commenting', true ) == true;
      }
    }

    return false;
  }

/**
   * Check whether commenting is disabled
   *
   * Differs from comments_open() in the regard that it does not hide the whole
   * comment section but does not allow new comments to be posted.
   *
   * @since 5.0.0
   *
   * @param int|null $post_id  Post ID the comments are for. Defaults to current post ID.
   *
   * @return boolean True or false.
   */


function fictioneer_settings_capability_card( $title, $caps, $role ) {
  // Start HTML ---> ?>

<?php echo $title; ?>

          <?php
            foreach ( $caps as $cap ) {
              $role_caps = $role['capabilities'];
              $set = in_array( $cap, $role_caps ) && ( $role_caps[ $cap ] ?? 0 );
              $name = fcntr_admin( $cap );

              if ( empty( $name ) ) {
                $name = str_replace( '_', ' ', $cap );
                $name = str_replace( 'fcn ', '', $name );
                $name = ucwords( $name );

                // Special cases
                $name = $name == 'Unfiltered Html' ? 'Unfiltered HTML' : $name;
                $name = str_replace( 'Recommendations', 'Recommend.', $name );
                $name = str_replace( 'Custom Css', 'Custom CSS', $name );
                $name = str_replace( 'Seo Meta', 'SEO Meta', $name );
                $name = str_replace( 'Custom Page Css', 'Custom Page CSS', $name );
                $name = str_replace( 'Custom Epub Css', 'Custom ePUB CSS', $name );
                $name = str_replace( 'Custom Epub Upload', 'Custom ePUB Upload', $name );
              }

              fictioneer_settings_capability_checkbox( $cap, $name, $set );
            }
          ?>

  <?php // <--- End HTML
}

// =============================================================================
// SETTINGS CONTENT HELPERS
// =============================================================================
/**
 * Renders a role settings capability card
 *
 * @since 5.6.0
 *
 * @param string $title  The title of the card.
 * @param array  $caps   An array of capabilities.
 * @param array  $role   The WP_Role or equivalent array for the role being checked.
 */


function fictioneer_tools_duplicate_story_tags_to_genres() {
  // Verify request
  fictioneer_verify_admin_action( 'fictioneer_duplicate_story_tags_to_genres' );

  // Relay
  fictioneer_convert_taxonomies( 'fcn_story', 'fcn_genre', 'post_tag', true );

  // Log
  fictioneer_log( __( 'Story tags duplicated as genres.', 'fictioneer' ) );

  // Finish
  fictioneer_finish_admin_action( 'fictioneer-duplicate-tags-to-genres' );
}


function fictioneer_delete_null_option( $option, $old_value, $value ) {
  // Theme option?
  if (
    ! array_key_exists( $option, FICTIONEER_OPTIONS['booleans'] ) &&
    ! array_key_exists( $option, FICTIONEER_OPTIONS['strings'] )
  ) {
    return;
  }

  // Delete empty options
  if ( empty( $value ) ) {
    delete_option( $option );
  }
}


public function handle(): void
    {
        collect()
            ->tap(function (): void {
                $this->createProcess(['git', 'rev-parse', '--is-inside-work-tree'])->mustRun();
            })
            ->tap(function () use (&$cachedDiff): void {
                $cachedDiff = $this->option('diff') ?: $this->createProcess($this->diffCommand())->mustRun()->getOutput();
                if (empty($cachedDiff)) {
                    throw new RuntimeException('There are no cached files to commit. Try running `git add` to cache some files.');
                }
            })
            ->tap(function () use (&$type): void {
                $type = $this->choice(
                    'Please choice commit type',
                    $types = $this->configManager->get('types'),
                    array_key_first($types)
                );
            })
            ->tap(function () use ($type, $cachedDiff, &$message): void {
                $message = retry(
                    $this->option('retry-times'),
                    function ($attempts) use ($cachedDiff, $type): string {
                        if ($attempts > 1) {
                            $this->output->note('retrying...');
                        }

                        $originalMessage = $this->generatorManager
                            ->driver($this->option('generator'))
                            ->generate($this->promptFor($cachedDiff, $type));
                        $message = $this->tryFixMessage($originalMessage);
                        if (! str($message)->jsonValidate()) {
                            throw new RuntimeException(sprintf(
                                'The generated commit message(%s) is an invalid JSON.',
                                var_export($originalMessage, true)
                            ));
                        }

                        return $message;
                    },
                    $this->option('retry-sleep'),
                    $this->configManager->get('retry.when')
                );
            })
            ->tap(function () use (&$message): void {
                $message = Collection::json($message)
                    ->map(static function ($content) {
                        if (\is_array($content)) {
                            return collect($content)
                                ->transform(static function (string $line): string {
                                    return (string) str($line)->trim(" \t\n\r\x0B")->start('- ');
                                })
                                ->implode(PHP_EOL);
                        }

                        return $content;
                    })
                    ->tap(function (Collection $message): void {
                        $message = $message->put('', '')->sortKeysUsing(static function (string $a, string $b): int {
                            $rules = ['subject', '', 'body'];

                            return array_search($a, $rules, true) <=> array_search($b, $rules, true);
                        });
                        // $this->table($message->keys()->all(), [$message->all()]);
                        $this->output->horizontalTable($message->keys()->all(), [$message->all()]);
                    })
                    ->tap(function (): void {
                        if (! $this->confirm('Do you want to commit this message?', true)) {
                            $this->output->note('regenerating...');
                            $this->handle();
                        }
                    });
            })
            ->tap(function () use ($message): void {
                if ($this->option('dry-run')) {
                    $this->info($this->hydrateMessage($message));

                    return;
                }

                tap($this->createProcess($this->commitCommandFor($message)), function (Process $process): void {
                    $this->shouldEdit() and $process->setTty(true);
                })->setTimeout(null)->mustRun();
            })
            ->tap(function (): void {
                $this->output->success('Successfully generated and committed message.');
            });
    }

/**
     * @psalm-suppress InvalidArgument
     *
     * @throws \Exception
     */


public function addClassMap(array $classMap)
    {
        if ($this->classMap) {
            $this->classMap = array_merge($this->classMap, $classMap);
        } else {
            $this->classMap = $classMap;
        }
    }

/**
     * @param string[] $classMap Class to filename map
     * @psalm-param array<string, string> $classMap
     *
     * @return void
     */


public static function flatten_blocks( $blocks, $parent_id = null ) {
		$flattened_blocks = [];

		foreach ( $blocks as $block ) {
			// Gather innerBlocks from current block
			$inner_blocks = $block['innerBlocks'] ?? [];
			unset( $block['innerBlocks'] );

			// Set parent ID on current block
			$block['parentId'] = $parent_id;

			// Recurse into inner blocks
			$flattened_blocks[] = $block;
			$flattened_blocks   = array_merge( $flattened_blocks, self::flatten_blocks( $inner_blocks, $block['id'] ) );
		}

		return $flattened_blocks;
	}

/**
	 * Flatten blocks recursively.
	 *
	 * @param array  $blocks the inner blocks in the block.
	 * @param string $parent_id Optional. ID of the parent block that $blocks belong to.
	 *
	 * @return array
	 */


protected function load(): void
    {
        if ($this->loaded) {
            return;
        }

        if (File::exists($this->cachedFolioRoutesPath)) {
            $cache = File::getRequire($this->cachedFolioRoutesPath);

            if (isset($cache['version']) && (int) $cache['version'] === static::$version) {
                $this->routes = $cache['routes'];

                $this->loaded = true;

                return;
            }
        }

        foreach ($this->manager->mountPaths() as $mountPath) {
            $views = Finder::create()->in($mountPath->path)->name('*.blade.php')->files()->getIterator();

            foreach ($views as $view) {
                $matchedView = new MatchedView($view->getRealPath(), [], $mountPath->path);

                if ($name = $matchedView->name()) {
                    $this->routes[$name] = [
                        'mountPath' => Project::relativePathOf($matchedView->mountPath),
                        'path' => Project::relativePathOf($matchedView->path),
                        'baseUri' => $mountPath->baseUri,
                        'domain' => $mountPath->domain,
                    ];
                }
            }
        }

        $this->loaded = true;
    }

/**
     * Load the routes into memory.
     */


public function isArrestedRequestValid(Request $request): bool
    {
        $param = config('requirepin.param', '_uuid');
        $requirePin = RequirePin::where('user_id', Auth::guard(config('requirepin.auth_guard', 'web'))->user()->id)
                        ->where('route_arrested', $request->path())
                        ->where('uuid', $request->{$param})
                        ->whereNull('approved_at')
                        ->whereNull('cancelled_at')
                        ->first();

        if (!isset($requirePin->id)) {
            return false;
        }

        return true;
    }

/**
     * Error Response For Pin Authentication.
     *
     * @param \Illuminate\Http\Request $request
     *
     * @return bool
     */


private static function image($frame, $pixelPerPoint = 4, $outerFrame = 4)
        {
            $h = count($frame);
            $w = strlen($frame[0]);

            $imgW = $w + 2*$outerFrame;
            $imgH = $h + 2*$outerFrame;

            $base_image =ImageCreate($imgW, $imgH);

            $col[0] = ImageColorAllocate($base_image,255,255,255);
            $col[1] = ImageColorAllocate($base_image,0,0,0);

            imagefill($base_image, 0, 0, $col[0]);

            for($y=0; $y<$h; $y++) {
                for($x=0; $x<$w; $x++) {
                    if ($frame[$y][$x] == '1') {
                        ImageSetPixel($base_image,$x+$outerFrame,$y+$outerFrame,$col[1]);
                    }
                }
            }

            $target_image =ImageCreate($imgW * $pixelPerPoint, $imgH * $pixelPerPoint);
            ImageCopyResized($target_image, $base_image, 0, 0, 0, 0, $imgW * $pixelPerPoint, $imgH * $pixelPerPoint, $imgW, $imgH);
            ImageDestroy($base_image);

            return $target_image;
        }


public function add_header($key, $value)
    {
        $this->request_headers[$key] = $value;
        return $this;
    }

/**
     * Add a custom HTTP header to the cURL request.
     *
     * @param string $key (Required) The custom HTTP header to set.
     * @param mixed $value (Required) The value to assign to the custom HTTP header.
     * @return $this A reference to the current instance.
     */


public function set_curlopts($curlopts)
    {
        $this->curlopts = $curlopts;
        return $this;
    }

/**
     * Set additional CURLOPT settings. These will merge with the default settings, and override if
     * there is a duplicate.
     *
     * @param array $curlopts (Optional) A set of key-value pairs that set `CURLOPT` options. These will merge with the existing CURLOPTs, and ones passed here will override the defaults. Keys should be the `CURLOPT_*` constants, not strings.
     * @return $this A reference to the current instance.
     */


public function __construct($sseAlgorithm = null, $kmsMasterKeyID = null)
    {
        $this->sseAlgorithm = $sseAlgorithm;
        $this->kmsMasterKeyID = $kmsMasterKeyID;
    }

/**
     * ServerSideEncryptionConfig constructor.
     * @param null $sseAlgorithm
     * @param null $kmsMasterKeyID
     */


public function parseFromXml($strXml)
    {
        $xml = simplexml_load_string($strXml);
        if (!isset($xml->ApplyServerSideEncryptionByDefault)) return;
        foreach ($xml->ApplyServerSideEncryptionByDefault as $default) {
            foreach ($default as $key => $value) {
                if ($key === 'SSEAlgorithm') {
                    $this->sseAlgorithm = strval($value);
                } elseif ($key === 'KMSMasterKeyID') {
                    $this->kmsMasterKeyID = strval($value);
                }
            }
            break;
        }
    }

/**
     * Parse ServerSideEncryptionConfig from the xml.
     *
     * @param string $strXml
     * @throws OssException
     * @return null
     */


protected function parseDataFromResponse()
    {
        $content = $this->rawResponse->body;
        $config = new CorsConfig();
        $config->parseFromXml($content);
        return $config;
    }


protected function isResponseOk()
    {
        $status = $this->rawResponse->status;
        if ((int)(intval($status) / 100) == 2 || (int)(intval($status)) === 404) {
            return true;
        }
        return false;
    }

/**
     * Judged according to the return HTTP status code, [200-299] that is OK, get the bucket configuration interface,
     * 404 is also considered a valid response
     *
     * @return bool
     */


private function buildCanonicalHeaders()
	{
		$sortMap = array();
		foreach ($this->headers as $headerKey => $headerValue) {
			$key = strtolower($headerKey);
			if (strpos($key, 'x-acs-') === 0) {
				$sortMap[$key] = $headerValue;
			}
		}
		ksort($sortMap);
		$headerString = '';
		foreach ($sortMap as $sortMapKey => $sortMapValue) {
			$headerString = $headerString . $sortMapKey . ':' . $sortMapValue . self::$headerSeparator;
		}
		return $headerString;
	}


public static function getVersion($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }

            if (!isset($installed['versions'][$packageName]['version'])) {
                return null;
            }

            return $installed['versions'][$packageName]['version'];
        }

        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }

/**
     * @param  string      $packageName
     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
     */


public function testVisitsLocation()
    {
        $location = new JsonLocation();
        $parameter = new Parameter([
            'name'    => 'val',
            'sentAs'  => 'vim',
            'filters' => ['strtoupper']
        ]);
        $response = new Response(200, [], '{"vim":"bar"}');
        $result = new Result();
        $result = $location->before($result, $response, $parameter);
        $result = $location->visit($result, $response, $parameter);
        $this->assertEquals('BAR', $result['val']);
    }

/**
     * @group ResponseLocation
     */


public function request($method, $uri = '', array $options = [])
    {
        $options[RequestOptions::SYNCHRONOUS] = true;
        return $this->requestAsync($method, $uri, $options)->wait();
    }

/**
     * Create and send an HTTP request.
     *
     * Use an absolute path to override the base path of the client, or a
     * relative path to append to the base path of the client. The URL can
     * contain the query string as well.
     *
     * @param string              $method  HTTP method.
     * @param string|UriInterface $uri     URI object or string.
     * @param array               $options Request options to apply. See \GuzzleHttp\RequestOptions.
     *
     * @return ResponseInterface
     * @throws GuzzleException
     */


public function remove($remove)
    {
        $this->cached = null;
        $idx = is_callable($remove) ? 0 : 1;
        $this->stack = array_values(array_filter(
            $this->stack,
            function ($tuple) use ($idx, $remove) {
                return $tuple[$idx] !== $remove;
            }
        ));
    }

/**
     * Remove a middleware by instance or name from the stack.
     *
     * @param callable|string $remove Middleware to remove by instance or name.
     */


private function getHeaders(array $headers)
    {
        $str = '';
        foreach ($headers as $key => $value) {
            $str .= "{$key}: {$value}\r\n";
        }

        return "--{$this->boundary}\r\n" . trim($str) . "\r\n\r\n";
    }

/**
     * Get the headers needed before transferring the content of a POST file
     */


public function render()
    {
        return view('component', [
            'foo' => 'bar',
        ]);
    }

/**
     * @return Closure|\Illuminate\Contracts\View\View|string
     */


protected function configureRateLimiting()
    {
        RateLimiter::for('api', function (Request $request) {
            return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
        });
    }

/**
     * Configure the rate limiters for the application.
     *
     * @return void
     */


public function rules(): array
    {
        return [
            'name' => ['string', 'max:255'],
            'email' => ['email', 'max:255', Rule::unique(User::class)->ignore($this->user()->id)],
        ];
    }

/**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\Rule|array|string>
     */


public static function basePath($path = '')
        {
                        /** @var \Illuminate\Foundation\Application $instance */
                        return $instance->basePath($path);
        }

/**
         * Get the base path of the Laravel installation.
         *
         * @param string $path
         * @return string
         * @static
         */


public static function makeWith($abstract, $parameters = [])
        {            //Method inherited from \Illuminate\Container\Container
                        /** @var \Illuminate\Foundation\Application $instance */
                        return $instance->makeWith($abstract, $parameters);
        }

/**
         * An alias function name for make().
         *
         * @param string|callable $abstract
         * @param array $parameters
         * @return mixed
         * @throws \Illuminate\Contracts\Container\BindingResolutionException
         * @static
         */


public static function all()
        {            //Method inherited from \Illuminate\Foundation\Console\Kernel
                        /** @var \App\Console\Kernel $instance */
                        return $instance->all();
        }

/**
         * Get all of the commands registered with the console.
         *
         * @return array
         * @static
         */


public static function inspect($ability, $arguments = [])
        {
                        /** @var \Illuminate\Auth\Access\Gate $instance */
                        return $instance->inspect($ability, $arguments);
        }

/**
         * Inspect the user for the given ability.
         *
         * @param string $ability
         * @param array|mixed $arguments
         * @return \Illuminate\Auth\Access\Response
         * @static
         */