using System.Text;
using Motus.Recorder.CodeEmit;
using Motus.Recorder.PageAnalysis;

namespace Motus.Recorder.PomEmit;

/// <summary>
/// Emits a strongly-typed C# partial class (Page Object Model) from discovered page elements.
/// </summary>
public sealed class PomEmitter
{
    /// <summary>
    /// Generates a <c>.g.cs</c> file containing a partial page object class.
    /// </summary>
    public string Emit(IReadOnlyList<DiscoveredElement> elements, PomEmitOptions? options = null)
        => EmitWithMetadata(elements, options).Source;

    /// <summary>
    /// Generates a <c>.g.cs</c> file and returns per-locator metadata for manifest emission.
    /// </summary>
    public PomEmitResult EmitWithMetadata(
        IReadOnlyList<DiscoveredElement> elements, PomEmitOptions? options = null)
    {
        options ??= new PomEmitOptions();
        var sb = new StringBuilder();
        var locators = new List<EmittedLocator>();
        var currentLine = 0;

        void AppendLine(string text)
        {
            sb.AppendLine(text);
            currentLine++;
        }

        void AppendBlank()
        {
            sb.AppendLine();
            currentLine++;
        }

        // Header
        AppendLine("// <auto-generated/>");
        AppendLine("using Motus.Abstractions;");
        AppendBlank();
        AppendLine($"namespace {options.Namespace};");
        AppendBlank();
        AppendLine($"public partial class {options.ClassName}");
        AppendLine("{");

        // Constructor and navigation
        AppendLine("    private readonly IPage _page;");
        AppendBlank();
        AppendLine($"    public {options.ClassName}(IPage page) => _page = page;");
        AppendBlank();
        AppendLine($"    public Task NavigateAsync() => _page.GotoAsync(\"{EscapeString(options.PageUrl)}\");");

        // Locators
        if (elements.Count > 0)
        {
            AppendBlank();
            AppendLine("    // Locators");
            foreach (var el in elements)
            {
                if (el.Selector is not null)
                {
                    AppendLine($"    public ILocator {el.MemberName} => _page.Locator(\"{EscapeString(el.Selector)}\");");

                    locators.Add(new EmittedLocator(
                        Selector: el.Selector,
                        LocatorMethod: "Locator",
                        SourceLine: currentLine,
                        PageUrl: options.PageUrl,
                        BackendNodeId: el.BackendNodeId));
                }
                else
                {
                    AppendLine($"    // TODO: No unique selector found for {el.MemberName} ({el.Info.Tag}[{el.Info.ElementIndex}])");
                }
            }
        }

        // Form actions
        var formGroups = FormGrouper.GroupByForm(elements);
        if (formGroups.Count > 0)
        {
            AppendBlank();
            AppendLine("    // Form actions");
            foreach (var group in formGroups)
            {
                EmitFormAction(sb, group, ref currentLine);
            }
        }

        AppendLine("}");
        return new PomEmitResult(sb.ToString(), locators);
    }

    private static void EmitFormAction(StringBuilder sb, FormGrouper.FormGroup group, ref int currentLine)
    {
        // Build method name from submit button
        var methodName = $"Submit{group.SubmitButton!.MemberName.Replace("Button", "")}FormAsync";

        // Build parameter list
        var parameters = new List<string>();
        foreach (var input in group.FillableInputs)
        {
            var paramName = ToCamelCase(input.MemberName.Replace("Input", "").Replace("Element", ""));
            if (string.IsNullOrEmpty(paramName))
                paramName = $"field{input.Info.ElementIndex}";
            parameters.Add($"string {paramName}");
        }

        sb.AppendLine($"    public async Task {methodName}({string.Join(", ", parameters)})");
        currentLine++;
        sb.AppendLine("    {");
        currentLine++;

        // Fill calls
        foreach (var input in group.FillableInputs)
        {
            var paramName = ToCamelCase(input.MemberName.Replace("Input", "").Replace("Element", ""));
            if (string.IsNullOrEmpty(paramName))
                paramName = $"field{input.Info.ElementIndex}";
            sb.AppendLine($"        await {input.MemberName}.FillAsync({paramName});");
            currentLine++;
        }

        // Submit click
        sb.AppendLine($"        await {group.SubmitButton.MemberName}.ClickAsync();");
        currentLine++;
        sb.AppendLine("    }");
        currentLine++;
    }

    private static string ToCamelCase(string pascalCase)
    {
        if (string.IsNullOrEmpty(pascalCase))
            return pascalCase;
        return char.ToLowerInvariant(pascalCase[0]) + pascalCase[1..];
    }

    private static string EscapeString(string value)
        => value
            .Replace("\\", "\\\\")
            .Replace("\"", "\\\"")
            .Replace("\n", "\\n")
            .Replace("\r", "\\r")
            .Replace("\t", "\\t");
}
