using Admin.NET.Plugin.AiDOP.Entity.S8;
using Admin.NET.Plugin.AiDOP.Infrastructure;
using Admin.NET.Plugin.AiDOP.Infrastructure.S8;
namespace Admin.NET.Plugin.AiDOP.Service.S8;
///
/// S8-STEP6C-CFG-NOTIFY-FIX-AND-CERT-1:通知分层「平台默认 / 工厂覆盖」的**单一** merge 契约。
///
/// 覆盖粒度 = (scene_code, 归一 severity, 归一 level_code),即逐层覆盖。
///
/// 修正前(S8-STEP6C-CFG-NOTIFY-CLOSURE-1 及更早)覆盖粒度是整个 (scene, severity) 键:
/// resolver 先按 (tenant,factory,scene,severity) 精确查,**仅当该键 0 行**才回落 (0,0)。
/// 后果是工厂只要为某一层建一行(例如只配 L2_MANAGER),平台默认在该 scene+severity 下的
/// **其余层级(L1/L3)会整体消失**——工厂本意是「补一层」,实际却是「换掉整组」,
/// 且该副作用在页面上不可见。本类把粒度收敛到 level,使「补一层」就只影响那一层。
///
/// ⚠️ resolver 与配置页 ListAsync 必须都走 。
/// 两处一旦各自实现,就会出现「页面显示一种生效配置、运行时按另一种派发」的静默背离
/// (已实测过一次:resolver 用 (0,0) 基线派发通知,而配置页只查精确租户 → 页面空表、通知照发)。
///
public static class S8NotificationLayerMerge
{
/// 层级归一:去空白 + 大写。历史脏数据存在 level_code = '' 的行,归一后自成一组,不与合法层级混淆。
public static string NormalizeLevel(string? levelCode) =>
(levelCode ?? string.Empty).Trim().ToUpperInvariant();
/// merge 键:(scene_code, 归一 severity, 归一 level_code)。
public static (string Scene, string Severity, string Level) KeyOf(AdoS8NotificationLayer x) =>
(x.SceneCode ?? string.Empty, S8SeverityCode.Normalize(x.Severity), NormalizeLevel(x.LevelCode));
///
/// 从「平台默认行 ∪ 本工厂行」的候选集算出生效集合:逐 (scene, severity, level) 取工厂优先,
/// 该层无工厂行则回落平台默认。
///
/// 排序为稳定确定序:scene → severity → level → id。
/// 末位 Id 是必要的——DB 侧无唯一索引(唯一性仅由 service 层 AnyAsync 前置检查保证),
/// 同键重复行若依赖 DB 返回序,派发顺序会在不同查询计划下漂移。
///
public static List Effective(IEnumerable candidates) =>
S8ConfigScope.EffectiveRows(candidates, x => x.TenantId, x => x.FactoryId, KeyOf)
.OrderBy(x => x.SceneCode, StringComparer.Ordinal)
.ThenBy(x => S8SeverityCode.Normalize(x.Severity), StringComparer.Ordinal)
.ThenBy(x => NormalizeLevel(x.LevelCode), StringComparer.Ordinal)
.ThenBy(x => x.Id)
.ToList();
}