/*
 *  Copyright (c) 2026, The OpenThread Authors.
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *  3. Neither the name of the copyright holder nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * @file
 * This file includes definitions for the Seeker module.
 *
 * The Seeker is responsible for discovering nearby Joiner Router candidates, prioritizing them, and
 * iterating through the list to select the next best candidate for connection. It acts as a
 * sub-module of the `Joiner` class.
 */

#ifndef OT_CORE_MESHCOP_SEEKER_HPP_
#define OT_CORE_MESHCOP_SEEKER_HPP_

#include "openthread-core-config.h"

#if OPENTHREAD_CONFIG_SEEKER_ENABLE || OPENTHREAD_CONFIG_JOINER_ENABLE

#include <openthread/seeker.h>

#include "common/as_core_type.hpp"
#include "common/callback.hpp"
#include "common/error.hpp"
#include "common/locator.hpp"
#include "common/msg_backed_array.hpp"
#include "common/non_copyable.hpp"
#include "common/numeric_limits.hpp"
#include "mac/mac_types.hpp"
#include "meshcop/extended_panid.hpp"
#include "net/socket.hpp"
#include "thread/discover_scanner.hpp"

namespace ot {

class UnitTester;

namespace MeshCoP {

/**
 * Represents a MeshCoP Seeker.
 */
class Seeker : public InstanceLocator, private NonCopyable
{
    friend class ot::UnitTester;

public:
    typedef otSeekerVerdict Verdict; ///< A verdict returned from `ScanEvaluator` evaluating a Discover Scan result.

    static constexpr Verdict kAccept          = OT_SEEKER_ACCEPT;           ///< Scan result is acceptable.
    static constexpr Verdict kAcceptPreferred = OT_SEEKER_ACCEPT_PREFERRED; ///< Scan result is acceptable & preferred.
    static constexpr Verdict kIgnore          = OT_SEEKER_IGNORE;           ///< Scan result should be ignored.

    /**
     * Represents the callback function type used to evaluate a scan result or report the end of a scan.
     *
     * See `otSeekerScanEvaluator` for more details.
     */
    typedef otSeekerScanEvaluator ScanEvaluator;

    /**
     * Initializes the `Seeker`
     *
     * @param[in]  aInstance  The OpenThread instance.
     */
    explicit Seeker(Instance &aInstance);

    /**
     * Starts the Seeker operation.
     *
     * The Seeker generates and sets a random MAC address for anonymity, then initiates an MLE Discover Scan to find
     * Joiner Router candidates.
     *
     * Found candidates are reported to the @p aScanEvaluator callback. Based on the returned `Verdict`, the Seeker
     * maintains a prioritized list of candidates for future connection attempts.
     *
     * @param[in] aScanEvaluator   The callback function to evaluate scan results.
     * @param[in] aContext         An arbitrary context pointer to use with @p aScanEvaluator.
     *
     * @retval kErrorNone          Successfully started the Seeker.
     * @retval kErrorBusy          The Seeker is already active (scanning or connecting).
     * @retval kErrorInvalidState  The IPv6 interface is not enabled, or MLE is enabled.
     */
    Error Start(ScanEvaluator aScanEvaluator, void *aContext);

    /**
     * Stops the Seeker operation.
     *
     * This method stops any ongoing discovery or connection process, unregisters the unsecure Joiner UDP port, and
     * clears internal state. If the Seeker is already stopped, this method has no effect.
     *
     * If the join process succeeds after a call to `SetupNextConnection()`, the caller MUST call this method to stop
     * the Seeker and, importantly, unregister the Joiner UDP port.
     *
     * Note: If `SetupNextConnection()` returns `kErrorNotFound` (indicating the candidate list is exhausted), the
     * Seeker stops automatically.
     */
    void Stop(void);

    /**
     * Indicates whether or not the Seeker is running.
     *
     * @retval TRUE    The seeker is active and running.
     * @retval FALSE   The seeker is stopped.
     */
    bool IsRunning(void) const { return GetState() != kStateStopped; }

    /**
     * Gets the Seeker UDP port.
     *
     * @returns The Seeker UDP port.
     */
    uint16_t GetUdpPort(void) const { return mUdpPort; }

    /**
     * Sets the Seeker UDP port.
     *
     * This method can only be called when the Seeker is not running.
     *
     * @param[in] aUdpPort    The Seeker UDP port.
     *
     * @retval kErrorNone           Successfully set the Seeker UDP port.
     * @retval kErrorInvalidState   The Seeker is already running.
     */
    Error SetUdpPort(uint16_t aUdpPort);

