using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;

namespace Ounibo.Catalog.Core
{
    /// <summary>参数化选型结果: 标准档位命中 → Variant; 非标 → 模板生成的编码+参数 (无 STEP)。</summary>
    public class ParamResult
    {
        public string ModelCode;
        public Dictionary<string, object> Params;
        public bool IsStandard;
        public Variant Variant;    // IsStandard 时非空 (即 CSV 网格行)
        public string Error;       // 非法原因键: param=参数不全 / domain=域外 / rule=违背规则 / template=模板错误 (上层本地化)
    }

    /// <summary>
    /// 参数化引擎 (一期, 2026-08-25):
    /// 参数域校验 + 型号编码模板渲染 + 非标组合生成 + 最近标准档位。
    /// 与网页 app.js 的参数化函数语义一一对应 (同一份规则 JSON + 同一份测试向量 parametric-vectors.json 对拍)。
    /// </summary>
    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); }

        /// <summary>模板渲染: {field} 或 {field:padN} (padN=前补零到 N 位)。未知字段/缺值/非法 token → null。</summary>
        public static string RenderCode(string template, Dictionary<string, object> 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();
        }

        /// <summary>参数取值在其域内: enum → values 命中; range → [min,max] 且按 step 对齐 (相对 min 的整数倍)。</summary>
        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<object>()).Any(x => Eq(x, v));
        }

        /// <summary>组合校验: 参数齐全 + 域 + 规则。返回错误键 (null=合法)。</summary>
        public static string ValidateCombo(Series s, Dictionary<string, object> sel)
        {
            foreach (var p in s.parameters ?? new List<ParameterDef>())
            {
                object v;
                if (!sel.TryGetValue(p.code, out v)) return "param";
                if (!DomainValid(p, v)) return "domain";
            }
            var cfg = new Configurator(s);
            var fake = new Variant { modelCode = "", @params = sel };
            if (cfg.RuleViolated(fake)) return "rule";
            return null;
        }

        /// <summary>
        /// 部分选择校验 (联动用): 只校验已选参数的域 + 规则 (Then 参数未选不判违背)。
        /// 与 Configurator.RuleViolatedPartial 配套, 与网页 validatePartial 同语义。
        /// </summary>
        public static string ValidatePartial(Series s, Dictionary<string, object> sel)
        {
            foreach (var p in s.parameters ?? new List<ParameterDef>())
            {
                object v;
                if (!sel.TryGetValue(p.code, out v)) continue;   // 未选参数不校验
                if (!DomainValid(p, v)) return "domain";
            }
            var cfg = new Configurator(s);
            if (cfg.RuleViolatedPartial(sel)) return "rule";
            return null;
        }

        /// <summary>生成结果: 网格命中 → Variant (IsStandard); 否则非标 (模板生成编码, 无 STEP)。不合法 → Error 非空。</summary>
        public static ParamResult Generate(Series s, Configurator cfg, Dictionary<string, object> sel)
        {
            var r = new ParamResult { Params = sel };
            if (sel.Count < (s.parameters ?? new List<ParameterDef>()).Count) { r.Error = "param"; return r; }
            r.Error = ValidateCombo(s, sel);
            if (r.Error != null) return r;
            r.ModelCode = RenderCode(s.modelCodeTemplate, sel);
            if (r.ModelCode == null) { r.Error = "template"; return r; }
            var v = cfg.ValidVariants().FirstOrDefault(x => string.Equals(x.modelCode, r.ModelCode, StringComparison.OrdinalIgnoreCase));
            if (v != null) { r.IsStandard = true; r.Variant = v; }
            return r;
        }

        /// <summary>
        /// 最近标准档位: 全部有效网格变体按参数距离排序 — range 参数按数值差累加, 枚举不等记大惩罚 (1e6);
        /// 距离相同取编码字典序小者 (与网页同语义)。
        /// </summary>
        public static Variant NearestStandard(Series s, Configurator cfg, Dictionary<string, object> sel)
        {
            Variant best = null;
            double bestDist = double.MaxValue;
            foreach (var v in cfg.ValidVariants())
            {
                double dist = 0;
                foreach (var p in s.parameters ?? new List<ParameterDef>())
                {
                    object a, b;
                    if (!sel.TryGetValue(p.code, out a) || !v.@params.TryGetValue(p.code, out b)) continue;
                    if (p.type == "range") dist += Math.Abs(D(a) - D(b));
                    else if (!Eq(a, b)) dist += 1e6;
                }
                if (best == null || dist < bestDist - 1e-9 ||
                    (Math.Abs(dist - bestDist) < 1e-9 && string.CompareOrdinal(v.modelCode, best.modelCode) < 0))
                { best = v; bestDist = dist; }
            }
            return best;
        }
    }
}
