using Nexus.Api.Models;

namespace Nexus.Api.Services;

public sealed class DocService(
    IOpenClawAgentConfigurationService configuration) : IDocService
{
    private static readonly HashSet<string> AllowedExtensions =
        new(
            [".md", ".json", ".txt", ".yaml", ".yml", ".html", ".css"],
            StringComparer.OrdinalIgnoreCase);

    private static readonly (string Path, string Category)[] ScanDirectories =
    [
        ("", "workspace"),
        ("nexus-phases", "phases"),
        ("skills", "skills"),
        ("nexus", "nexus"),
        ("nexus/phases", "nexus-phases")
    ];

    public async Task<IReadOnlyList<DocFileInfo>> GetAllAsync(
        string agentId,
        CancellationToken cancellationToken = default)
    {
        var normalizedAgentId = NormalizeAgentId(agentId);
        var directories = await OpenClawContentReadHelpers.SelectBoundedAsync<
            (string Path, string Category),
            DirectoryResult>(
            ScanDirectories,
            async (directory, token) =>
            {
                try
                {
                    var listing = await configuration.GetWorkspaceAsync(
                        normalizedAgentId,
                        directory.Path,
                        0,
                        100,
                        token);
                    return new DirectoryResult(
                        directory.Category,
                        listing);
                }
                catch (OpenClawGatewayRpcException exception)
                    when (OpenClawContentReadHelpers.IsNotFound(exception))
                {
                    return null;
                }
            },
            cancellationToken);

        return directories
            .SelectMany(directory => directory.Listing.Entries
                .Where(entry =>
                    string.Equals(entry.Kind, "file", StringComparison.Ordinal)
                    && !(string.IsNullOrEmpty(directory.Listing.Path)
                         && string.Equals(
                             entry.Name,
                             "MEMORY.md",
                             StringComparison.OrdinalIgnoreCase))
                    && AllowedExtensions.Contains(
                        Path.GetExtension(entry.Name)))
                .Select(entry => new DocFileInfo(
                    entry.Name,
                    entry.Path,
                    directory.Category,
                    Path.GetExtension(entry.Name).TrimStart('.')
                        .ToLowerInvariant(),
                    entry.Size ?? 0,
                    (entry.UpdatedAt
                        ?? directory.Listing.CheckedAt).UtcDateTime,
                    normalizedAgentId,
                    entry.Path)))
            .OrderByDescending(item => item.ModifiedAt)
            .Take(100)
            .ToArray();
    }

    public async Task<DocFileContent?> GetFileAsync(
        string path,
        string agentId,
        CancellationToken cancellationToken = default)
    {
        if (string.IsNullOrWhiteSpace(path)
            || !AllowedExtensions.Contains(Path.GetExtension(path)))
        {
            return null;
        }

        var normalizedAgentId = NormalizeAgentId(agentId);
        try
        {
            var file = await configuration.GetWorkspaceFileAsync(
                normalizedAgentId,
                path,
                cancellationToken);
            var content = OpenClawContentReadHelpers.ReadText(file);
            if (content is null
                || !AllowedExtensions.Contains(Path.GetExtension(file.Name)))
            {
                return null;
            }

            return new DocFileContent(
                file.Name,
                file.Path,
                content,
                file.Size,
                (file.UpdatedAt ?? file.CheckedAt).UtcDateTime,
                normalizedAgentId,
                file.Path);
        }
        catch (OpenClawGatewayRpcException exception)
            when (OpenClawContentReadHelpers.IsNotFound(exception))
        {
            return null;
        }
    }

    private static string NormalizeAgentId(string? agentId)
        => string.IsNullOrWhiteSpace(agentId)
            ? "iris"
            : agentId.Trim().ToLowerInvariant();

    private sealed record DirectoryResult(
        string Category,
        OpenClawWorkspaceCollectionDto Listing);
}
