// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using osu.Framework.Graphics;

namespace osu.Game.Screens.Edit
{
    /// <summary>
    /// A component that tracks a batch change, only applying after all active changes are completed.
    /// </summary>
    public abstract partial class TransactionalCommitComponent : Component
    {
        /// <summary>
        /// Fires whenever a transaction begins. Will not fire on nested transactions.
        /// </summary>
        public event Action? TransactionBegan;

        /// <summary>
        /// Fires when the last transaction completes.
        /// </summary>
        public event Action? TransactionEnded;

        /// <summary>
        /// Fires when <see cref="SaveState"/> is called and results in a non-transactional state save.
        /// </summary>
        public event Action? SaveStateTriggered;

        public bool TransactionActive => bulkChangesStarted > 0;

        private int bulkChangesStarted;

        /// <summary>
        /// Signal the beginning of a change.
        /// </summary>
        public virtual void BeginChange()
        {
            if (bulkChangesStarted++ == 0)
                TransactionBegan?.Invoke();
        }

        /// <summary>
        /// Signal the end of a change.
        /// </summary>
        /// <exception cref="InvalidOperationException">Throws if <see cref="BeginChange"/> was not first called.</exception>
        public void EndChange()
        {
            if (bulkChangesStarted == 0)
                throw new InvalidOperationException($"Cannot call {nameof(EndChange)} without a previous call to {nameof(BeginChange)}.");

            if (--bulkChangesStarted == 0)
            {
                UpdateState();
                TransactionEnded?.Invoke();
            }
        }

        /// <summary>
        /// Force an update of the state with no attached transaction.
        /// This is a no-op if a transaction is already active. Should generally be used as a safety measure to ensure granular changes are not left outside a transaction.
        /// </summary>
        public void SaveState()
        {
            if (bulkChangesStarted > 0)
                return;

            SaveStateTriggered?.Invoke();
            UpdateState();
        }

        protected abstract void UpdateState();
    }
}