#pragma once


#include <saas/library/st_indexer_report/cpp/yt_report.h>
#include <util/generic/vector.h>
#include <util/generic/string.h>

#include <mapreduce/yt/interface/client.h>

// TODO: consider re-using NJupiter::TCommand
// TODO: consider switching to protobuf-only backend and use NJupiter::TTable instead of NYT::TRichYPath

/* The following class is a virtual interface for an YT operation combined with input/output paths, specs, jobs specs
 * and other parameters. Main method is Run() which requires only an YT client pointer and a resume flag. This allows
 * storing pointers to derived classes in a container and Run() them all in an uniform manner. Specs are built prior
 * to Run() using PrepareSpec() method. This helps with catching broken specs early for operations that runs late
 * in the chain.
 * This class also implements the following logic:
 * * Wraps each operation into YT transaction.
 * * If (resume == true) skips operation when all output tables are already available. Check for output is in the
 *   same transaction with the rest of operations.
 * * If (resume == false) removes any existing output tables prior to operation.
 * * Does some verbose printing in stderr when (verbose == true).
 */

struct TStandaloneIndexerError : yexception {
    TStandaloneIndexerError(TStringBuf prefix) {
        *this << prefix << ": ";
    }
};

class TYTCommand {
public:
    using TInputs = TVector<NYT::TRichYPath>;
    using TOutputs = TVector<NYT::TRichYPath>;
    using TInputAttrs = TVector<NYT::TNode>;

public:
    TYTCommand(TInputs inputs, TOutputs outputs, TString name, bool verbose, NSaas::TYTLaunchReport& report, const NYT::TNode& acl);
    virtual ~TYTCommand() = default;

    void PrepareSpec();
    void Run(NYT::IClientBase* client, bool& resume);


protected:
    virtual void DoPrepareSpec() = 0;
    virtual void DoRun(NYT::IClientBase* client) = 0;
    void PrintVerbose(const TString& msg, bool isError = false);

    NYT::IOperationPtr Map(
        NYT::IClientBase* client,
        const NYT::TMapOperationSpec& spec,
        ::TIntrusivePtr<NYT::IMapperBase> mapper,
        const NYT::TOperationOptions& options = {})
    {
        return client->Map(spec, mapper, AddAclIfNeed(options));
    }

    NYT::IOperationPtr Reduce(
        NYT::IClientBase* client,
        const NYT::TReduceOperationSpec& spec,
        ::TIntrusivePtr<NYT::IReducerBase> reducer,
        const NYT::TOperationOptions& options = {})
    {
        return client->Reduce(spec, reducer, AddAclIfNeed(options));
    }

    NYT::IOperationPtr MapReduce(
        NYT::IClientBase* client,
        const NYT::TMapReduceOperationSpec& spec,
        ::TIntrusivePtr<NYT::IMapperBase> mapper,
        ::TIntrusivePtr<NYT::IReducerBase> reduceCombiner,
        ::TIntrusivePtr<NYT::IReducerBase> reducer,
        const NYT::TOperationOptions& options = {})
    {
        return client->MapReduce(spec, mapper, reduceCombiner, reducer, AddAclIfNeed(options));
    }

    NYT::IOperationPtr Sort(
        NYT::IClientBase* client,
        const NYT::TSortOperationSpec& spec,
        const NYT::TOperationOptions& options = {})
    {
        return client->Sort(spec, AddAclIfNeed(options));
    }

protected:
    TInputs Inputs;
    TOutputs Outputs;
    TInputAttrs InputAttrs;
    NSaas::TYTLaunchReport& Report;

private:
    NYT::TOperationOptions AddAclIfNeed(const NYT::TOperationOptions& src);

    bool OutputExists(NYT::IClientBase* client);
    void DeleteOutput(NYT::IClientBase* client);
    void CreateOutputDirectories(NYT::IClientBase* client);
    void FillInputAttrs(NYT::IClientBase* client);

private:
    const TString Name;
    const bool Verbose;
    NYT::TNode Acl;
};
