/** @file skreplxx.cpp
 *
 *  @author Roland Conybeare, Feb 2026
 **/

#include <xo/interpreter2/init_interpreter2.hpp>
#include <xo/interpreter2/VirtualSchematikaMachine.hpp>
#include <xo/alloc2/Arena.hpp>
#include <xo/facet/FacetRegistry.hpp>
#include <replxx.hxx>
#include <iostream>
#ifdef __APPLE__
#include <unistd.h> // for STDIN_FILENO on OSX
#endif

namespace xo {
    using xo::scm::DVirtualSchematikaMachine;
    using xo::scm::VsmResultExt;
    using xo::mm::AAllocator;
    using xo::mm::ArenaConfig;
    using xo::mm::DArena;
    using span_type = xo::mm::span<const char>;
    using xo::facet::FacetRegistry;
    using xo::facet::TypeRegistry;
    using std::cerr;

    // presumeably replxx assumes input is a tty anyway?
    //
    bool replxx_getline(bool interactive,
                        bool is_at_toplevel,
                        replxx::Replxx & rx,
                        span_type * p_input
                        //const char ** p_input
                        )
    {
        using namespace std;

        char const * prompt = "";

        if (interactive) {
            prompt = ((is_at_toplevel) ? "> " : ". ");
        }

        const char * input_cstr = rx.input(prompt);

        bool retval = (input_cstr != nullptr);

        if (retval)
            *p_input = span_type::from_cstr(input_cstr);

        if (input_cstr)
            rx.history_add(input_cstr);

        return retval;
    }

    void
    welcome(std::ostream & os)
    {
        using namespace std;

        os << "schematika repl" << endl;
        os << "  ctrl-a/ctrl-e   beginning/end of line" << endl;
        os << "  ctrl-u          delete entire line" << endl;
        os << "  ctrl-k          delete to end of line" << endl;
        os << "  meta-<bs>       backward delete word" << endl;
        os << "  <up>|meta-p     previous command from history" << endl;
        os << "  <down>|meta-n   next command from history" << endl;
        os << "  <pgup>/<pgdown> page through history faster" << endl;
        os << "  ctrl-s/ctrl-r   forward/backward history search" << endl;
        os << endl;
    }

    struct ReplConfig {
        ReplConfig() = default;

        std::size_t max_history_size_ = 1000;
        std::string repl_history_fname_ = "skrepl_history.txt";
        bool debug_flag_ = false;
    };

    struct AppConfig {
        using VsmConfig = xo::scm::VsmConfig;

        //using ReaderConfig = xo::scm::ReaderConfig;
        //using X1CollectorConfig = xo::mm::X1CollectorConfig;
        //using ArenaConfig = xo::mm::ArenaConfig;

        AppConfig(const ReplConfig & repl_cfg = ReplConfig(),
                  const ArenaConfig & app_arena_cfg = ArenaConfig().with_name("skreplxx").with_size(32 * 1024),
                  const VsmConfig & vsm_cfg = VsmConfig().with_x1_config(VsmConfig::std_x1_config().with_debug_flag(true).with_sanitize_flag(true)))
        : repl_config_{repl_cfg}, app_arena_config_{app_arena_cfg}, vsm_config_{vsm_cfg}
        {
            //rdr_config_.reader_debug_flag_ = true;
            //rdr_config.parser_debug_flag_ = true;
            //rdr_config.tk_debug_flag_ = true;
        }

        ReplConfig repl_config_;
        ArenaConfig app_arena_config_;
        VsmConfig vsm_config_;
        //ReaderConfig rdr_config_;
        //X1CollectorConfig x1_config_ = (X1CollectorConfig().with_name("gc").with_size(4*1024*1024));
        //ArenaConfig fixed_config_ = (ArenaConfig().with_name("fixed").with_size(4*1024));
    };

