// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
using System;
using System.Collections.Generic;
using GFramework.Core.Abstractions.Logging;
namespace GFramework.Godot.Logging;
///
/// Represents one immutable Godot logger configuration snapshot.
///
///
/// A snapshot combines mode-specific , an optional default log level, and category overrides.
/// Category matching is ordinal and deterministic: exact matches win first, then the longest dotted prefix such as
/// Game.Services for Game.Services.Inventory, and finally is used when
/// present.
///
internal sealed class GodotLoggerSettings
{
private readonly IReadOnlyDictionary _loggerLevels;
///
/// Gets the default settings snapshot used when no configuration file is available.
///
public static GodotLoggerSettings Default { get; } = new(new GodotLoggerOptions());
///
/// Creates a settings snapshot from normalized options and optional category thresholds.
///
/// The formatting and mode options for this snapshot.
/// The optional fallback level used when no category override matches.
/// Exact category names or dotted prefixes mapped to minimum levels.
public GodotLoggerSettings(
GodotLoggerOptions options,
LogLevel? defaultLogLevel = null,
IReadOnlyDictionary? loggerLevels = null)
{
Options = (options ?? throw new ArgumentNullException(nameof(options))).CreateNormalizedCopy();
DefaultLogLevel = defaultLogLevel;
_loggerLevels = loggerLevels ?? new Dictionary(StringComparer.Ordinal);
}
///
/// Gets the optional fallback minimum level for categories without exact or prefix overrides.
///
public LogLevel? DefaultLogLevel { get; }
///
/// Gets normalized rendering and mode options for this snapshot.
///
public GodotLoggerOptions Options { get; }
///
/// Gets exact and dotted-prefix category level overrides.
///
///
/// Keys are interpreted with semantics. A key only matches a child category
/// when the category starts with the key plus a dot, which prevents Game.Service from matching
/// Game.Services accidentally.
///
public IReadOnlyDictionary LoggerLevels => _loggerLevels;
///
/// Creates a settings snapshot from options without any category overrides.
///
/// The options to normalize and wrap.
/// A settings snapshot that relies only on the option-level minimum level.
public static GodotLoggerSettings FromOptions(GodotLoggerOptions options)
{
return new GodotLoggerSettings(options);
}
///
/// Calculates the effective minimum level for a category.
///
/// The logger category name.
/// The provider-level floor captured by the logger.
/// The strictest level selected from options, provider floor, and category configuration.
///
/// The merge starts with and
/// , then applies when it returns a
/// value. is used at each step so configuration can only make a logger
/// stricter, never more verbose than the active floor.
///
public LogLevel GetEffectiveMinLevel(string categoryName, LogLevel providerMinLevel)
{
ArgumentNullException.ThrowIfNull(categoryName);
var effective = Max(Options.GetEffectiveMinLevel(), providerMinLevel);
var configuredLevel = GetConfiguredMinLevel(categoryName);
return configuredLevel.HasValue ? Max(effective, configuredLevel.Value) : effective;
}
///
/// Finds the configured category level using exact match, longest dotted-prefix match, then default fallback.
///
/// The category to resolve.
/// The configured level, or null when no default or override applies.
private LogLevel? GetConfiguredMinLevel(string categoryName)
{
// Exact category configuration is the most specific and avoids unnecessary prefix scans.
if (_loggerLevels.TryGetValue(categoryName, out var exactLevel))
{
return exactLevel;
}
var bestMatchLength = -1;
LogLevel? bestMatchLevel = DefaultLogLevel;
foreach (var pair in _loggerLevels)
{
// The dotted boundary keeps sibling categories from matching by raw string prefix alone.
if (!categoryName.StartsWith(pair.Key + ".", StringComparison.Ordinal))
{
continue;
}
if (pair.Key.Length <= bestMatchLength)
{
continue;
}
bestMatchLength = pair.Key.Length;
bestMatchLevel = pair.Value;
}
return bestMatchLevel;
}
///
/// Returns the stricter of two log levels.
///
/// The first level.
/// The second level.
/// The level with the higher severity ordering.
private static LogLevel Max(LogLevel left, LogLevel right)
{
return left > right ? left : right;
}
}