using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security;
using System.Text;
using Newtonsoft.Json.Linq;

namespace GxMcp.Worker.Helpers
{
    /// <summary>
    /// W3 (mcp-roadmap-ide-parity 2026-05-19) — pure layout XML builder for the
    /// genexus_create_popup tool. Reverse-engineered from IDE-authored popups in the
    /// AcademicoHomolog1 KB (e.g. ListaAtiCPAlunoUniGra). Emits a Form type="layout"
    /// table-responsive structure so that Radio Button / Combo Box bindings render
    /// editable at runtime — see LayoutGotchaScanner GotchaGxAttributeHtmlFormDiscreteReadOnly.
    ///
    /// Theme classes (Attribute, Button, Form) are referenced by NAME — the generator/IDE
    /// resolves them at runtime against the KB's active Theme. No GUIDs required.
    ///
    /// Pure; no SDK / KB dependency, fully unit-testable.
    /// </summary>
    public static class PopupLayoutBuilder
    {
        public sealed class PopupSpec
        {
            public string Title { get; set; }
            public string Description { get; set; }
            public List<PopupInput> Inputs { get; set; } = new List<PopupInput>();
            public List<PopupButton> Buttons { get; set; } = new List<PopupButton>();
            public List<string> InParms { get; set; } = new List<string>();
            public List<string> OutParms { get; set; } = new List<string>();
        }

        public sealed class PopupInput
        {
            public string Type { get; set; }      // "radio" | "combo" | "text"
            public string VarName { get; set; }
            public string Label { get; set; }
            public List<PopupOption> Options { get; set; } = new List<PopupOption>();
            public string ShowWhen { get; set; }
        }

        public sealed class PopupOption
        {
            public string Value { get; set; }
            public string Label { get; set; }
        }

        public sealed class PopupButton
        {
            public string Caption { get; set; }
            public string Event { get; set; }
        }

        public sealed class ParsedSpec
        {
            public PopupSpec Spec { get; set; }
            public List<string> Errors { get; set; } = new List<string>();
            public bool IsValid => Errors.Count == 0;
        }

        public static ParsedSpec ParseSpec(JObject spec)
        {
            var result = new ParsedSpec { Spec = new PopupSpec() };
            if (spec == null)
            {
                result.Errors.Add("spec is required");
                return result;
            }

            result.Spec.Title = spec["title"]?.ToString();
            result.Spec.Description = spec["description"]?.ToString();

            if (spec["inputs"] is JArray inputs)
            {
                foreach (var t in inputs.OfType<JObject>())
                {
                    var inp = new PopupInput
                    {
                        Type = t["type"]?.ToString(),
                        VarName = t["varName"]?.ToString(),
                        Label = t["label"]?.ToString(),
                        ShowWhen = t["showWhen"]?.ToString()
                    };
                    if (t["options"] is JArray opts)
                    {
                        foreach (var o in opts.OfType<JObject>())
                        {
                            inp.Options.Add(new PopupOption
                            {
                                Value = o["value"]?.ToString(),
                                Label = o["label"]?.ToString()
                            });
                        }
                    }
                    result.Spec.Inputs.Add(inp);
                }
            }

            if (spec["buttons"] is JArray buttons)
            {
                foreach (var b in buttons.OfType<JObject>())
                {
                    result.Spec.Buttons.Add(new PopupButton
                    {
                        Caption = b["caption"]?.ToString(),
                        Event = b["event"]?.ToString()
                    });
                }
            }

            if (spec["inParms"] is JArray inP)
                result.Spec.InParms = inP.Select(t => t.ToString()).ToList();
            if (spec["outParms"] is JArray outP)
                result.Spec.OutParms = outP.Select(t => t.ToString()).ToList();

            // Validation
            if (result.Spec.Inputs.Count == 0)
                result.Errors.Add("At least one input is required");

            int i = 0;
            foreach (var inp in result.Spec.Inputs)
            {
                if (string.IsNullOrWhiteSpace(inp.VarName))
                    result.Errors.Add($"inputs[{i}].varName is required");
                if (string.IsNullOrWhiteSpace(inp.Type))
                    result.Errors.Add($"inputs[{i}].type is required");
                else if (!IsKnownType(inp.Type))
                    result.Errors.Add($"inputs[{i}].type '{inp.Type}' is not one of radio|combo|text");
                if ((inp.Type == "radio" || inp.Type == "combo") && inp.Options.Count == 0)
                    result.Errors.Add($"inputs[{i}] (type={inp.Type}) requires non-empty options[]");
                i++;
            }

            if (result.Spec.Buttons.Count == 0)
            {
                // Default a Confirmar/Enter button if none supplied — matches the IDE
                // popup convention.
                result.Spec.Buttons.Add(new PopupButton { Caption = "Confirmar", Event = "Enter" });
            }

            return result;
        }