    struct App {
        //using AAllocator = xo::mm::AAllocator;
        //using DX1Collector = xo::mm::DX1Collector;
        //using X1CollectorConfig = xo::mm::X1CollectorConfig;
        using AGCObject = xo::mm::AGCObject;
        //using DArena = xo::mm::DArena;
        //using ArenaConfig = xo::mm::ArenaConfig;
        using VsmConfig = xo::scm::VsmConfig;
        using Replxx = replxx::Replxx;
        using span_type = DVirtualSchematikaMachine::span_type;

        App(const AppConfig & cfg = AppConfig())
        : repl_config_{cfg.repl_config_},
          app_arena_{cfg.app_arena_config_},
          vsm_config_{cfg.vsm_config_}
        {
            this->interactive_ = isatty(STDIN_FILENO);

            rx_.set_max_history_size(repl_config_.max_history_size_);
            rx_.history_load(repl_config_.repl_history_fname_);
            //    rx.bind_key_internal(Replxx::KEY::control('p'), "history_previous");
            //    rx.bind_key_internal(Replxx::KEY::control('n'), "history_next");
        }

        /** borrows calling thread to run application **/
        void run();

    private:
        void _init();
        void _start();
        void _repl();
        bool _read_eval_print(span_type * p_input, bool eof);
        void _stop();

    private:
        InitEvidence init_evidence_ = 0;
        ReplConfig repl_config_;
        bool interactive_ = false;
        Replxx rx_;
        ///** collector/allocator for schematika expressions **/
        //DX1Collector x1_;
        ///** e.g. for DArenaHashMap within global symtab **/
        //DArena fixed_;

        /** arena with same lifetime as this application **/
        DArena app_arena_;
        /** schematika virtual machine **/
        VsmConfig vsm_config_;
        abox<AGCObject,DVirtualSchematikaMachine> vsm_;
    };

    void
    App::run()
    {
        this->_init();
        this->_start();
        this->_repl();
    }

    void
    App::_init()
    {
        // window to control size of registries ends as soon as we init other subsystems
        TypeRegistry::instance(1024);
        FacetRegistry::instance(1024);

        InitEvidence init_evidence_ = (InitSubsys<S_interpreter2_tag>::require());

        Subsystem::initialize_all();

        vsm_.adopt(DVirtualSchematikaMachine::make(obj<AAllocator,DArena>(&app_arena_),
                                                   vsm_config_,
                                                   obj<AAllocator,DArena>(&app_arena_)));
    }

    void
    App::_start()
    {
        welcome(cerr);

        vsm_->begin_interactive_session();
    }

    void
    App::_repl()
    {
        bool eof = false;
        span_type input;

        // outer loop: fetch one line of interactive input
        while (replxx_getline(interactive_, vsm_->is_at_toplevel(), rx_, &input)) {

            // inner loop: consume up to one expression at a time.
            while (!input.empty() && this->_read_eval_print(&input, false /*eof*/))
                {
                    ;
                }

            /* here: either:
             * 1. input.empty() or
             * 2. error encountered
             */
        }

        /* reminder: eof can complete at most one token */
        this->_read_eval_print(&input, true /*eof*/);
    }

    /** body of read-parse-print loop
     *
     *  true -> no errors;
     *  false -> reader encountered error
     **/
    bool
    App::_read_eval_print(span_type * p_input,
                          bool eof)
    {
        scope log(XO_DEBUG(repl_config_.debug_flag_));

        if (!p_input || p_input->empty())
            return true;

        VsmResultExt res = vsm_->read_eval_print(*p_input, eof);

        *p_input = res.remaining_;

        return !res.is_error();
    }

    void
    App::_stop()
    {
        vsm_._drop();
    }

} /*namespace xo*/

int
main (int argc, char * argv[])
{
    using xo::AppConfig;
    using xo::App;

    AppConfig cfg;
    // [cmdline options here]

    cfg.vsm_config_.x1_config_.debug_flag_ = true;

    App app(cfg);

    app.run();
} /*main*/

/* end skreplxx.cpp */
