package p.studio.workspaces.assets.list;

import p.studio.workspaces.assets.messages.AssetListGroup;
import p.studio.workspaces.assets.messages.AssetListProjection;
import p.studio.workspaces.assets.messages.AssetWorkspaceAssetState;
import p.studio.workspaces.assets.messages.AssetWorkspaceAssetSummary;

import java.nio.file.Path;
import java.util.*;

public final class AssetListProjectionBuilder {
    private AssetListProjectionBuilder() {
    }

    public static AssetListProjection build(
            List<AssetWorkspaceAssetSummary> assets,
            Path projectRoot,
            String searchQuery,
            Set<AssetListFilter> filters) {
        Objects.requireNonNull(assets, "assets");
        final Path normalizedProjectRoot = Objects.requireNonNull(projectRoot, "projectRoot").toAbsolutePath().normalize();
        final String normalizedQuery = normalizeQuery(searchQuery);
        final Set<AssetListFilter> normalizedFilters = filters == null || filters.isEmpty()
                ? EnumSet.noneOf(AssetListFilter.class)
                : EnumSet.copyOf(filters);

        final Map<String, List<AssetWorkspaceAssetSummary>> grouped = new LinkedHashMap<>();
        for (AssetWorkspaceAssetSummary asset : assets) {
            if (!matchesFilters(asset, normalizedFilters) || !matchesQuery(asset, normalizedProjectRoot, normalizedQuery)) {
                continue;
            }
            grouped.computeIfAbsent(groupLabel(asset, normalizedProjectRoot), ignored -> new ArrayList<>())
                    .add(asset);
        }

        final List<AssetListGroup> groups = grouped.entrySet().stream()
                .map(entry -> new AssetListGroup(entry.getKey(), entry.getValue()))
                .toList();
        final int visibleAssetCount = groups.stream().mapToInt(group -> group.assets().size()).sum();
        return new AssetListProjection(groups, visibleAssetCount);
    }

    public static String relativeRoot(AssetWorkspaceAssetSummary asset, Path projectRoot) {
        return relativize(asset.assetRoot(), projectRoot).toString().replace('\\', '/');
    }

    private static boolean matchesFilters(AssetWorkspaceAssetSummary asset, Set<AssetListFilter> filters) {
        if (filters.isEmpty()) {
            return true;
        }

        final boolean includeUnregistered = filters.contains(AssetListFilter.UNREGISTERED);
        if (includeUnregistered) {
            final boolean stateMatches = asset.state() == AssetWorkspaceAssetState.UNREGISTERED;
            if (!stateMatches) {
                return false;
            }
        }

        if (filters.contains(AssetListFilter.DIAGNOSTICS) && !asset.hasDiagnostics()) {
            return false;
        }

        if (filters.contains(AssetListFilter.PRELOAD) && !asset.preload()) {
            return false;
        }

        return true;
    }

    private static boolean matchesQuery(AssetWorkspaceAssetSummary asset, Path projectRoot, String normalizedQuery) {
        if (normalizedQuery.isBlank()) {
            return true;
        }

        final String relativeRoot = relativeRoot(asset, projectRoot);
        return asset.assetName().toLowerCase(Locale.ROOT).contains(normalizedQuery)
                || asset.assetFamily().matchesQuery(normalizedQuery)
                || relativeRoot.toLowerCase(Locale.ROOT).contains(normalizedQuery);
    }

    private static String groupLabel(AssetWorkspaceAssetSummary asset, Path projectRoot) {
        final Path relativeRoot = relativize(asset.assetRoot(), projectRoot);
        final Path parent = relativeRoot.getParent();
        return Objects.requireNonNullElse(parent, relativeRoot).toString().replace('\\', '/');
    }

    private static Path relativize(Path assetRoot, Path projectRoot) {
        try {
            return projectRoot.relativize(assetRoot.toAbsolutePath().normalize());
        } catch (IllegalArgumentException ignored) {
            return assetRoot.getFileName();
        }
    }

    private static String normalizeQuery(String query) {
        return query == null ? "" : query.trim().toLowerCase(Locale.ROOT);
    }
}
