TSTypeFile EnsureInitialized(IActivityMonitor monitor, TSTypeFile f, ref HashSet <Type>?cycleDetector)
        {
            if (!f.IsInitialized)
            {
                TypeScriptAttribute attr = f.Attribute;
                var generators           = f.Generators;
                var t = f.Type;

                ITSCodeGenerator?globalControl = null;
                foreach (var g in _globals)
                {
                    _success &= g.ConfigureTypeScriptAttribute(monitor, this, t, attr, generators, ref globalControl);
                }
                if (globalControl == null)
                {
                    if (generators.Count > 0)
                    {
                        foreach (var g in generators)
                        {
                            _success &= g.ConfigureTypeScriptAttribute(monitor, attr, generators);
                        }
                    }
                }

                NormalizedPath folder;
                string?        fileName  = null;
                Type?          refTarget = attr.SameFileAs ?? attr.SameFolderAs;
                if (refTarget != null)
                {
                    if (cycleDetector == null)
                    {
                        cycleDetector = new HashSet <Type>();
                    }
                    if (!cycleDetector.Add(t))
                    {
                        throw new InvalidOperationException($"TypeScript.SameFoldeAs cycle detected: {cycleDetector.Select( c => c.Name ).Concatenate( " => " )}.");
                    }

                    var target = DoGetTSTypeFile(monitor, refTarget, ref cycleDetector);
                    folder = target.Folder;
                    if (attr.SameFileAs != null)
                    {
                        fileName = target.FileName;
                    }
                }
                else
                {
                    folder = attr.Folder ?? t.Namespace !.Replace('.', '/');
                }
                var defName = t.GetExternalName() ?? t.Name;
                fileName ??= attr.FileName ?? (defName + ".ts");
                string typeName = attr.TypeName ?? defName;
                f.Initialize(folder, fileName, typeName, globalControl);
            }
            return(f);
        }
        TSTypeFile DoGetTSTypeFile(IActivityMonitor monitor, Type t, ref HashSet <Type>?cycleDetector)
        {
            TSTypeFile?f = _typeMappings.GetValueOrDefault(t);

            if (f == null)
            {
                Debug.Assert(!_attributeCache.ContainsKey(t));
                f = new TSTypeFile(this, t, Array.Empty <ITSCodeGeneratorType>(), null);
                _typeMappings.Add(t, f);
                _typeFiles.Add(f);
            }
            if (!f.IsInitialized)
            {
                EnsureInitialized(monitor, f, ref cycleDetector);
            }
            return(f);
        }
        internal bool BuildTSTypeFilesFromAttributes(IActivityMonitor monitor)
        {
            List <ITSCodeGenerator>?globals = null;
            // Reused per type.
            TypeScriptImpl?impl = null;
            List <ITSCodeGeneratorType> generators = new List <ITSCodeGeneratorType>();

            foreach (var attributeCache in _attributeCache.Values)
            {
                impl = null;
                generators.Clear();

                foreach (var m in attributeCache.GetTypeCustomAttributes <ITSCodeGeneratorAutoDiscovery>())
                {
                    if (m is ITSCodeGenerator g)
                    {
                        if (globals == null)
                        {
                            globals = new List <ITSCodeGenerator>();
                        }
                        globals.Add(g);
                    }
                    if (m is TypeScriptImpl a)
                    {
                        impl = a;
                    }
                    if (m is ITSCodeGeneratorType tG)
                    {
                        generators.Add(tG);
                    }
                }
                if (impl != null || generators.Count > 0)
                {
                    var f = new TSTypeFile(this, attributeCache.Type, generators.ToArray(), impl?.Attribute);
                    _typeMappings.Add(attributeCache.Type, f);
                    _typeFiles.Add(f);
                }
            }
            _globals = (IReadOnlyList <ITSCodeGenerator>?)globals ?? Array.Empty <ITSCodeGenerator>();
            return(_success);
        }