        private static bool IsKnownType(string t) =>
            string.Equals(t, "radio", StringComparison.OrdinalIgnoreCase)
            || string.Equals(t, "combo", StringComparison.OrdinalIgnoreCase)
            || string.Equals(t, "text", StringComparison.OrdinalIgnoreCase);

        /// <summary>
        /// Builds the WebForm part XML for a Form type="layout" popup body.
        /// </summary>
        public static string BuildLayoutXml(PopupSpec spec)
        {
            if (spec == null) throw new ArgumentNullException(nameof(spec));

            var sb = new StringBuilder();
            sb.Append("<GxMultiForm>");
            sb.Append("<Form id=\"1\" type=\"layout\" Class=\"Form\">");
            sb.Append("<table id=\"TblMain\" class=\"Table\">");

            // Title row
            if (!string.IsNullOrWhiteSpace(spec.Title))
            {
                sb.Append("<row id=\"RowTitle\"><cell id=\"CellTitle\" ColSpan=\"2\">");
                sb.Append("<gxTextBlock id=\"LblTitle\" CaptionExpression=\"")
                  .Append(Esc(spec.Title))
                  .Append("\" Class=\"TextBlock\"/>");
                sb.Append("</cell></row>");
            }

            // Description row
            if (!string.IsNullOrWhiteSpace(spec.Description))
            {
                sb.Append("<row id=\"RowDescription\"><cell id=\"CellDescription\" ColSpan=\"2\">");
                sb.Append("<gxTextBlock id=\"LblDescription\" CaptionExpression=\"")
                  .Append(Esc(spec.Description))
                  .Append("\" Class=\"TextBlock\"/>");
                sb.Append("</cell></row>");
            }

            // Inputs
            int idx = 0;
            foreach (var inp in spec.Inputs)
            {
                string safeVar = SafeId(inp.VarName);
                string groupId = "GrpInput" + idx;
                string rowId = "RowInput" + idx;
                string lblId = "LblInput" + idx;
                string attId = "AttInput" + idx;

                sb.Append("<row id=\"").Append(rowId).Append("\">");
                sb.Append("<cell id=\"CellInput").Append(idx).Append("\" ColSpan=\"2\">");
                sb.Append("<group id=\"").Append(groupId).Append("\" Class=\"Group\">");
                sb.Append("<table id=\"Tbl").Append(groupId).Append("\" class=\"Table\">");

                // Label
                if (!string.IsNullOrWhiteSpace(inp.Label))
                {
                    sb.Append("<row id=\"").Append(rowId).Append("L\"><cell id=\"")
                      .Append("Cell").Append(lblId).Append("\">");
                    sb.Append("<gxTextBlock id=\"").Append(lblId).Append("\" CaptionExpression=\"")
                      .Append(Esc(inp.Label))
                      .Append("\" Class=\"TextBlockLabel\"/>");
                    sb.Append("</cell></row>");
                }

                // Control
                sb.Append("<row id=\"").Append(rowId).Append("C\"><cell id=\"")
                  .Append("Cell").Append(attId).Append("\">");

                string controlType = TypeToControlType(inp.Type);
                sb.Append("<gxAttribute id=\"").Append(attId)
                  .Append("\" AttID=\"&amp;").Append(safeVar).Append("\"");
                if (!string.IsNullOrEmpty(controlType))
                {
                    sb.Append(" ControlType=\"").Append(Esc(controlType)).Append("\"");
                    if (controlType == "Radio Button" || controlType == "Combo Box")
                    {
                        sb.Append(" ControlValues=\"").Append(Esc(BuildControlValues(inp.Options))).Append("\"");
                    }
                }
                sb.Append(" Class=\"Attribute\"/>");
                sb.Append("</cell></row>");

                sb.Append("</table></group>");
                sb.Append("</cell></row>");
                idx++;
            }

            // Buttons row
            sb.Append("<row id=\"RowButtons\"><cell id=\"CellButtons\" ColSpan=\"2\" HAlign=\"right\">");
            sb.Append("<table id=\"TblButtons\" class=\"Table\"><row id=\"RowBtns\">");
            int b = 0;
            foreach (var btn in spec.Buttons)
            {
                string caption = string.IsNullOrEmpty(btn.Caption) ? "Confirmar" : btn.Caption;
                string evt = string.IsNullOrEmpty(btn.Event) ? "Enter" : btn.Event;
                sb.Append("<cell id=\"CellBtn").Append(b).Append("\">");
                sb.Append("<action id=\"Btn").Append(SafeId(evt)).Append("\" Caption=\"")
                  .Append(Esc(caption)).Append("\" onClickEvent=\"'")
                  .Append(Esc(evt)).Append("'\" Class=\"Button\"/>");
                sb.Append("</cell>");
                b++;
            }
            sb.Append("</row></table></cell></row>");

            sb.Append("</table>");
            sb.Append("</Form>");
            sb.Append("</GxMultiForm>");
            return sb.ToString();
        }

