Editor/AssetBrowserAddon/locations/FilteredLocation.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Editor;
using Sandbox;

namespace Sandbox.AssetBrowserAddon;

/// <summary>
/// Location implementation that filters assets based on a serialized definition.
/// </summary>
public record FilteredLocation : LocalAssetBrowser.Location
{
    public CustomLocationDefinition Definition { get; }

    public override bool IsAggregate => true;

    public FilteredLocation(CustomLocationDefinition definition)
        : base(definition?.Name ?? "Custom Location",
            string.IsNullOrWhiteSpace(definition?.Icon) ? "extension" : definition.Icon)
    {
        Definition = definition ?? throw new ArgumentNullException(nameof(definition));
        Path = $"@custom/{Definition.Id}";
        Icon = string.IsNullOrWhiteSpace(Definition.Icon) ? Icon : Definition.Icon;
    }

    public override bool CanGoUp() => false;
    public override IEnumerable<LocalAssetBrowser.Location> GetDirectories() => Enumerable.Empty<LocalAssetBrowser.Location>();

    public override IEnumerable<FileInfo> GetFiles()
    {
        return AssetSystem.All
            .Where(IsMatch)
            .OrderBy(asset => asset.RelativePath, StringComparer.OrdinalIgnoreCase)
            .Select(asset => new FileInfo(asset.AbsolutePath));
    }

    private bool IsMatch(Asset asset)
    {
        var rel = Normalize(asset.RelativePath);
        var abs = Normalize(asset.AbsolutePath); // add this

        if (asset is null)
            return false;

        if (Definition.ProjectAssetsOnly)
        {
            var projectRoot = Normalize(Project.Current?.GetRootPath());
            if (!string.IsNullOrEmpty(projectRoot))
            {
                var assetPath = Normalize(asset.AbsolutePath);
                if (!assetPath.StartsWith(projectRoot, StringComparison.OrdinalIgnoreCase))
                    return false;
            }
        }

        if (Definition.AssetTypes is { Count: > 0 })
        {
            var extension = NormalizeExtension(asset.AssetType?.FileExtension ?? System.IO.Path.GetExtension(asset.AbsolutePath));
            var matchesType = Definition.AssetTypes.Any(tag =>
                string.Equals(tag, extension, StringComparison.OrdinalIgnoreCase));

            if (!matchesType)
                return false;
        }

        var relative = Normalize(asset.RelativePath);

        // include
        if (Definition.IncludeFolders is { Count: > 0 })
        {
            var inc = Definition.IncludeFolders.Any(p =>
            {
                var pat = Normalize(p);
                return rel.Contains(pat) || abs.Contains(pat);
            });
            if (!inc) return false;
        }
        // exclude
        if (Definition.ExcludeFolders is { Count: > 0 })
        {
            var exc = Definition.ExcludeFolders.Any(p =>
            {
                var pat = Normalize(p);
                return rel.Contains(pat) || abs.Contains(pat);
            });
            if (exc) return false;
        }

        return true;
    }

    private static string Normalize(string value)
    {
        if (string.IsNullOrWhiteSpace(value))
            return string.Empty;

        return value.Replace('\\', '/').ToLowerInvariant();
    }

    private static string NormalizeExtension(string value)
    {
        if (string.IsNullOrWhiteSpace(value))
            return string.Empty;

        return value.Trim().TrimStart('.').ToLowerInvariant();
    }
}