// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using Microsoft.AspNetCore.JsonPatch.Adapters;
using Newtonsoft.Json.Serialization;

namespace Microsoft.AspNetCore.JsonPatch.Internal;

/// <summary>
/// This API supports infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class ObjectVisitor
{
    private readonly IAdapterFactory _adapterFactory;
    private readonly IContractResolver _contractResolver;
    private readonly ParsedPath _path;

    /// <summary>
    /// Initializes a new instance of <see cref="ObjectVisitor"/>.
    /// </summary>
    /// <param name="path">The path of the JsonPatch operation</param>
    /// <param name="contractResolver">The <see cref="IContractResolver"/>.</param>
    public ObjectVisitor(ParsedPath path, IContractResolver contractResolver)
        : this(path, contractResolver, AdapterFactory.Default)
    {
    }

    /// <summary>
    /// Initializes a new instance of <see cref="ObjectVisitor"/>.
    /// </summary>
    /// <param name="path">The path of the JsonPatch operation</param>
    /// <param name="contractResolver">The <see cref="IContractResolver"/>.</param>
    /// <param name="adapterFactory">The <see cref="IAdapterFactory"/> to use when creating adaptors.</param>
    public ObjectVisitor(ParsedPath path, IContractResolver contractResolver, IAdapterFactory adapterFactory)
    {
        _path = path;
        _contractResolver = contractResolver ?? throw new ArgumentNullException(nameof(contractResolver));
        _adapterFactory = adapterFactory ?? throw new ArgumentNullException(nameof(adapterFactory));
    }

    public bool TryVisit(ref object target, out IAdapter adapter, out string errorMessage)
    {
        if (target == null)
        {
            adapter = null;
            errorMessage = null;
            return false;
        }

        adapter = SelectAdapter(target);

        // Traverse until the penultimate segment to get the target object and adapter
        for (var i = 0; i < _path.Segments.Count - 1; i++)
        {
            if (!adapter.TryTraverse(target, _path.Segments[i], _contractResolver, out var next, out errorMessage))
            {
                adapter = null;
                return false;
            }

            // If we hit a null on an interior segment then we need to stop traversing.
            if (next == null)
            {
                adapter = null;
                return false;
            }

            target = next;
            adapter = SelectAdapter(target);
        }

        errorMessage = null;
        return true;
    }

    private IAdapter SelectAdapter(object targetObject)
    {
        return _adapterFactory.Create(targetObject, _contractResolver);
    }
}
