using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.AdminUI.ScriptAnalysis;

namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.ScriptAnalysis;

/// <summary>
///     Covers the two {{equip}}-aware editor niceties: hover on a path literal containing the
///     <c>{{equip}}</c> token shows an "equipment-relative" note (not the "not a known configured
///     tag path" warning), and completion after <c>{{equip}}.</c> offers the attribute leaf names.
/// </summary>
public sealed class EquipTokenEditorTests
{
    /// <summary>A fake catalog whose configured paths all share an object prefix, so the attribute
    /// leaf names ("Source", "Other") are what {{equip}}. completion should surface.</summary>
    private sealed class FakeCatalog : IScriptTagCatalog
    {
        private static readonly string[] Paths = { "Mixer_001.Source", "Mixer_001.Other" };

        public Task<IReadOnlyList<string>> GetPathsAsync(string? filter, CancellationToken ct)
            => Task.FromResult<IReadOnlyList<string>>(Paths);

        public Task<ScriptTagInfo?> GetTagInfoAsync(string path, CancellationToken ct)
            => Task.FromResult<ScriptTagInfo?>(null);

        public Task<IReadOnlyList<string>> GetEquipmentRelativeLeavesAsync(string? filter, CancellationToken ct)
        {
            IEnumerable<string> leaves = new[] { "Source", "Other" };
            if (!string.IsNullOrWhiteSpace(filter))
                leaves = leaves.Where(n => n.StartsWith(filter, StringComparison.OrdinalIgnoreCase));
            return Task.FromResult<IReadOnlyList<string>>(leaves.ToList());
        }
    }

    private static readonly ScriptAnalysisService Svc = new(new FakeCatalog());

    private static async Task<IReadOnlyList<CompletionItem>> Items(string code, int line, int col)
        => (await Svc.CompleteAsync(new CompletionsRequest(code, line, col))).Items;

    [Fact] public async Task Completion_after_equip_dot_offers_attribute_leaves()
    {
        // caret right after the dot of "{{equip}}." — column 30 sits between the trailing dot and
        // the closing quote of ctx.GetTag("{{equip}}.").
        var items = await Items("""return ctx.GetTag("{{equip}}.").Value;""", 1, 30);
        items.Select(i => i.Label).ShouldContain("{{equip}}.Source");
        items.ShouldAllBe(i => i.Detail == "tag path");
    }

    [Fact] public async Task Completion_after_equip_dot_inserts_the_full_token_qualified_leaf()
    {
        var items = await Items("""return ctx.GetTag("{{equip}}.").Value;""", 1, 30);
        var source = items.FirstOrDefault(i => i.Label == "{{equip}}.Source");
        source.ShouldNotBeNull();
        source!.InsertText.ShouldBe("{{equip}}.Source");
    }

    [Fact] public async Task Hover_on_equip_token_literal_notes_equipment_relative()
    {
        // caret inside ctx.GetTag("{{equip}}.Source")
        var md = (await Svc.Hover(new HoverRequest("""return ctx.GetTag("{{equip}}.Source").Value;""", 1, 24))).Markdown;
        md.ShouldNotBeNull();
        md!.ShouldContain("Equipment-relative");
        // the {{equip}} token must render literally (non-interpolated markdown segment)
        md.ShouldContain("{{equip}}");
        md.ShouldNotContain("Not a known");
    }
}
