using System.Linq;
using Newtonsoft.Json.Linq;
namespace GxMcp.Gateway.Routers
{
    public class ObjectRouter : IMcpModuleRouter
    {
        public string ModuleName => "Object";

        private static readonly string[] _validEditModes = { "xml", "ops", "patch", "full" };

        // Keep in sync with GxMcp.Worker.Services.SemanticOpsService.Dispatch — adding an op
        // there without adding it here will cause the gateway to reject the call before it
        // ever reaches the worker.
        private static readonly string[] _validSemanticOps = {
            "set_attribute", "add_attribute", "remove_attribute",
            "add_rule", "remove_rule", "set_property"
        };

        private static void ValidateEditMode(string? mode)
        {
            if (string.IsNullOrEmpty(mode)) return;
            if (_validEditModes.Contains(mode)) return;
            throw new UsageException(
                "usage_error",
                DidYouMean.FormatSuggestionMessage("edit mode", mode, _validEditModes)
            );
        }

        private static void ValidateSemanticOps(JToken? opsTok)
        {
            if (!(opsTok is JArray ops)) return;
            for (int i = 0; i < ops.Count; i++)
            {
                string? opName = (ops[i] as JObject)?["op"]?.ToString();
                if (string.IsNullOrEmpty(opName))
                {
                    throw new UsageException("usage_error", $"ops[{i}]: 'op' field is required.");
                }
                if (_validSemanticOps.Contains(opName)) continue;
                throw new UsageException(
                    "usage_error",
                    DidYouMean.FormatSuggestionMessage($"ops[{i}].op", opName, _validSemanticOps)
                );
            }
        }

        public object? ConvertToolCall(string toolName, JObject? args)
        {
            string? target = args?["name"]?.ToString();
            string part = args?["part"]?.ToString() ?? "Source";

