// <auto-generated />
#nullable enable

namespace GFramework.Game.Config.Generated;

/// <summary>
///     Provides a project-level catalog for every config table generated from the current consumer project's schemas.
///     Use this entry point when you want the C# runtime bootstrap path to register all generated tables without repeating one call per schema.
/// </summary>
public static class GeneratedConfigCatalog
{
    /// <summary>
    ///     Describes one generated config table so bootstrap code can enumerate generated domains without re-parsing schema files at runtime.
    /// </summary>
    public readonly struct TableMetadata
    {
        /// <summary>
        ///     Initializes one generated table metadata entry.
        /// </summary>
        /// <param name="configDomain">Logical config domain derived from the schema base name.</param>
        /// <param name="tableName">Runtime registration name.</param>
        /// <param name="configRelativePath">Relative YAML directory path.</param>
        /// <param name="schemaRelativePath">Relative schema file path.</param>
        public TableMetadata(
            string configDomain,
            string tableName,
            string configRelativePath,
            string schemaRelativePath)
        {
            ConfigDomain = configDomain ?? throw new global::System.ArgumentNullException(nameof(configDomain));
            TableName = tableName ?? throw new global::System.ArgumentNullException(nameof(tableName));
            ConfigRelativePath = configRelativePath ?? throw new global::System.ArgumentNullException(nameof(configRelativePath));
            SchemaRelativePath = schemaRelativePath ?? throw new global::System.ArgumentNullException(nameof(schemaRelativePath));
        }

        /// <summary>
        ///     Gets the logical config domain derived from the schema base name.
        /// </summary>
        public string ConfigDomain { get; }

        /// <summary>
        ///     Gets the runtime registration name used by <see cref="global::GFramework.Game.Config.YamlConfigLoader" />.
        /// </summary>
        public string TableName { get; }

        /// <summary>
        ///     Gets the relative directory that stores YAML files for the generated config table.
        /// </summary>
        public string ConfigRelativePath { get; }

        /// <summary>
        ///     Gets the relative schema file path collected by the source generator.
        /// </summary>
        public string SchemaRelativePath { get; }
    }

    /// <summary>
    ///     Gets metadata for every generated config table in the current consumer project.
    /// </summary>
    public static global::System.Collections.Generic.IReadOnlyList<TableMetadata> Tables { get; } = global::System.Array.AsReadOnly(new TableMetadata[]
    {
        new(
            MonsterConfigBindings.Metadata.ConfigDomain,
            MonsterConfigBindings.Metadata.TableName,
            MonsterConfigBindings.Metadata.ConfigRelativePath,
            MonsterConfigBindings.Metadata.SchemaRelativePath),
    });

    /// <summary>
    ///     Resolves one generated relative config path against the caller-supplied config root directory.
    /// </summary>
    /// <param name="configRootPath">Absolute or workspace-local config root directory.</param>
    /// <param name="relativePath">Generated relative config or schema path.</param>
    /// <returns>The combined absolute path.</returns>
    /// <exception cref="global::System.ArgumentException">When <paramref name="configRootPath"/> is null, empty, or whitespace.</exception>
    /// <exception cref="global::System.ArgumentNullException">When <paramref name="relativePath"/> is null.</exception>
    internal static string ResolveAbsolutePath(string configRootPath, string relativePath)
    {
        if (string.IsNullOrWhiteSpace(configRootPath))
        {
            throw new global::System.ArgumentException("Config root path cannot be null or whitespace.", nameof(configRootPath));
        }

        if (relativePath is null)
        {
            throw new global::System.ArgumentNullException(nameof(relativePath));
        }

        var normalizedRelativePath = relativePath.Replace('/', global::System.IO.Path.DirectorySeparatorChar)
            .Replace('\\', global::System.IO.Path.DirectorySeparatorChar);
        return global::System.IO.Path.Combine(configRootPath, normalizedRelativePath);
    }

