using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates;

namespace ZB.MOM.WW.ScadaBridge.TemplateEngine;

/// <summary>
/// Resolves the hierarchical ("qualified") name of a composition-derived
/// template. A derived template stores only its <em>contained</em> name — the
/// owning composition slot's <c>InstanceName</c>, unique only within that owner.
/// The qualified path (<c>Owner.Slot.Slot…</c>) is computed on demand by
/// walking the <see cref="Template.OwnerCompositionId"/> chain up to the base
/// template.
/// </summary>
public static class TemplateNaming
{
    /// <summary>
    /// Returns the dotted hierarchical name of <paramref name="template"/>. For
    /// a base (non-derived) template this is just its stored name. The walk is
    /// null-safe: if any owner link is missing from the supplied lookups it
    /// stops and falls back to the stored contained name, and a cycle (which
    /// the composition graph should never contain) is broken defensively.
    /// </summary>
    /// <param name="template">The template whose qualified name to compute.</param>
    /// <param name="byId">Lookup of all templates by primary key.</param>
    /// <param name="compById">Lookup of all template compositions by primary key.</param>
    /// <returns>The dotted hierarchical name (e.g., <c>Owner.Slot.Name</c>).</returns>
    public static string QualifiedName(
        Template template,
        IReadOnlyDictionary<int, Template> byId,
        IReadOnlyDictionary<int, TemplateComposition> compById)
    {
        ArgumentNullException.ThrowIfNull(template);
        ArgumentNullException.ThrowIfNull(byId);
        ArgumentNullException.ThrowIfNull(compById);

        return Resolve(template, byId, compById, new HashSet<int>());
    }

    private static string Resolve(
        Template template,
        IReadOnlyDictionary<int, Template> byId,
        IReadOnlyDictionary<int, TemplateComposition> compById,
        HashSet<int> visited)
    {
        // Base template, broken owner link, or a cycle → the stored name is the
        // best (and contained) answer.
        if (!template.IsDerived
            || template.OwnerCompositionId is not { } compId
            || !compById.TryGetValue(compId, out var composition)
            || !byId.TryGetValue(composition.TemplateId, out var owner)
            || !visited.Add(template.Id))
        {
            return template.Name;
        }

        return $"{Resolve(owner, byId, compById, visited)}.{composition.InstanceName}";
    }
}