            switch (toolName)
            {
                case "genexus_read":
                {
                    var targetsTokRead = args?["targets"];
                    bool hasTargetsRead = targetsTokRead is JArray;
                    bool hasNameRead = !string.IsNullOrEmpty(target);
                    if (hasNameRead && hasTargetsRead)
                        throw new UsageException("usage_error", "name and targets are mutually exclusive");
                    if (hasTargetsRead)
                    {
                        return new {
                            module = "Batch",
                            action = "BatchRead",
                            items = (JArray)targetsTokRead!,
                            part = part
                        };
                    }
                    var partsTok = args?["parts"];
                    bool hasParts = partsTok is JArray partsArr && partsArr.Count > 0;
                    if (hasParts)
                    {
                        return new {
                            module = "Read",
                            action = "ExtractParts",
                            target = target,
                            parts = (JArray)partsTok!,
                            type = args?["type"]?.ToString()
                        };
                    }
                    return new {
                        module = "Read",
                        action = "ExtractSource",
                        target = target,
                        part = part,
                        offset = args?["offset"]?.ToObject<int?>(),
                        limit = args?["limit"]?.ToObject<int?>(),
                        type = args?["type"]?.ToString()
                    };
                }

                case "genexus_edit":
                {
                    if (args?["changes"] != null)
                        throw new UsageException("usage_error", "argument 'changes' removed in v2.0.0; use 'targets' instead");

                    var targetsTokEdit = args?["targets"];
                    bool hasTargetsEdit = targetsTokEdit is JArray;
                    bool hasNameEdit = !string.IsNullOrEmpty(target);
                    if (hasNameEdit && hasTargetsEdit)
                        throw new UsageException("usage_error", "name and targets are mutually exclusive");
                    if (hasTargetsEdit)
                    {
                        return new {
                            module = "Batch",
                            action = "MultiEdit",
                            items = (JArray)targetsTokEdit!,
                            dryRun = args?["dryRun"]?.ToObject<bool?>() ?? false
                        };
                    }

                    string? mode = args?["mode"]?.ToString();
                    ValidateEditMode(mode);
                    bool returnPostState = args?["return_post_state"]?.ToObject<bool?>() ?? true;
                    bool verbose = args?["verbose"]?.ToObject<bool?>() ?? false;
                    // Items 5 + 37 (friction 2026-05-22): forward visualVerify to the
                    // worker so it can shell out to chrome-devtools-axi / playwright
                    // after the edit lands and attach a screenshot + pixel-diff envelope.
                    bool visualVerify = args?["visualVerify"]?.ToObject<bool?>() ?? false;
                    if (mode == "ops")
                    {
                        ValidateSemanticOps(args?["ops"]);
                        return new {
                            module = "SemanticOps",
                            action = "Apply",
                            target = target,
                            part = part,
                            ops = args?["ops"],
                            dryRun = args?["dryRun"]?.ToObject<bool?>() ?? false,
                            return_post_state = returnPostState,
                            verbose = verbose,
                            visualVerify = visualVerify
                        };
                    }
                    if (mode == "patch")
                    {
                        var patchTok = args?["patch"];
                        if (patchTok is JArray patchArr)
                        {
                            // RFC 6902 JSON-Patch (array payload)
                            return new {
                                module = "JsonPatch",
                                action = "Apply",
                                target = target,
                                part = part,
                                patch = patchArr,
                                dryRun = args?["dryRun"]?.ToObject<bool?>() ?? false,
                                return_post_state = returnPostState,
                                verbose = verbose,
                                visualVerify = visualVerify
                            };
                        }

                        // FR#16 (friction-report 2026-05-14): accept the {find, replace} JSON form.
                        // The schema advertised it but only the legacy (operation, context, content)
                        // string form was implemented, so callers got
                        // "'context' (old_string) is required for Replace" even with a valid object.
                        // Map find→context and replace→payload to reuse the existing patch pipeline.
                        string opFromObj = null;
                        string contextFromObj = null;
                        string payloadFromObj = null;
                        if (patchTok is JObject patchObj)
                        {
                            var find = patchObj["find"]?.ToString();
                            var replace = patchObj["replace"]?.ToString();
                            if (find != null || replace != null)
                            {
                                contextFromObj = find;
                                payloadFromObj = replace ?? string.Empty;
                                opFromObj = "Replace";
                            }
                        }

                        // Legacy text-patch (string payload) — unchanged path otherwise.
                        return new {
                            module = "Patch",
                            action = "Apply",
                            target = target,
                            part = part,
                            operation = opFromObj ?? args?["operation"]?.ToString() ?? "Replace",
                            // Worker dispatcher reads request["payload"] for the replacement text
                            // (see CommandDispatcher.cs:154 `payload = request["payload"]`).
                            payload = payloadFromObj
                                   ?? (patchTok is JValue ? patchTok.ToString() : null)
                                   ?? args?["content"]?.ToString(),
                            context = contextFromObj ?? args?["context"]?.ToString(),
                            expectedCount = args?["expectedCount"]?.ToObject<int?>() ?? 1,
                            dryRun = args?["dryRun"]?.ToObject<bool?>() ?? false,
                            verifyRollback = args?["verifyRollback"]?.ToObject<bool?>() ?? false,
                            return_post_state = returnPostState,
                            verbose = verbose,
                            // v2.6.6 FR#13 follow-up: forward `validate` so the worker's
                            // CommandDispatcher.patch.Apply branch can honor validate=only
                            // (mapped to dryRun=true). Without this the gateway silently
                            // stripped the flag and the schema lied to the LLM.
                            validate = args?["validate"]?.ToString(),
                            // Item 9 (friction 2026-05-22): replaceAll=true applies patch to all
                            // occurrences instead of requiring expectedCount to match exactly.
                            replaceAll = args?["replaceAll"]?.ToObject<bool?>() ?? false,
                            visualVerify = visualVerify
                        };
                    }
                    else
                    {
                        return new {
                            module = "Write",
                            action = part,
                            target = target,
                            payload = args?["content"]?.ToString(),
                            type = args?["type"]?.ToString(),
                            dryRun = args?["dryRun"]?.ToObject<bool?>() ?? false,
                            visualVerify = visualVerify
                        };
                    }
                }

                // Aliases legados (escondidos mas funcionais para a Gateway interna se necessário)
                case "genexus_read_source":
                    return new { module = "Read", action = "ExtractSource", target = target, part = part };
                case "genexus_patch":
                    return new {
                        module = "Patch",
                        action = "Apply",
                        target = target,
                        part = part,
                        operation = args?["operation"]?.ToString(),
                        content = args?["content"]?.ToString(),
                        context = args?["context"]?.ToString(),
                        expectedCount = args?["expectedCount"]?.ToObject<int?>() ?? 1,
                        dryRun = args?["dryRun"]?.ToObject<bool?>() ?? false,
                        verifyRollback = args?["verifyRollback"]?.ToObject<bool?>() ?? false
                    };
                case "genexus_write_object":
                    return new { module = "Write", action = part, target = target, payload = args?["code"]?.ToString() };
                case "genexus_get_variables":
                    return new { module = "Read", action = "GetVariables", target = target };
                case "genexus_get_attribute":
                    return new { module = "Read", action = "GetAttribute", target = target };
                case "genexus_get_properties":
                    return new { module = "Property", action = "Get", target = target, control = args?["control"]?.ToString() };

                // export_object + import_object merged into genexus_io umbrella (OperationsRouter).

                case "genexus_edit_and_build":
                    // Items 5 + 37: hoist visualVerify + part to the top-level
                    // of the worker command so the dispatcher's post-edit hook
                    // can read them straight off `args` without unwrapping the
                    // nested orchestrator envelope.
                    return new
                    {
                        module = "EditAndBuild",
                        action = "Orchestrate",
                        target = args?["name"]?.ToString(),
                        part = args?["part"]?.ToString(),
                        args = args,
                        visualVerify = args?["visualVerify"]?.ToObject<bool?>() ?? false
                    };

                default:
                    return null;
            }
        }
    }
}