    /// <summary>
    ///     Tries to resolve generated table metadata by runtime registration name.
    /// </summary>
    /// <param name="tableName">Runtime registration name.</param>
    /// <param name="metadata">Resolved generated table metadata when the registration name exists; otherwise the default value.</param>
    /// <returns><see langword="true" /> when the registration name belongs to a generated config table; otherwise <see langword="false" />.</returns>
    public static bool TryGetByTableName(string tableName, out TableMetadata metadata)
    {
        if (tableName is null)
        {
            throw new global::System.ArgumentNullException(nameof(tableName));
        }

        if (string.Equals(tableName, MonsterConfigBindings.Metadata.TableName, global::System.StringComparison.Ordinal))
        {
            metadata = Tables[0];
            return true;
        }

        metadata = default;
        return false;
    }

    /// <summary>
    ///     Resolves the generated table metadata entries that belong to the specified logical config domain.
    /// </summary>
    /// <param name="configDomain">Logical config domain derived from the schema base name.</param>
    /// <returns>A deterministic metadata snapshot for the requested config domain, or an empty list when no generated table belongs to that domain.</returns>
    /// <exception cref="global::System.ArgumentNullException">When <paramref name="configDomain"/> is null.</exception>
    public static global::System.Collections.Generic.IReadOnlyList<TableMetadata> GetTablesInConfigDomain(string configDomain)
    {
        if (configDomain is null)
        {
            throw new global::System.ArgumentNullException(nameof(configDomain));
        }

        var matchedTables = new global::System.Collections.Generic.List<TableMetadata>();
        foreach (var metadata in Tables)
        {
            if (string.Equals(metadata.ConfigDomain, configDomain, global::System.StringComparison.Ordinal))
            {
                matchedTables.Add(metadata);
            }
        }

        return matchedTables.Count == 0 ? global::System.Array.Empty<TableMetadata>() : matchedTables.ToArray();
    }

    /// <summary>
    ///     Resolves the generated table metadata entries that aggregate registration would currently include under the supplied filters.
    /// </summary>
    /// <param name="options">Optional aggregate registration filters and comparer overrides. When null, every generated table remains eligible.</param>
    /// <returns>A deterministic metadata snapshot in the same order used by <see cref="GeneratedConfigRegistrationExtensions.RegisterAllGeneratedConfigTables(global::GFramework.Game.Config.YamlConfigLoader, GeneratedConfigRegistrationOptions?)" />.</returns>
    public static global::System.Collections.Generic.IReadOnlyList<TableMetadata> GetTablesForRegistration(GeneratedConfigRegistrationOptions? options = null)
    {
        var matchedTables = new global::System.Collections.Generic.List<TableMetadata>();
        foreach (var metadata in Tables)
        {
            if (MatchesRegistrationOptions(metadata, options))
            {
                matchedTables.Add(metadata);
            }
        }

        return matchedTables.Count == 0 ? global::System.Array.Empty<TableMetadata>() : matchedTables.ToArray();
    }

    /// <summary>
    ///     Evaluates whether one generated table metadata entry remains eligible under the supplied aggregate registration filters.
    /// </summary>
    /// <param name="metadata">Generated table metadata under evaluation.</param>
    /// <param name="options">Optional aggregate registration filters and comparer overrides. When null, the metadata entry is always eligible.</param>
    /// <returns><see langword="true" /> when the generated table would be included by aggregate registration; otherwise <see langword="false" />.</returns>
    public static bool MatchesRegistrationOptions(
        TableMetadata metadata,
        GeneratedConfigRegistrationOptions? options)
    {
        if (options is null)
        {
            return true;
        }

        // Apply cheap generated allow-lists before invoking the optional caller predicate so startup diagnostics stay aligned with real registration.
        if (!MatchesOptionalAllowList(options.IncludedConfigDomains, metadata.ConfigDomain))
        {
            return false;
        }

        if (!MatchesOptionalAllowList(options.IncludedTableNames, metadata.TableName))
        {
            return false;
        }

        return options.TableFilter?.Invoke(metadata) ?? true;
    }

