using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
namespace Ounibo.Catalog.Core
{
/// 参数化选型结果: 标准档位命中 → Variant; 非标 → 模板生成的编码+参数 (无 STEP)。
public class ParamResult
{
public string ModelCode;
public Dictionary Params;
public bool IsStandard;
public Variant Variant; // IsStandard 时非空 (即 CSV 网格行)
public string Error; // 非法原因键: param=参数不全 / domain=域外 / rule=违背规则 / template=模板错误 (上层本地化)
}
///
/// 参数化引擎 (一期, 2026-08-25):
/// 参数域校验 + 型号编码模板渲染 + 非标组合生成 + 最近标准档位。
/// 与网页 app.js 的参数化函数语义一一对应 (同一份规则 JSON + 同一份测试向量 parametric-vectors.json 对拍)。
///
public static class ParametricEngine
{
public static bool IsParametric(Series s) { return s != null && s.mode == "parametric"; }
static string S(object o) { return o == null ? "" : Convert.ToString(o, CultureInfo.InvariantCulture); }
static bool Eq(object a, object b) { return string.Equals(S(a), S(b), StringComparison.OrdinalIgnoreCase); }
static double D(object o) { return Convert.ToDouble(o, CultureInfo.InvariantCulture); }
/// 模板渲染: {field} 或 {field:padN} (padN=前补零到 N 位)。未知字段/缺值/非法 token → null。
public static string RenderCode(string template, Dictionary p)
{
if (string.IsNullOrEmpty(template)) return null;
var sb = new StringBuilder();
int i = 0;
while (i < template.Length)
{
var c = template[i];
if (c == '{')
{
var close = template.IndexOf('}', i + 1);
if (close < 0) return null;
var token = template.Substring(i + 1, close - i - 1);
var parts = token.Split(':');
var field = parts[0].Trim();
object val;
if (!p.TryGetValue(field, out val)) return null;
var sv = S(val);
if (parts.Length > 1)
{
int pad;
var padTok = parts[1].Trim();
if (padTok.StartsWith("pad", StringComparison.OrdinalIgnoreCase)) padTok = padTok.Substring(3);
if (!int.TryParse(padTok, out pad) || pad < 1) return null;
sv = sv.PadLeft(pad, '0');
}
sb.Append(sv);
i = close + 1;
}
else { sb.Append(c); i++; }
}
return sb.ToString();
}
/// 参数取值在其域内: enum → values 命中; range → [min,max] 且按 step 对齐 (相对 min 的整数倍)。
public static bool DomainValid(ParameterDef p, object v)
{
if (v == null) return false;
if (p.type == "range")
{
double d;
if (!double.TryParse(S(v), NumberStyles.Float, CultureInfo.InvariantCulture, out d)) return false;
if (p.min != null && d < p.min.Value) return false;
if (p.max != null && d > p.max.Value) return false;
var st = p.step ?? 1;
var baseV = p.min ?? 0;
if (st > 0 && Math.Abs(d - baseV - Math.Round((d - baseV) / st) * st) > 1e-6) return false;
return true;
}
return (p.values ?? new List