        /// <summary>
        /// Builds the Rules part source from inParms / outParms.
        /// </summary>
        public static string BuildRulesSource(PopupSpec spec)
        {
            var parts = new List<string>();
            foreach (var p in spec.InParms ?? new List<string>())
            {
                string varName = ExtractVarName(p);
                if (!string.IsNullOrEmpty(varName))
                    parts.Add("in:&" + varName);
            }
            foreach (var p in spec.OutParms ?? new List<string>())
            {
                string varName = ExtractVarName(p);
                if (!string.IsNullOrEmpty(varName))
                    parts.Add("out:&" + varName);
            }
            if (parts.Count == 0) return string.Empty;
            return "parm(" + string.Join(", ", parts) + ");\n";
        }

        /// <summary>
        /// Builds the Events part source. Includes the Enter event placeholder plus
        /// a Refresh event when any input has a showWhen condition.
        /// </summary>
        public static string BuildEventsSource(PopupSpec spec)
        {
            var sb = new StringBuilder();
            // Refresh event for showWhen visibility
            var conditional = (spec.Inputs ?? new List<PopupInput>())
                .Where(i => !string.IsNullOrWhiteSpace(i.ShowWhen))
                .ToList();
            if (conditional.Count > 0)
            {
                sb.AppendLine("Event Refresh");
                int idx = 0;
                foreach (var inp in spec.Inputs)
                {
                    if (!string.IsNullOrWhiteSpace(inp.ShowWhen))
                    {
                        var cond = TranslateCondition(inp.ShowWhen);
                        sb.AppendLine("    if " + cond);
                        sb.AppendLine("        GrpInput" + idx + ".Visible = True");
                        sb.AppendLine("    else");
                        sb.AppendLine("        GrpInput" + idx + ".Visible = False");
                        sb.AppendLine("    endif");
                    }
                    idx++;
                }
                sb.AppendLine("EndEvent");
                sb.AppendLine();
            }

            // One Enter event placeholder per declared button event (deduped by name).
            var evtNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
            foreach (var btn in spec.Buttons ?? new List<PopupButton>())
            {
                string e = string.IsNullOrEmpty(btn.Event) ? "Enter" : btn.Event;
                if (!evtNames.Add(e)) continue;
                sb.AppendLine("Event " + e);
                sb.AppendLine("    // TODO: agent provides " + e + " event body");
                sb.AppendLine("EndEvent");
                sb.AppendLine();
            }
            return sb.ToString();
        }

        // === helpers ===

        private static string TypeToControlType(string t)
        {
            if (string.Equals(t, "radio", StringComparison.OrdinalIgnoreCase)) return "Radio Button";
            if (string.Equals(t, "combo", StringComparison.OrdinalIgnoreCase)) return "Combo Box";
            return null; // text → default Edit
        }

        private static string BuildControlValues(List<PopupOption> options)
        {
            // Convention: "<label>,<value>;<label>,<value>" — matches IDE-authored Static
            // ControlValues string used by Radio Button / Combo Box gxAttribute controls.
            return string.Join(";", options.Select(o => (o.Label ?? "") + "," + (o.Value ?? "")));
        }

        private static string TranslateCondition(string showWhen)
        {
            // Accept input like "RespRegProf == 'S'" — pass through with a leading &
            // on the variable identifier so it's a valid GeneXus event expression.
            var s = (showWhen ?? "").Trim();
            if (s.Length == 0) return "True";
            // Replace '==' with '=' (GeneXus equality)
            s = s.Replace("==", "=");
            // Prefix the first identifier with & if not already
            int i = 0;
            while (i < s.Length && (char.IsLetter(s[i]) || s[i] == '_')) i++;
            if (i > 0 && s[0] != '&')
                s = "&" + s;
            return s + " then";
        }

        private static string ExtractVarName(string parm)
        {
            if (string.IsNullOrWhiteSpace(parm)) return null;
            // Accept "VarName" or "VarName:Numeric(2)" or "&VarName"
            string s = parm.Trim().TrimStart('&');
            int colon = s.IndexOf(':');
            if (colon > 0) s = s.Substring(0, colon).Trim();
            return s;
        }

        private static string SafeId(string s)
        {
            if (string.IsNullOrEmpty(s)) return "X";
            var chars = s.Where(c => char.IsLetterOrDigit(c) || c == '_').ToArray();
            return chars.Length == 0 ? "X" : new string(chars);
        }

        private static string Esc(string s) => SecurityElement.Escape(s ?? "");
    }
}
