/*
┌──────────────────────────────────────────────────────────────────┐
│ Author: Ivan Murzak (https://github.com/IvanMurzak) │
│ Repository: GitHub (https://github.com/IvanMurzak/Unity-MCP) │
│ Copyright (c) 2025 Ivan Murzak │
│ Licensed under the Apache License, Version 2.0. │
│ See the LICENSE file in the project root for more information. │
└──────────────────────────────────────────────────────────────────┘
*/
#nullable enable
using System.Collections.Generic;
using com.IvanMurzak.ReflectorNet;
using com.IvanMurzak.ReflectorNet.Model;
using Microsoft.Extensions.Logging;
namespace com.IvanMurzak.Unity.MCP.Editor.API
{
///
/// Shared helpers for path-based read tools. Centralises the construction of an aggregate
/// envelope whose fields mirror one
/// call per requested path. The envelope is the same
/// shape callers already expect from the legacy single-call Serialize path, so
/// path-scoped reads slot in without breaking the response schema.
///
internal static class PathReadHelper
{
public const string PathReadAggregateTypeName = "Unity-MCP.PathReadAggregate";
public const string EmptyPathTypeName = "";
public const string UnresolvedTypeName = "";
///
/// Reads each path via and aggregates the results into a single
/// envelope: top-level fields[i].name is the requested path,
/// fields[i] contents are the serialised value at that path. When a path fails to navigate,
/// the entry is replaced with a sentinel field whose name is the path and value is null. Per-path
/// diagnostics are emitted via and, when supplied, also appended to
/// so callers that maintain a structured log accumulator can
/// surface them back to the AI agent.
///
/// The reflector that performs the per-path read.
/// Object to read paths from.
/// Optional name for the aggregate envelope.
/// Paths to read. Null or empty entries are skipped with an
/// sentinel field rather than silently navigating
/// the root (which would defeat the token-saving purpose of path-scoped reads).
/// Optional logger for per-path diagnostics.
/// Optional structured log accumulator. When non-null,
/// per-path failure entries are appended so the caller can surface them in its response.
public static SerializedMember BuildPathReadAggregate(
Reflector reflector,
object obj,
string? rootName,
IReadOnlyList paths,
ILogger? logger,
Logs? aggregateLogs = null)
{
var fields = new SerializedMemberList(paths.Count);
for (var i = 0; i < paths.Count; i++)
{
var path = paths[i];
if (string.IsNullOrEmpty(path))
{
fields.Add(new SerializedMember
{
name = path,
typeName = EmptyPathTypeName
});
var emptyMsg = $"[path-read] paths[{i}] is empty or null and was skipped.";
logger?.LogWarning(emptyMsg);
aggregateLogs?.Warning(emptyMsg);
continue;
}
var perPathLogs = new Logs();
if (reflector.TryReadAt(obj, path, out var member, logs: perPathLogs, logger: logger) && member != null)
{
member.name = path;
fields.Add(member);
}
else
{
fields.Add(new SerializedMember
{
name = path,
typeName = UnresolvedTypeName
});
foreach (var entry in perPathLogs)
{
var msg = $"[path-read] '{path}': {entry}";
logger?.LogWarning(msg);
aggregateLogs?.Warning(msg);
}
}
}
return new SerializedMember
{
name = rootName,
typeName = PathReadAggregateTypeName,
fields = fields
};
}
}
}