Editor/Core/UnityAsciiFbxLinks.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace ImportUnityPackage;

/// <summary>Legacy ASCII FBX attaches texture/material objects to the mesh by name.</summary>
internal sealed class UnityAsciiFbxLinks
{
	readonly Dictionary<string, string> textures = new( StringComparer.Ordinal );
	readonly List<(string Child, string Parent)> connections = new();
	int depth;
	int objectsDepth = -1;
	int textureDepth = -1;
	string texture;

	public void Observe( string line )
	{
		if ( Regex.IsMatch( line, @"^\s*Objects:\s*\{" ) ) objectsDepth = depth + 1;
		if ( objectsDepth > 0 && depth == objectsDepth )
		{
			var start = Regex.Match( line, "^\\s*Texture:\\s*\"(Texture::[^\"]+)\"" );
			if ( start.Success ) { texture = start.Groups[1].Value; textureDepth = depth + 1; }
		}
		if ( texture != null && depth >= textureDepth )
		{
			var file = Regex.Match( line, "^\\s*(?:FileName|Filename|RelativeFilename):\\s*\"([^\"]+)\"" );
			if ( file.Success ) textures[texture] = file.Groups[1].Value.Replace( '\\', '/' ).Split( '/' ).Last();
		}
		var connection = Regex.Match( line, "^\\s*Connect:\\s*\"OO\",\\s*\"([^\"]+)\",\\s*\"([^\"]+)\"" );
		if ( connection.Success ) connections.Add( (connection.Groups[1].Value, connection.Groups[2].Value) );
		var structural = line.Contains( '"' ) ? Regex.Replace( line, "\"[^\"]*\"", "" ) : line;
		structural = structural.Split( ';' )[0];
		depth += structural.Count( c => c == '{' ) - structural.Count( c => c == '}' );
		if ( depth < textureDepth ) { texture = null; textureDepth = -1; }
		if ( depth < objectsDepth ) objectsDepth = -1;
	}

	public IEnumerable<KeyValuePair<string, string>> Resolve()
	{
		var candidates = new List<(string Material, string File)>();
		foreach ( var mesh in connections.Where( c => c.Parent.StartsWith( "Model::", StringComparison.Ordinal ) ).GroupBy( c => c.Parent ) )
		{
			var materials = mesh.Select( c => c.Child ).Where( c => c.StartsWith( "Material::", StringComparison.Ordinal ) ).Distinct().ToArray();
			var maps = mesh.Select( c => c.Child ).Where( textures.ContainsKey ).Distinct().ToArray();
			// Without polygon-slot information, multiple materials/textures are ambiguous.
			if ( materials.Length == 1 && maps.Length == 1 ) candidates.Add( (materials[0]["Material::".Length..], textures[maps[0]]) );
		}
		foreach ( var material in candidates.GroupBy( c => c.Material ) )
		{
			var files = material.Select( c => c.File ).Distinct( StringComparer.OrdinalIgnoreCase ).ToArray();
			if ( files.Length == 1 ) yield return new( material.Key, files[0] );
		}
	}
}