/*
 *    Copyright (c) 2025 Project CHIP Authors
 *    All rights reserved.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
#pragma once

#include <app/server-cluster/ServerClusterInterfaceRegistry.h>

namespace chip {
namespace app {

/// This class specializes ServerClusterInterfaceRegistry to register ServerClusterInterface references
/// that are limited to a single endpoint. In other words, GetPaths() must return path(s) in the same
/// endpoint.
/// The assumption that every ServerClusterInterface registered is limited to a single endpoint
/// allows us to provide additional helper methods such as `ClustersOnEndpoint()` and `UnregisterAllFromEndpoint()`.
class SingleEndpointServerClusterRegistry : public ServerClusterInterfaceRegistry
{
public:
    /// represents an iterable list of clusters
    class ClustersList
    {
    public:
        class Iterator
        {
        public:
            Iterator(ServerClusterRegistration * interface, EndpointId endpoint) : mEndpointId(endpoint), mRegistration(interface)
            {
                if (mRegistration != nullptr)
                {
                    mSpan = interface->serverClusterInterface->GetPaths();
                }
                AdvanceUntilMatchingEndpoint();
            }

            Iterator & operator++()
            {
                if (!mSpan.empty())
                {
                    mSpan = mSpan.SubSpan(1);
                }
                AdvanceUntilMatchingEndpoint();
                return *this;
            }
            bool operator==(const Iterator & other) const { return mRegistration == other.mRegistration; }
            bool operator!=(const Iterator & other) const { return mRegistration != other.mRegistration; }
            ClusterId operator*() { return mSpan.begin()->mClusterId; }

        private:
            const EndpointId mEndpointId;
            ServerClusterRegistration * mRegistration;
            Span<const ConcreteClusterPath> mSpan;

            void AdvanceUntilMatchingEndpoint()
            {
                while (mRegistration != nullptr)
                {
                    if (mSpan.empty())
                    {
                        mRegistration = mRegistration->next;
                        if (mRegistration != nullptr)
                        {
                            mSpan = mRegistration->serverClusterInterface->GetPaths();
                        }
                        continue;
                    }
                    if (mSpan.begin()->mEndpointId == mEndpointId)
                    {
                        return;
                    }

                    // need to keep searching
                    mSpan = mSpan.SubSpan(1);
                }
            }
        };

        constexpr ClustersList(ServerClusterRegistration * start, EndpointId endpointId) : mEndpointId(endpointId), mStart(start) {}
        Iterator begin() { return { mStart, mEndpointId }; }
        Iterator end() { return { nullptr, mEndpointId }; }

    private:
        const EndpointId mEndpointId;
        ServerClusterRegistration * mStart;
    };

    ~SingleEndpointServerClusterRegistry() = default;

    /// Add the given entry to the registry.
    /// NOTE the requirement of entries to be part of the same endpoint.
    ///
    /// Requirements:
    ///   - entry MUST NOT be part of any other registration
    ///   - paths MUST be part of the same endpoint
    ///
    ///   - LIFETIME of entry must outlive the Registry (or entry must be unregistered)
    ///
    /// There can be only a single registration for a given `endpointId/clusterId` path.
    [[nodiscard]] CHIP_ERROR Register(ServerClusterRegistration & entry);

    /// Provides a list of clusters that are registered for the given endpoint.
    ///
    /// ClustersList points inside the internal registrations of the registry, so
    /// the list is only valid as long as the registry is not modified.
    ClustersList ClustersOnEndpoint(EndpointId endpointId);

    /// Unregister all registrations for the given endpoint.
    void UnregisterAllFromEndpoint(EndpointId endpointId);
};

} // namespace app
} // namespace chip