    /// <summary>
    ///     Treats a null or empty allow-list as an unrestricted match, and otherwise performs ordinal string comparison against the generated metadata value.
    /// </summary>
    /// <param name="allowedValues">Optional caller-supplied allow-list.</param>
    /// <param name="candidate">Generated metadata value being evaluated.</param>
    /// <returns><see langword="true" /> when the value should remain eligible for registration; otherwise <see langword="false" />.</returns>
    private static bool MatchesOptionalAllowList(
        global::System.Collections.Generic.IReadOnlyCollection<string>? allowedValues,
        string candidate)
    {
        if (allowedValues is null || allowedValues.Count == 0)
        {
            return true;
        }

        foreach (var allowedValue in allowedValues)
        {
            if (allowedValue is not null &&
                string.Equals(allowedValue, candidate, global::System.StringComparison.Ordinal))
            {
                return true;
            }
        }

        return false;
    }
}

/// <summary>
///     Captures optional per-table registration overrides for the generated aggregate registration entry point.
/// </summary>
public sealed class GeneratedConfigRegistrationOptions
{
    /// <summary>
    ///     Gets or sets the optional allow-list of generated config domains that aggregate registration should include. When null or empty, every generated domain remains eligible.
    /// </summary>
    public global::System.Collections.Generic.IReadOnlyCollection<string>? IncludedConfigDomains { get; init; }

    /// <summary>
    ///     Gets or sets the optional allow-list of runtime table names that aggregate registration should include. When null or empty, every generated table remains eligible.
    /// </summary>
    public global::System.Collections.Generic.IReadOnlyCollection<string>? IncludedTableNames { get; init; }

    /// <summary>
    ///     Gets or sets the optional predicate that can reject individual generated table metadata entries after allow-list filtering has passed.
    /// </summary>
    public global::System.Predicate<GeneratedConfigCatalog.TableMetadata>? TableFilter { get; init; }

    /// <summary>
    ///     Gets or sets the optional key comparer forwarded to MonsterConfigBindings.RegisterMonsterTable using <c>global::System.Collections.Generic.IEqualityComparer&lt;int&gt;?</c> when aggregate registration runs.
    /// </summary>
    public global::System.Collections.Generic.IEqualityComparer<int>? MonsterComparer { get; init; }
}

/// <summary>
///     Provides a single extension method that registers every generated config table discovered in the current consumer project.
/// </summary>
public static class GeneratedConfigRegistrationExtensions
{
    /// <summary>
    ///     Registers all generated config tables using schema-derived conventions so bootstrap code can stay one-line even as schemas grow.
    /// </summary>
    /// <param name="loader">Target YAML config loader.</param>
    /// <returns>The same loader instance after all generated table registrations have been applied.</returns>
    /// <exception cref="global::System.ArgumentNullException">When <paramref name="loader"/> is null.</exception>
    public static global::GFramework.Game.Config.YamlConfigLoader RegisterAllGeneratedConfigTables(
        this global::GFramework.Game.Config.YamlConfigLoader loader)
    {
        if (loader is null)
        {
            throw new global::System.ArgumentNullException(nameof(loader));
        }

        return RegisterAllGeneratedConfigTables(loader, options: null);
    }

    /// <summary>
    ///     Registers all generated config tables while preserving optional per-table overrides such as custom key comparers.
    /// </summary>
    /// <param name="loader">Target YAML config loader.</param>
    /// <param name="options">Optional per-table overrides for aggregate registration; when null, all tables use their default comparer behavior.</param>
    /// <returns>The same loader instance after all generated table registrations have been applied.</returns>
    /// <exception cref="global::System.ArgumentNullException">When <paramref name="loader"/> is null.</exception>
    public static global::GFramework.Game.Config.YamlConfigLoader RegisterAllGeneratedConfigTables(
        this global::GFramework.Game.Config.YamlConfigLoader loader,
        GeneratedConfigRegistrationOptions? options)
    {
        if (loader is null)
        {
            throw new global::System.ArgumentNullException(nameof(loader));
        }

        var effectiveOptions = options ?? new GeneratedConfigRegistrationOptions();

        if (GeneratedConfigCatalog.MatchesRegistrationOptions(GeneratedConfigCatalog.Tables[0], effectiveOptions))
        {
            loader.RegisterMonsterTable(effectiveOptions.MonsterComparer);
        }

        return loader;
    }
}