    /**
     * Selects the next best candidate and prepares the connection.
     *
     * This method must be called after the discovery scan has completed (indicated by the `ScanEvaluator` callback
     * receiving `nullptr`). Calling it before scan completion will result in `kErrorInvalidState`.
     *
     * This method iterates through the discovered Joiner Router candidates in order of priority. For the selected
     * candidate, it configures the radio channel and PAN ID, and populates @p aSockAddr with the candidate's address.
     * It also registers the Seeker UDP port (`GetUdpPort()`) as an unsecure port to allow UDP connection to the
     * candidate.
     *
     * If the list is exhausted, this method returns `kErrorNotFound` and automatically calls `Stop()`, which removes
     * the unsecure port and clears internal state.
     *
     * @param[out] aSockAddr       A reference to a socket address to output the candidate's address.
     *
     * @retval kErrorNone          Successfully set up the connection to the next candidate.
     * @retval kErrorNotFound      No more candidates are available (list exhausted).
     * @retval kErrorInvalidState  The Seeker is not in a valid state (e.g. scan not yet completed).
     */
    Error SetUpNextConnection(Ip6::SockAddr &aSockAddr);

private:
    static constexpr uint16_t kDefaultUdpPort          = OPENTHREAD_CONFIG_JOINER_UDP_PORT;
    static constexpr uint16_t kMaxCandidates           = OPENTHREAD_CONFIG_JOINER_MAX_CANDIDATES;
    static constexpr uint16_t kMaxCandidatesPerNetwork = OPENTHREAD_CONFIG_JOINER_CANDIDATES_PER_NETWORK;

    enum State : uint8_t
    {
        kStateStopped,
        kStateDiscovering,
        kStateDiscoverDone,
        kStateConnectingNetworks,
        kStateConnectingAny,
    };

    struct Candidate : public Clearable<Candidate>
    {
        enum Action : uint8_t
        {
            kSave,
            kReplace,
            kEvict,
            kDrop,
            kConnect,
        };

        Candidate(void) { Clear(); }
        void SetFrom(const ScanResult &aResult, bool aPreferred);
        bool IsFavoredOver(const Candidate &aOther) const;
        bool IsFavoredOver(const ScanResult &aResult, bool aPreferred) const;
        bool IsFavoredOver(int8_t aRssi, bool aPreferred) const;
        bool Matches(const MeshCoP::ExtendedPanId &aExtPanId) const { return mExtPanId == aExtPanId; }
        bool Matches(const MeshCoP::ExtendedPanId &aExtPanId, const Mac::ExtAddress &aExtAddr) const;
        void Log(Action aAction) const;

        static const char *ActionToString(Action aAction);

        MeshCoP::ExtendedPanId mExtPanId;
        Mac::ExtAddress        mExtAddr;
        Mac::PanId             mPanId;
        uint16_t               mJoinerUdpPort;
        uint8_t                mChannel;
        int8_t                 mRssi;
        bool                   mPreferred : 1;
        bool                   mConnAttempted : 1;
    };

    using CandidateArray = MessageBackedArray<Candidate, kMaxCandidates>;

    struct CandidateEntry : public CandidateArray::IndexedEntry
    {
        CandidateEntry(void) { MarkAsEmpty(); }
        void MarkAsEmpty(void) { SetIndexToInvalid(); }
        bool IsEmpty(void) const { return IsIndexInvalid(); }
        void ReplaceWithIfFavored(const CandidateEntry &aEntry);
    };

    State    GetState(void) const { return mState; }
    void     SetState(State aState) { mState = aState; }
    void     SaveCandidate(const ScanResult &aResult, bool aPreferred);
    Error    EvictCandidate(CandidateEntry &aEntry);
    Error    SelectNextCandidate(CandidateEntry &aEntry);
    uint16_t CountAndSelectLeastFavoredCandidateFor(const MeshCoP::ExtendedPanId &aExtPanId,
                                                    CandidateEntry               &aEntry) const;
    Error SelectMostFavoredCandidateFor(const MeshCoP::ExtendedPanId &aExtPanId, CandidateEntry &aFavoredEntry) const;

    DeclareScanResultHandlerIn(Seeker, HandleDiscoverResult);

    State                   mState;
    uint16_t                mUdpPort;
    Callback<ScanEvaluator> mScanEvaluator;
    CandidateArray          mCandidates;
};

} // namespace MeshCoP
} // namespace ot

#endif // OPENTHREAD_CONFIG_SEEKER_ENABLE || OPENTHREAD_CONFIG_JOINER_ENABLE

#endif // OT_CORE_MESHCOP_SEEKER_HPP_
