using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Reflection;
using Artech.Architecture.Common.Objects;
using Artech.Genexus.Common.Objects;
using Artech.Genexus.Common.Parts;

namespace GxMcp.Worker.Helpers
{
    public static class ReportLayoutHelper
    {
        public static KBObjectPart IsReportPart(KBObjectPart part)
        {
            if (part == null) return null;
            var name = part.GetType().FullName;
            if (name.Contains("ReportPart") || name.Contains("LayoutPart")) return part;
            return null;
        }

        public static string ReadLayout(KBObjectPart part)
        {
            try
            {
                var partType = part.GetType();
                var layoutProp = partType.GetProperty("MyLayout", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase)
                              ?? partType.GetProperty("Layout", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                
                if (layoutProp == null) return null;
                var layout = layoutProp.GetValue(part, null);
                if (layout == null) return null;

                var bandsProp = layout.GetType().GetProperty("ReportBands", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                if (bandsProp == null) return null;

                var bands = bandsProp.GetValue(layout, null) as System.Collections.IEnumerable;
                if (bands == null) return null;

                return GenerateVisualXmlFromBands(bands);
            }
            catch (Exception ex)
            {
                Logger.Error("ReportLayoutHelper.ReadLayout Error: " + ex.Message);
                return null;
            }
        }

        private static string GenerateVisualXmlFromBands(System.Collections.IEnumerable bands)
        {
            var root = new XElement("Report");

            foreach (var band in bands)
            {
                var bType = band.GetType();
                var bName = AttributeTypeApplier.GetPropertyUnambiguous(bType, "Name")?.GetValue(band, null)?.ToString() ?? "Band";
                var pb = new XElement("PrintBlock", new XAttribute("Name", bName), new XAttribute("ControlName", bName));

                foreach (var pName in new[] { "Height" })
                {
                    var pVal = AttributeTypeApplier.GetPropertyUnambiguous(bType, pName)?.GetValue(band, null);
                    if (pVal != null) pb.SetAttributeValue(pName, pVal.ToString());
                }

                var itemsProp = AttributeTypeApplier.GetPropertyUnambiguous(bType, "Items")
                             ?? AttributeTypeApplier.GetPropertyUnambiguous(bType, "Elements")
                             ?? AttributeTypeApplier.GetPropertyUnambiguous(bType, "Controls")
                             ?? AttributeTypeApplier.GetPropertyUnambiguous(bType, "Components");
                var items = itemsProp?.GetValue(band, null) as System.Collections.IEnumerable;

                if (items != null)
                {
                    foreach (var item in items)
                    {
                        var iType = item.GetType();
                        var el = new XElement("Control");
                        el.SetAttributeValue("TypeName", iType.Name);

                        var map = new Dictionary<string, string> {
                            { "Name", "Name" },
                            { "Left", "X" },
                            { "Top", "Y" },
                            { "Width", "Width" },
                            { "Height", "Height" },
                            { "Caption", "Text" },
                            { "ControlSource", "ControlSource" },
                            { "ForeColor", "ForeColor" },
                            { "BackColor", "BackColor" },
                            { "Borders", "Borders" },
                            { "BorderWidth", "BorderWidth" },
                            { "BorderColor", "BorderColor" },
                            { "Alignment", "Alignment" },
                            { "WordWrap", "WordWrap" },
                            { "Visible", "Visible" },
                            { "Enabled", "Enabled" }
                        };

                        foreach (var entry in map)
                        {
                            var prop = iType.GetProperty(entry.Value, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase)
                                    ?? iType.GetProperty(entry.Key, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                            var pVal = prop?.GetValue(item, null);
                            if (pVal != null)
                            {
                                var serialized = pVal.ToString();
                                if (IsColorAttributeName(entry.Key))
                                {
                                    serialized = NormalizeColorToken(serialized);
                                }
                                el.SetAttributeValue(entry.Key, serialized);
                            }
                        }

                        var currentName = AttributeTypeApplier.GetPropertyUnambiguous(iType, "Name")?.GetValue(item, null)?.ToString();
                        var ctrlName = (AttributeTypeApplier.GetPropertyUnambiguous(iType, "ControlName")?.GetValue(item, null) ?? currentName)?.ToString();
                        if (!string.IsNullOrEmpty(ctrlName)) el.SetAttributeValue("ControlName", ctrlName);

                        pb.Add(el);
                    }
                }
                root.Add(pb);
            }
            return root.ToString();
        }

        public static bool WriteLayout(KBObjectPart part, string xml)
        {
            if (part == null || string.IsNullOrWhiteSpace(xml)) return false;

            try
            {
                var visualDoc = XDocument.Parse(xml);
                var partType = part.GetType();
                var layoutProp = partType.GetProperty("MyLayout", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase)
                              ?? partType.GetProperty("Layout", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                
                if (layoutProp == null) return false;
                var layout = layoutProp.GetValue(part, null);
                if (layout == null) return false;

                var bandsProp = layout.GetType().GetProperty("ReportBands", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                if (bandsProp == null) return false;

                var bandsCollection = bandsProp.GetValue(layout, null) as System.Collections.IEnumerable;
                if (bandsCollection == null) return false;
                var bandsList = bandsCollection.Cast<object>().ToList();

                bool anyChange = false;
                int appliedAssignments = 0;
                foreach (var elXml in visualDoc.Descendants("Control"))
                {
                    var elName = elXml.Attribute("ControlName")?.Value ?? elXml.Attribute("Name")?.Value;
                    if (string.IsNullOrEmpty(elName)) continue;

                    foreach (var bandObj in bandsList)
                    {
                        var items = GetCollection(bandObj, "Items", "Elements", "Controls", "Components");
                        if (items == null) continue;

                        foreach (var item in items)
                        {
                            var iType = item.GetType();
                            var nameProp = iType.GetProperty("Name", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                            var currentName = nameProp?.GetValue(item, null)?.ToString();
                            var controlNameProp = iType.GetProperty("ControlName", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                            var currentControlName = controlNameProp?.GetValue(item, null)?.ToString();
                            
                            if (string.Equals(currentName, elName, StringComparison.OrdinalIgnoreCase) ||
                                string.Equals(currentControlName, elName, StringComparison.OrdinalIgnoreCase))
                            {
                                foreach (var attr in elXml.Attributes())
                                {
                                    string aName = attr.Name.LocalName;
                                    if (IsExcludedAttribute(aName)) continue;
                                    string rawValue = attr.Value;
                                    if (IsColorAttributeName(aName))
                                    {
                                        rawValue = NormalizeColorToken(rawValue);
                                    }

                                    bool assignmentApplied = false;
                                    foreach (var sdkPropName in ResolveSdkPropertyCandidates(aName))
                                    {
                                        if (TrySetProperty(item, iType, sdkPropName, rawValue))
                                        {
                                            assignmentApplied = true;
                                            appliedAssignments++;
                                            if (!TryReadProperty(item, iType, sdkPropName, out string afterValue))
                                            {
                                                Logger.Debug($"ReportLayoutHelper: assigned {elName}.{sdkPropName}='{rawValue}' (read-back unavailable).");
                                            }
                                            else
                                            {
                                                Logger.Debug($"ReportLayoutHelper: assigned {elName}.{sdkPropName}='{rawValue}' (read-back='{afterValue}').");
                                            }
                                            break;
                                        }
                                    }

                                    if (!assignmentApplied)
                                    {
                                        Logger.Warn($"ReportLayoutHelper: failed to map attribute '{aName}' for control '{elName}' to any writable SDK property.");
                                    }
                                }

                                anyChange = appliedAssignments > 0;
                            }
                        }
                    }
                }

                if (anyChange)
                {
                    try
                    {
                        // Some GX SDK implementations expose layout snapshots; assigning back reinforces persistence semantics.
                        layoutProp.SetValue(part, layout, null);
                    }
                    catch (Exception ex)
                    {
                        Logger.Warn("ReportLayoutHelper: layout reassignment failed: " + ex.Message);
                    }

                    try
                    {
                        part.Save();
                        if (part.KBObject != null) part.KBObject.Save();
                        return true;
                    }
                    catch (Exception ex)
                    {
                        Logger.Warn("ReportLayoutHelper.WriteLayout Save failed. Trying fallback without validation barriers: " + ex.Message);
                        try
                        {
                            if (part.KBObject != null)
                            {
                                part.KBObject.EnsureSave(false);
                                Logger.Info("ReportLayoutHelper.WriteLayout fallback EnsureSave(false) succeeded.");
                                return true;
                            }
                        }
                        catch (Exception fallbackEx)
                        {
                            Logger.Error("ReportLayoutHelper.WriteLayout fallback failed: " + fallbackEx.Message);
                        }

                        Logger.Error("ReportLayoutHelper.WriteLayout Save failed: " + ex.Message);
                        return false;
                    }
                }
                return anyChange;
            }
            catch (Exception ex)
            {
                Logger.Error("ReportLayoutHelper.WriteLayout Error: " + ex.Message);
                return false;
            }
        }

        public static bool RenamePrintBlock(KBObjectPart part, string currentName, string newName, bool persist = true)
        {
            if (part == null || string.IsNullOrWhiteSpace(currentName) || string.IsNullOrWhiteSpace(newName))
            {
                return false;
            }

            try
            {
                var layout = GetLayoutInstance(part);
                if (layout == null) return false;
                var bands = GetBandsList(layout);
                if (bands == null || bands.Count == 0) return false;

                object band = bands.FirstOrDefault(b =>
                {
                    var n = b.GetType().GetProperty("Name", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase)?.GetValue(b, null)?.ToString();
                    return string.Equals(n, currentName, StringComparison.OrdinalIgnoreCase);
                });

                if (band == null) return false;

                bool renamed = TrySetProperty(band, band.GetType(), "Name", newName);
                renamed = TrySetProperty(band, band.GetType(), "ControlName", newName) || renamed;
                renamed = TrySetPropertyValueFallback(band, "Name", newName) || renamed;
                renamed = TrySetPropertyValueFallback(band, "ControlName", newName) || renamed;

                bool readBackMatches = false;
                if (TryReadProperty(band, band.GetType(), "Name", out string afterName) &&
                    string.Equals(afterName, newName, StringComparison.OrdinalIgnoreCase))
                {
                    readBackMatches = true;
                }
                if (TryReadProperty(band, band.GetType(), "ControlName", out string afterControlName) &&
                    string.Equals(afterControlName, newName, StringComparison.OrdinalIgnoreCase))
                {
                    readBackMatches = true;
                }

                if (!renamed || !readBackMatches)
                {
                    // Fallback: clone+swap to force a new band identity bound to new name.
                    if (!TryCloneSwapBand(layout, bands, band, newName))
                    {
                        Logger.Warn($"ReportLayoutHelper.RenamePrintBlock: unable to persist rename for band '{currentName}'.");
                        return false;
                    }
                }

                TryMarkLayoutDirty(part, layout);

                if (persist)
                {
                    if (!TryPersistPart(part, "RenamePrintBlock"))
                    {
                        return false;
                    }
                    var persistedBands = GetBandsList(GetLayoutInstance(part));
                    bool exists = persistedBands != null && persistedBands.Any(b =>
                        string.Equals(GetBandName(b), newName, StringComparison.OrdinalIgnoreCase) ||
                        string.Equals(GetBandControlName(b), newName, StringComparison.OrdinalIgnoreCase));
                    if (!exists)
                    {
                        Logger.Warn($"ReportLayoutHelper.RenamePrintBlock: post-save verification failed for '{newName}'.");
                        return false;
                    }
                }
                Logger.Info(persist
                    ? $"ReportLayoutHelper.RenamePrintBlock: '{currentName}' -> '{newName}' persisted and verified."
                    : $"ReportLayoutHelper.RenamePrintBlock: '{currentName}' -> '{newName}' staged in memory.");
                return true;
            }
            catch (Exception ex)
            {
                Logger.Error("ReportLayoutHelper.RenamePrintBlock Error: " + ex.Message);
                return false;
            }
        }

        public static bool AddPrintBlock(KBObjectPart part, string printBlockName, int? height, bool persist = true)
        {
            if (part == null || string.IsNullOrWhiteSpace(printBlockName))
            {
                return false;
            }

            try
            {
                var layout = GetLayoutInstance(part);
                if (layout == null) return false;

                var bands = GetBandsList(layout);
                if (bands == null || bands.Count == 0) return false;

                var templateBand = bands.FirstOrDefault(b =>
                    string.Equals(GetBandName(b), "printBlock3", StringComparison.OrdinalIgnoreCase) ||
                    string.Equals(GetBandName(b), "printBlock1", StringComparison.OrdinalIgnoreCase))
                    ?? bands.FirstOrDefault();
                if (templateBand == null) return false;

                object newBand = null;
                try
                {
                    // Prefer an independent instance; shallow clone can alias underlying SDK handles.
                    newBand = Activator.CreateInstance(templateBand.GetType());
                }
                catch
                {
                }

                if (newBand == null)
                {
                    newBand = CloneBand(templateBand);
                }
                if (newBand == null) return false;

                var newBandType = newBand.GetType();

                bool named = TrySetProperty(newBand, newBandType, "Name", printBlockName);
                named = TrySetProperty(newBand, newBandType, "ControlName", printBlockName) || named;
                named = TrySetPropertyValueFallback(newBand, "Name", printBlockName) || named;
                named = TrySetPropertyValueFallback(newBand, "ControlName", printBlockName) || named;
                if (!named)
                {
                    Logger.Warn($"ReportLayoutHelper.AddPrintBlock: unable to set Name/ControlName='{printBlockName}'.");
                    return false;
                }

                int bandHeight = height.HasValue && height.Value > 0 ? height.Value : 40;
                TrySetProperty(newBand, newBandType, "Height", bandHeight.ToString());
                TrySetPropertyValueFallback(newBand, "Height", bandHeight.ToString());

                // Prevent duplicate fixed control names when cloning a template.
                var existingControlNames = CollectControlNames(bands);
                EnsureUniqueControlNames(newBand, existingControlNames);

                bool inserted = TryInsertBandBeforeFooter(layout, bands, newBand);
                if (!inserted)
                {
                    if (!TryAddBandToCollection(layout, newBand))
                    {
                        Logger.Warn("ReportLayoutHelper.AddPrintBlock: no compatible AddBand/collection mutator found.");
                        return false;
                    }
                }

                TryMarkLayoutDirty(part, layout);

                if (persist)
                {
                    if (!TryPersistPart(part, "AddPrintBlock"))
                    {
                        return false;
                    }
                    var persistedBands = GetBandsList(GetLayoutInstance(part));
                    bool exists = persistedBands != null && persistedBands.Any(b =>
                        string.Equals(GetBandName(b), printBlockName, StringComparison.OrdinalIgnoreCase) ||
                        string.Equals(GetBandControlName(b), printBlockName, StringComparison.OrdinalIgnoreCase));
                    if (!exists)
                    {
                        Logger.Warn($"ReportLayoutHelper.AddPrintBlock: post-save verification failed for '{printBlockName}'.");
                        return false;
                    }
                }
                Logger.Info(persist
                    ? $"ReportLayoutHelper.AddPrintBlock: '{printBlockName}' persisted and verified."
                    : $"ReportLayoutHelper.AddPrintBlock: '{printBlockName}' staged in memory.");
                return true;
            }
            catch (Exception ex)
            {
                Logger.Error("ReportLayoutHelper.AddPrintBlock Error: " + ex.Message);
                return false;
            }
        }

        private static bool IsExcludedAttribute(string name)
        {
            string[] excluded = { "ControlName", "Name", "TypeName", "ControlSource" };
            return excluded.Contains(name, StringComparer.OrdinalIgnoreCase);
        }

        private static bool TryPersistPart(KBObjectPart part, string operation)
        {
            if (part == null) return false;
            try
            {
                part.Save();
                part.KBObject?.Save();
                return true;
            }
            catch (Exception ex)
            {
                Logger.Warn($"ReportLayoutHelper.{operation}: default save failed, trying EnsureSave(false). Reason: {ex.Message}");
                try
                {
                    if (part.KBObject != null)
                    {
                        part.KBObject.EnsureSave(false);
                        Logger.Info($"ReportLayoutHelper.{operation}: fallback EnsureSave(false) succeeded.");
                        return true;
                    }
                }
                catch (Exception fallbackEx)
                {
                    Logger.Error($"ReportLayoutHelper.{operation}: fallback EnsureSave(false) failed: {fallbackEx.Message}");
                }

                Logger.Error($"ReportLayoutHelper.{operation}: persist failed after fallback.");
                return false;
            }
        }

        private static object ConvertValue(string value, Type targetType)
        {
            if (targetType == null) return null;

            var nullableType = Nullable.GetUnderlyingType(targetType);
            if (nullableType != null)
            {
                targetType = nullableType;
            }

            if (targetType == typeof(string)) return value;
            if (targetType == typeof(int)) return int.TryParse(value, out int i) ? (object)i : null;
            if (targetType == typeof(float)) return float.TryParse(value, out float f) ? (object)f : null;
            if (targetType == typeof(double)) return double.TryParse(value, out double d) ? (object)d : null;
            if (targetType == typeof(decimal)) return decimal.TryParse(value, out decimal dec) ? (object)dec : null;
            if (targetType == typeof(bool)) return bool.TryParse(value, out bool b) ? (object)b : null;

            if (targetType.IsEnum)
            {
                try
                {
                    return Enum.Parse(targetType, value, true);
                }
                catch
                {
                    return null;
                }
            }

            if (targetType == typeof(System.Drawing.Color))
            {
                try
                {
                    if (TryParseColor(value, out var parsedColor))
                    {
                        return parsedColor;
                    }
                }
                catch { }
            }

            return null;
        }

        private static IEnumerable<string> ResolveSdkPropertyCandidates(string visualAttributeName)
        {
            if (string.Equals(visualAttributeName, "Caption", StringComparison.OrdinalIgnoreCase))
            {
                // In report controls, Text is the canonical SDK property, but some controls still expose Caption.
                yield return "Text";
                yield return "Caption";
                yield break;
            }

            if (string.Equals(visualAttributeName, "Left", StringComparison.OrdinalIgnoreCase))
            {
                yield return "X";
                yield return "Left";
                yield break;
            }

            if (string.Equals(visualAttributeName, "Top", StringComparison.OrdinalIgnoreCase))
            {
                yield return "Y";
                yield return "Top";
                yield break;
            }

            yield return visualAttributeName;
        }

        private static bool TrySetProperty(object instance, Type instanceType, string sdkPropertyName, string rawValue)
        {
            if (instance == null || instanceType == null || string.IsNullOrWhiteSpace(sdkPropertyName))
            {
                return false;
            }
            string normalizedForSdk = IsColorAttributeName(sdkPropertyName)
                ? NormalizeColorTokenForSdkWrite(rawValue)
                : rawValue;

            var prop = instanceType.GetProperty(sdkPropertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
            if (prop != null && prop.CanWrite)
            {
                object val = ConvertValue(normalizedForSdk, prop.PropertyType);
                if (val != null)
                {
                    prop.SetValue(instance, val);
                    return true;
                }
            }

            // Fallback for controls that expose dynamic property setters instead of writable CLR properties.
            var setPropertyValue = instanceType.GetMethod(
                "SetPropertyValue",
                BindingFlags.Public | BindingFlags.Instance,
                null,
                new[] { typeof(string), typeof(object) },
                null);

            if (setPropertyValue != null)
            {
                setPropertyValue.Invoke(instance, new object[] { sdkPropertyName, normalizedForSdk });
                return true;
            }

            var setPropertyValueString = instanceType.GetMethod(
                "SetPropertyValueString",
                BindingFlags.Public | BindingFlags.Instance,
                null,
                new[] { typeof(string), typeof(string) },
                null);

            if (setPropertyValueString != null)
            {
                setPropertyValueString.Invoke(instance, new object[] { sdkPropertyName, normalizedForSdk });
                return true;
            }

            return false;
        }

        private static bool IsColorAttributeName(string attributeName)
        {
            return string.Equals(attributeName, "ForeColor", StringComparison.OrdinalIgnoreCase) ||
                   string.Equals(attributeName, "BackColor", StringComparison.OrdinalIgnoreCase) ||
                   string.Equals(attributeName, "BorderColor", StringComparison.OrdinalIgnoreCase);
        }

        private static bool TryParseColor(string raw, out System.Drawing.Color color)
        {
            color = System.Drawing.Color.Empty;
            if (string.IsNullOrWhiteSpace(raw)) return false;

            string token = ExtractColorLeafToken(raw);
            if (string.IsNullOrWhiteSpace(token)) return false;

            if (string.Equals(token, "Transparent", StringComparison.OrdinalIgnoreCase))
            {
                color = System.Drawing.Color.Transparent;
                return true;
            }

            var rgbMatch = System.Text.RegularExpressions.Regex.Match(
                token,
                @"^\s*(\d{1,3})\s*;\s*(\d{1,3})\s*;\s*(\d{1,3})\s*\|?\s*$");
            if (rgbMatch.Success)
            {
                if (int.TryParse(rgbMatch.Groups[1].Value, out int r) &&
                    int.TryParse(rgbMatch.Groups[2].Value, out int g) &&
                    int.TryParse(rgbMatch.Groups[3].Value, out int b))
                {
                    r = Math.Max(0, Math.Min(255, r));
                    g = Math.Max(0, Math.Min(255, g));
                    b = Math.Max(0, Math.Min(255, b));
                    color = System.Drawing.Color.FromArgb(r, g, b);
                    return true;
                }
            }

            var named = System.Drawing.Color.FromName(token);
            if (named.IsKnownColor || named.IsNamedColor || named.IsSystemColor)
            {
                color = named;
                return true;
            }

            return false;
        }

        private static string NormalizeColorToken(string raw)
        {
            if (string.IsNullOrWhiteSpace(raw)) return raw;
            if (!TryParseColor(raw, out var color))
            {
                return raw.Trim();
            }

            if (color.A == 0 && string.Equals(ExtractColorLeafToken(raw), "Transparent", StringComparison.OrdinalIgnoreCase))
            {
                return "Transparent";
            }

            // GeneXus color editor interoperates well with this canonical RGB token form.
            return $"{color.R}; {color.G}; {color.B}|";
        }

        private static string NormalizeColorTokenForSdkWrite(string raw)
        {
            if (string.IsNullOrWhiteSpace(raw)) return raw;

            string token = ExtractColorLeafToken(raw);
            if (string.IsNullOrWhiteSpace(token))
            {
                return raw.Trim();
            }

            // Prevent recursive wrapping like Color[Color[...]] when SDK stores string-based colors.
            if (string.Equals(token, "Transparent", StringComparison.OrdinalIgnoreCase))
            {
                return "Transparent";
            }

            // Prefer named color when possible to keep GX palette semantics stable.
            var known = System.Drawing.Color.FromName(token);
            if (known.IsKnownColor || known.IsNamedColor || known.IsSystemColor)
            {
                return known.Name;
            }

            var rgbMatch = System.Text.RegularExpressions.Regex.Match(
                token,
                @"^\s*(\d{1,3})\s*;\s*(\d{1,3})\s*;\s*(\d{1,3})\s*\|?\s*$");
            if (rgbMatch.Success)
            {
                if (int.TryParse(rgbMatch.Groups[1].Value, out int r) &&
                    int.TryParse(rgbMatch.Groups[2].Value, out int g) &&
                    int.TryParse(rgbMatch.Groups[3].Value, out int b))
                {
                    r = Math.Max(0, Math.Min(255, r));
                    g = Math.Max(0, Math.Min(255, g));
                    b = Math.Max(0, Math.Min(255, b));
                    return $"{r}; {g}; {b}|";
                }
            }

            return token;
        }

        private static string ExtractColorLeafToken(string raw)
        {
            if (string.IsNullOrWhiteSpace(raw)) return string.Empty;

            string token = raw.Trim();
            if (token.StartsWith("'", StringComparison.Ordinal) && token.EndsWith("'", StringComparison.Ordinal) && token.Length > 1)
            {
                token = token.Substring(1, token.Length - 2).Trim();
            }

            var matches = System.Text.RegularExpressions.Regex.Matches(token, @"\[(?<name>[^\[\]]+)\]");
            if (matches.Count > 0)
            {
                for (int i = matches.Count - 1; i >= 0; i--)
                {
                    string candidate = matches[i].Groups["name"].Value.Trim();
                    if (!string.Equals(candidate, "Color", StringComparison.OrdinalIgnoreCase))
                    {
                        return candidate;
                    }
                }
            }

            return token;
        }

        private static bool TrySetPropertyValueFallback(object instance, string propertyName, string value)
        {
            if (instance == null || string.IsNullOrWhiteSpace(propertyName)) return false;
            var type = instance.GetType();

            try
            {
                var setPropertyValue = type.GetMethod(
                    "SetPropertyValue",
                    BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
                    null,
                    new[] { typeof(string), typeof(object) },
                    null);
                if (setPropertyValue != null)
                {
                    setPropertyValue.Invoke(instance, new object[] { propertyName, value });
                    return true;
                }
            }
            catch { }

            try
            {
                var setPropertyValueString = type.GetMethod(
                    "SetPropertyValueString",
                    BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
                    null,
                    new[] { typeof(string), typeof(string) },
                    null);
                if (setPropertyValueString != null)
                {
                    setPropertyValueString.Invoke(instance, new object[] { propertyName, value });
                    return true;
                }
            }
            catch { }

            return false;
        }

        private static bool TryReadProperty(object instance, Type instanceType, string sdkPropertyName, out string value)
        {
            value = null;
            try
            {
                var prop = instanceType.GetProperty(sdkPropertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                if (prop != null && prop.CanRead)
                {
                    var raw = prop.GetValue(instance, null);
                    value = raw != null ? raw.ToString() : null;
                    return true;
                }
            }
            catch { }

            return false;
        }

        private static object GetLayoutInstance(KBObjectPart part)
        {
            var partType = part.GetType();
            var layoutProp = partType.GetProperty("MyLayout", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase)
                          ?? partType.GetProperty("Layout", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
            return layoutProp?.GetValue(part, null);
        }

        private static List<object> GetBandsList(object layout)
        {
            if (layout == null) return null;
            var bandsProp = layout.GetType().GetProperty("ReportBands", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
            var bandsCollection = bandsProp?.GetValue(layout, null) as System.Collections.IEnumerable;
            return bandsCollection?.Cast<object>().ToList();
        }

        private static string GetBandName(object band)
        {
            if (band == null) return null;
            return band.GetType().GetProperty("Name", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase)?.GetValue(band, null)?.ToString();
        }

        private static string GetBandControlName(object band)
        {
            if (band == null) return null;
            return band.GetType().GetProperty("ControlName", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase)?.GetValue(band, null)?.ToString();
        }

        private static object CloneBand(object sourceBand)
        {
            if (sourceBand == null) return null;
            try
            {
                var cloneMethod = typeof(object).GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic);
                return cloneMethod?.Invoke(sourceBand, null);
            }
            catch
            {
                return null;
            }
        }

        private static bool TryCloneSwapBand(object layout, List<object> currentBands, object oldBand, string newName)
        {
            if (layout == null || currentBands == null || oldBand == null || string.IsNullOrWhiteSpace(newName))
            {
                return false;
            }

            object replacement = CloneBand(oldBand);
            if (replacement == null) return false;

            var replacementType = replacement.GetType();
            bool renamed = TrySetProperty(replacement, replacementType, "Name", newName);
            renamed = TrySetProperty(replacement, replacementType, "ControlName", newName) || renamed;
            renamed = TrySetPropertyValueFallback(replacement, "Name", newName) || renamed;
            renamed = TrySetPropertyValueFallback(replacement, "ControlName", newName) || renamed;
            if (!renamed) return false;

            int oldIndex = currentBands.IndexOf(oldBand);
            if (oldIndex < 0) return false;

            if (!TryInsertBandAt(layout, replacement, oldIndex))
            {
                return false;
            }

            return TryRemoveBand(layout, oldBand, oldIndex + 1);
        }

        private static bool TryInsertBandBeforeFooter(object layout, List<object> currentBands, object newBand)
        {
            if (layout == null || currentBands == null || newBand == null) return false;
            int footerIndex = currentBands.FindIndex(b =>
                string.Equals(GetBandName(b), "footer", StringComparison.OrdinalIgnoreCase) ||
                string.Equals(GetBandControlName(b), "footer", StringComparison.OrdinalIgnoreCase));
            if (footerIndex < 0) return false;
            return TryInsertBandAt(layout, newBand, footerIndex);
        }

        private static bool TryInsertBandAt(object layout, object band, int index)
        {
            if (layout == null || band == null || index < 0) return false;

            var insertBandMethod = layout.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance)
                .FirstOrDefault(m =>
                    string.Equals(m.Name, "InsertBand", StringComparison.OrdinalIgnoreCase) &&
                    m.GetParameters().Length == 2 &&
                    m.GetParameters()[0].ParameterType == typeof(int) &&
                    m.GetParameters()[1].ParameterType.IsAssignableFrom(band.GetType()));

            if (insertBandMethod != null)
            {
                insertBandMethod.Invoke(layout, new object[] { index, band });
                return true;
            }

            var childrenProp = layout.GetType().GetProperty("Children", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
            var children = childrenProp?.GetValue(layout, null);
            if (children != null)
            {
                var insertMethod = children.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance)
                    .FirstOrDefault(m =>
                        string.Equals(m.Name, "Insert", StringComparison.OrdinalIgnoreCase) &&
                        m.GetParameters().Length == 2 &&
                        m.GetParameters()[0].ParameterType == typeof(int) &&
                        m.GetParameters()[1].ParameterType.IsAssignableFrom(band.GetType()));
                if (insertMethod != null)
                {
                    insertMethod.Invoke(children, new object[] { index, band });
                    return true;
                }
            }

            return false;
        }

        private static bool TryRemoveBand(object layout, object band, int indexHint)
        {
            if (layout == null || band == null) return false;

            var childrenProp = layout.GetType().GetProperty("Children", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
            var children = childrenProp?.GetValue(layout, null);
            if (children != null)
            {
                var removeMethod = children.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance)
                    .FirstOrDefault(m =>
                        string.Equals(m.Name, "Remove", StringComparison.OrdinalIgnoreCase) &&
                        m.GetParameters().Length == 1 &&
                        m.GetParameters()[0].ParameterType.IsAssignableFrom(band.GetType()));
                if (removeMethod != null)
                {
                    var removed = removeMethod.Invoke(children, new object[] { band });
                    if (removed is bool removedBool) return removedBool;
                    return true;
                }

                if (indexHint >= 0)
                {
                    var removeAtMethod = children.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance)
                        .FirstOrDefault(m =>
                            string.Equals(m.Name, "RemoveAt", StringComparison.OrdinalIgnoreCase) &&
                            m.GetParameters().Length == 1 &&
                            m.GetParameters()[0].ParameterType == typeof(int));
                    if (removeAtMethod != null)
                    {
                        removeAtMethod.Invoke(children, new object[] { indexHint });
                        return true;
                    }
                }
            }

            return false;
        }

        private static HashSet<string> CollectControlNames(IEnumerable<object> bands)
        {
            var names = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
            if (bands == null) return names;

            foreach (var band in bands)
            {
                var items = GetCollection(band, "Items", "Elements", "Controls", "Components");
                if (items == null) continue;
                foreach (var item in items)
                {
                    var type = item.GetType();
                    string n = type.GetProperty("Name", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase)?.GetValue(item, null)?.ToString();
                    string c = type.GetProperty("ControlName", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase)?.GetValue(item, null)?.ToString();
                    if (!string.IsNullOrWhiteSpace(n)) names.Add(n);
                    if (!string.IsNullOrWhiteSpace(c)) names.Add(c);
                }
            }

            return names;
        }

        private static void EnsureUniqueControlNames(object band, HashSet<string> usedNames)
        {
            if (band == null) return;
            if (usedNames == null) usedNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

            var items = GetCollection(band, "Items", "Elements", "Controls", "Components");
            if (items == null) return;

            foreach (var item in items)
            {
                var type = item.GetType();
                var nameProp = type.GetProperty("Name", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                var controlNameProp = type.GetProperty("ControlName", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);

                string current = nameProp?.GetValue(item, null)?.ToString();
                if (string.IsNullOrWhiteSpace(current))
                {
                    current = controlNameProp?.GetValue(item, null)?.ToString();
                }
                if (string.IsNullOrWhiteSpace(current))
                {
                    continue;
                }

                if (current.StartsWith("&", StringComparison.Ordinal))
                {
                    // Attribute controls can repeat across print blocks.
                    continue;
                }

                string candidate = current;
                int index = 1;
                while (usedNames.Contains(candidate))
                {
                    candidate = current + "_mcp" + index.ToString();
                    index++;
                }

                if (!string.Equals(candidate, current, StringComparison.OrdinalIgnoreCase))
                {
                    TrySetProperty(item, type, "Name", candidate);
                    TrySetProperty(item, type, "ControlName", candidate);
                    TrySetPropertyValueFallback(item, "Name", candidate);
                    TrySetPropertyValueFallback(item, "ControlName", candidate);
                }

                usedNames.Add(candidate);
            }
        }

        private static bool TryAddBandToCollection(object layout, object band)
        {
            if (layout == null || band == null) return false;
            var bandsProp = layout.GetType().GetProperty("ReportBands", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
            var bandsCollection = bandsProp?.GetValue(layout, null);
            if (bandsCollection != null)
            {
                var addMethod = bandsCollection.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance)
                    .FirstOrDefault(m =>
                        string.Equals(m.Name, "Add", StringComparison.OrdinalIgnoreCase) &&
                        m.GetParameters().Length == 1 &&
                        m.GetParameters()[0].ParameterType.IsAssignableFrom(band.GetType()));

                if (addMethod != null)
                {
                    addMethod.Invoke(bandsCollection, new[] { band });
                    return true;
                }
            }

            var childrenProp = layout.GetType().GetProperty("Children", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
            var childrenCollection = childrenProp?.GetValue(layout, null);
            if (childrenCollection != null)
            {
                var addMethod = childrenCollection.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance)
                    .FirstOrDefault(m =>
                        string.Equals(m.Name, "Add", StringComparison.OrdinalIgnoreCase) &&
                        m.GetParameters().Length == 1 &&
                        m.GetParameters()[0].ParameterType.IsAssignableFrom(band.GetType()));

                if (addMethod != null)
                {
                    addMethod.Invoke(childrenCollection, new[] { band });
                    return true;
                }
            }

            return false;
        }

        private static System.Collections.IEnumerable GetCollection(object target, params string[] propNames)
        {
            if (target == null) return null;
            var t = target.GetType();
            foreach (var name in propNames)
            {
                var p = t.GetProperty(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                if (p != null) return p.GetValue(target, null) as System.Collections.IEnumerable;
            }
            return null;
        }

        private static void TryMarkLayoutDirty(KBObjectPart part, object layout)
        {
            if (part == null || layout == null) return;
            try
            {
                var partType = part.GetType();

                var myLayoutProp = partType.GetProperty("MyLayout", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.IgnoreCase);
                if (myLayoutProp != null && myLayoutProp.CanWrite)
                {
                    myLayoutProp.SetValue(part, layout, null);
                }

                var layoutProp = partType.GetProperty("Layout", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.IgnoreCase);
                if (layoutProp != null && layoutProp.CanWrite)
                {
                    layoutProp.SetValue(part, layout, null);
                }

                var updateMethod = partType.GetMethod("UpdateMyLayout", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.IgnoreCase);
                if (updateMethod != null)
                {
                    updateMethod.Invoke(part, new[] { layout });
                }

                var dirtyProp = partType.GetProperty("Dirty", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.IgnoreCase);
                if (dirtyProp != null && dirtyProp.CanWrite && dirtyProp.PropertyType == typeof(bool))
                {
                    dirtyProp.SetValue(part, true, null);
                }
            }
            catch (Exception ex)
            {
                Logger.Warn("ReportLayoutHelper.TryMarkLayoutDirty warning: " + ex.Message);
            }
        }
    }
}
