using System.Collections.Generic; using System.Globalization; using System.Linq; namespace Sozsoft.Platform.Editors; /// /// Tek bir Script Builder kurali. uzerindeki tarif /// metotlariyla olusturulur, akici metotlarla kosul ve tetikleyici eklenir. /// public sealed class EditorScriptRule { /// Zorunlu parametreler. Dialogdaki isRuleComplete kontrolunun karsiligi. private static readonly Dictionary RequiredParams = new Dictionary { ["multiply"] = new[] { "left", "right", "target" }, ["subtract"] = new[] { "left", "right", "target" }, ["percent"] = new[] { "amount", "rate", "target" }, ["sum"] = new[] { "target" }, ["formula"] = new[] { "target", "expression" }, ["today"] = new[] { "target" }, ["days"] = new[] { "start", "end", "target" }, ["hours"] = new[] { "start", "end", "target" }, ["copy"] = new[] { "source", "target" }, ["setValue"] = new[] { "target", "text" }, ["clear"] = new string[0], ["readOnly"] = new[] { "target" }, ["notify"] = new[] { "message" }, ["ask"] = new[] { "message" }, ["openUrl"] = new[] { "url" }, ["apiToField"] = new[] { "target", "url" }, ["location"] = new[] { "lat", "lng" }, ["custom"] = new[] { "code" } }; /// Alan listesi isteyen tarifler. private static readonly HashSet FieldListRecipes = new HashSet { "sum", "clear" }; /// /// Grup bazli varsayilan tetikleyici. Deger ureten kurallar form acilirken de /// calisir; kullaniciya bir sey gosteren ya da dis cagri yapan kurallar /// yalnizca deger degistiginde calisir. /// private static readonly Dictionary GroupDefaultTrigger = new Dictionary { ["multiply"] = EditorScriptTrigger.Both, ["subtract"] = EditorScriptTrigger.Both, ["percent"] = EditorScriptTrigger.Both, ["sum"] = EditorScriptTrigger.Both, ["formula"] = EditorScriptTrigger.Both, ["today"] = EditorScriptTrigger.Both, ["days"] = EditorScriptTrigger.Both, ["hours"] = EditorScriptTrigger.Both, ["copy"] = EditorScriptTrigger.Both, ["setValue"] = EditorScriptTrigger.Both, ["clear"] = EditorScriptTrigger.Both, ["readOnly"] = EditorScriptTrigger.Both, ["notify"] = EditorScriptTrigger.Change, ["ask"] = EditorScriptTrigger.Change, ["openUrl"] = EditorScriptTrigger.Change, ["apiToField"] = EditorScriptTrigger.Change, ["location"] = EditorScriptTrigger.Change, ["custom"] = EditorScriptTrigger.Change }; private readonly string _recipe; private readonly List> _params = new List>(); private readonly List _fields = new List(); private readonly List _conditions = new List(); private string _id; private bool _anyCondition; private EditorScriptTrigger _trigger = EditorScriptTrigger.Default; internal EditorScriptRule(string recipe) { _recipe = recipe; } /* ---------------------------------------------------------------- * * Akici yapilandirma * ---------------------------------------------------------------- */ /// Kural yalnizca verilen kosullarin hepsi saglandiginda calisir. public EditorScriptRule When(params EditorScriptCondition[] conditions) => AddConditions(conditions, false); /// Kural verilen kosullardan herhangi biri saglandiginda calisir. public EditorScriptRule WhenAny(params EditorScriptCondition[] conditions) => AddConditions(conditions, true); /// Yalnizca alanin degeri degistiginde calissin. public EditorScriptRule OnChange() => WithTrigger(EditorScriptTrigger.Change); /// Yalnizca form acilirken calissin (varsayilan deger uretmek icin). public EditorScriptRule OnOpen() => WithTrigger(EditorScriptTrigger.Open); /// Hem acilista hem deger degistiginde calissin. public EditorScriptRule OnOpenAndChange() => WithTrigger(EditorScriptTrigger.Both); /// Tetikleyiciyi acikca belirler. Default ise tarifin grup varsayilani kullanilir. public EditorScriptRule WithTrigger(EditorScriptTrigger trigger) { _trigger = trigger; return this; } /// /// Kural kimligini sabitler. Verilmezse Build sirasinda tarif adi ve sira /// numarasindan uretilir (ornek: "multiply_1"). /// public EditorScriptRule WithId(string id) { _id = id; return this; } /// Tek kurallik script metni. Birden fazla kural icin kullanin. public string ToScript() => EditorScript.Build(this); /// Kurali dogrudan EditorScript alanina atanabilir hale getirir. public static implicit operator string(EditorScriptRule rule) => rule?.ToScript() ?? string.Empty; /* ---------------------------------------------------------------- * * Ic kullanim * ---------------------------------------------------------------- */ internal EditorScriptRule Set(string key, string value) { // Bos parametreler dialogda da yazilmaz; JSON'un birebir esit kalmasi icin atlanir. if (!string.IsNullOrEmpty(value)) { _params.Add(new KeyValuePair(key, value)); } return this; } internal EditorScriptRule SetNumber(string key, int? value) => value.HasValue ? Set(key, value.Value.ToString(CultureInfo.InvariantCulture)) : this; internal EditorScriptRule SetFields(IEnumerable fields) { if (fields != null) { _fields.AddRange(fields.Where(field => !string.IsNullOrEmpty(field))); } return this; } internal void EnsureId(int order) { if (string.IsNullOrEmpty(_id)) { _id = _recipe + "_" + order.ToString(CultureInfo.InvariantCulture); } } internal EditorScriptTrigger EffectiveTrigger() { if (_trigger != EditorScriptTrigger.Default) { return _trigger; } return GroupDefaultTrigger.TryGetValue(_recipe, out var trigger) ? trigger : EditorScriptTrigger.Change; } internal bool IsComplete() { if (!RequiredParams.TryGetValue(_recipe, out var required)) { return false; } if (_conditions.Any(condition => !condition.IsComplete())) { return false; } if (FieldListRecipes.Contains(_recipe) && _fields.Count == 0) { return false; } return required.All(key => _params.Any(pair => pair.Key == key && pair.Value.Trim().Length > 0)); } internal string ToJson() { var conditions = EffectiveConditions(); var json = "{\"id\":" + EditorScript.Quote(_id) + ",\"recipe\":" + EditorScript.Quote(_recipe) + ",\"condition\":" + conditions[0].ToJson(); if (conditions.Count > 1) { json += ",\"conditions\":[" + string.Join(",", conditions.Skip(1).Select(c => c.ToJson())) + "]" + ",\"join\":" + EditorScript.Quote(_anyCondition ? "or" : "and"); } json += ",\"params\":{" + string.Join(",", _params.Select(pair => EditorScript.Quote(pair.Key) + ":" + EditorScript.Quote(pair.Value))) + "}"; json += ",\"fields\":[" + string.Join(",", _fields.Select(EditorScript.Quote)) + "]"; if (_trigger != EditorScriptTrigger.Default) { json += ",\"trigger\":" + EditorScript.Quote(TriggerName(_trigger)); } return json + "}"; } /// /// Kuralin tek satirlik kodu. runsOnOpen yalnizca script'in tamami acilista da /// calisiyorsa true gelir; o durumda her kuralin ne zaman calisacagi isReady /// ile ayristirilir. /// internal string ToCode(bool runsOnOpen) { var body = BuildBody(); if (string.IsNullOrEmpty(body)) { return string.Empty; } var guards = new List(); if (runsOnOpen) { var trigger = EffectiveTrigger(); if (trigger == EditorScriptTrigger.Change) { guards.Add("!isReady"); } else if (trigger == EditorScriptTrigger.Open) { guards.Add("isReady"); } } // readOnly'de kosul dogrudan aksiyonun argumanidir, if ile sarmalanmaz. if (_recipe != "readOnly") { var condition = ConditionExpression(); if (!string.IsNullOrEmpty(condition)) { guards.Add(condition); } } return guards.Count > 0 ? "if (" + string.Join(" && ", guards) + ") " + body : body; } private List EffectiveConditions() => _conditions.Count > 0 ? _conditions : new List { EditorScriptCondition.Always() }; private string ConditionExpression() { var parts = EffectiveConditions() .Select(condition => condition.ToExpression()) .Where(expression => !string.IsNullOrEmpty(expression)) .ToList(); if (parts.Count == 0) { return string.Empty; } if (parts.Count == 1) { return parts[0]; } return string.Join(_anyCondition ? " || " : " && ", parts.Select(part => "(" + part + ")")); } private EditorScriptRule AddConditions(EditorScriptCondition[] conditions, bool any) { if (conditions != null) { _conditions.AddRange(conditions.Where(condition => condition != null)); } if (_conditions.Count > 1) { _anyCondition = any; } return this; } private string Param(string key) { var match = _params.FirstOrDefault(pair => pair.Key == key); return match.Key == null ? string.Empty : match.Value; } private string Digits() => Param("digits").Length > 0 ? Param("digits") : "2"; private string BuildBody() { var q = new System.Func(EditorScript.Quote); var n = new System.Func(EditorScript.NumberOrField); switch (_recipe) { case "multiply": return "set(" + q(Param("target")) + ", round(" + n(Param("left")) + " * " + n(Param("right")) + ", " + Digits() + "))"; case "subtract": return "set(" + q(Param("target")) + ", round(" + n(Param("left")) + " - " + n(Param("right")) + ", " + Digits() + "))"; case "percent": { var amount = n(Param("amount")); var portion = amount + " * " + n(Param("rate")) + " / 100"; var expression = Param("mode") == "add" ? amount + " + " + portion : Param("mode") == "subtract" ? amount + " - " + portion : portion; return "set(" + q(Param("target")) + ", round(" + expression + ", " + Digits() + "))"; } case "sum": return "set(" + q(Param("target")) + ", round(sum(" + string.Join(", ", _fields.Select(q)) + "), " + Digits() + "))"; case "formula": return "set(" + q(Param("target")) + ", " + (Param("expression").Length > 0 ? Param("expression") : "null") + ")"; case "today": { var offset = Param("offset"); var parsed = int.TryParse(offset, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value) ? value : 0; var start = parsed != 0 ? "new Date(Date.now() + " + parsed.ToString(CultureInfo.InvariantCulture) + " * 86400000)" : "new Date()"; return "set(" + q(Param("target")) + ", " + start + ".toISOString().slice(0, 10))"; } case "days": return "set(" + q(Param("target")) + ", days(" + q(Param("start")) + ", " + q(Param("end")) + "))"; case "hours": return "set(" + q(Param("target")) + ", hours(" + q(Param("start")) + ", " + q(Param("end")) + "))"; case "copy": return "copy(" + q(Param("source")) + ", " + q(Param("target")) + ")"; case "setValue": return "set(" + q(Param("target")) + ", tpl(" + q(Param("text")) + "))"; case "clear": return "clear(" + string.Join(", ", _fields.Select(q)) + ")"; case "readOnly": { var expression = ConditionExpression(); if (expression.Length == 0) { expression = "true"; } var flag = Param("invert") == "true" ? "!(" + expression + ")" : expression; return "readOnly(" + q(Param("target")) + ", " + flag + ")"; } case "notify": return "notify(" + q(Param("message")) + ")"; case "ask": return "if (!ask(" + q(Param("message")) + ")) return"; case "openUrl": return Param("target").Length > 0 && Param("target") != "_blank" ? "openUrl(" + q(Param("url")) + ", " + q(Param("target")) + ")" : "openUrl(" + q(Param("url")) + ")"; case "apiToField": return "set(" + q(Param("target")) + ", await api(" + q(Param("url")) + (Param("path").Length > 0 ? ", " + q(Param("path")) : string.Empty) + "))"; case "location": return "await geoTo(" + q(Param("lat")) + ", " + q(Param("lng")) + (Param("accuracy").Length > 0 ? ", " + q(Param("accuracy")) : string.Empty) + ")"; case "custom": return Param("code").Trim().TrimEnd(';'); default: return string.Empty; } } private static string TriggerName(EditorScriptTrigger trigger) { switch (trigger) { case EditorScriptTrigger.Change: return "change"; case EditorScriptTrigger.Open: return "open"; default: return "both"; } } }