#pragma once

#include <string>

// #include "source.h"
#include "katom_list.h"
#include "klammer_registry.h"
#include "klammerset_registry.h"
#include "option_set_registry.h"
#include "target_registry.h"
#include "argtype_registry.h"
#include "state.h"

#include "error.h"

class Machine
{
public:
    using input_sources_t =
        std::vector<std::variant<fs::path, std::string>>;

    Machine();

    // Copy constructor
    Machine(const Machine& other)
        : m_argtypes(other.m_argtypes)
        , m_state(other.m_state)
        , m_targets(other.m_targets)
        , m_klammers(other.m_klammers)
        , m_klammersets(other.m_klammersets)
        , m_option_sets(other.m_option_sets)
        , m_result(other.m_result)
    {}

    // Copy assignment operator
    Machine& operator=(const Machine& other) {
        if (this != &other) {
            m_argtypes = other.m_argtypes;
            m_state = other.m_state;
            m_targets = other.m_targets;
            m_klammers = other.m_klammers;
            m_klammersets = other.m_klammersets;
            m_option_sets = other.m_option_sets;
            m_result = other.m_result;
        }
        return *this;
    }
    
    // @cond is resolved at APPLICATION time, not read time (Andy, 2026-08-15,
    // deciding notes/Klammertext_improvements.md §4.2).  mark_cond_content()
    // runs during process_katoms and makes every branch inert;
    // resolve_cond_katoms() runs inside the apply fold, selects a branch, and
    // processes only that one.  The document then behaves like a klammer body:
    // its state variables are bound before its conditionals are decided.
    void mark_cond_content(std::vector<Katom>& katoms);
    int resolve_cond_katoms(std::vector<Katom>& katoms, const std::string& target);
    void process_eval_katoms(std::vector<Katom>& katoms);
    void mark_literal_klammer_content(std::vector<Katom>& katoms);
    void escape_target_characters(const Target& target, std::vector<Katom>& katoms);
    
    // std::vector<Katom> 
    void process_katoms(
        std::vector<Katom>& katoms, const std::string& source,
        bool nonascii=true, bool literal=true, bool ignore=true, bool whitespace=true,
        bool klammers=true, bool eval=true, bool cond=true, bool read=true);

    std::vector<Katom> process(
        std::string text, const std::string& source,
        bool nonascii=true, bool literal=true, bool ignore=true, bool whitespace=true,
        bool klammers=true, bool eval=true, bool cond=true, bool read=true);

    void read(const fs::path& pathname);
    void read(const std::string& s);

    void expand_read_katoms(
        std::vector<Katom>& katoms, std::string source_filename,
        bool nonascii=true, bool literal=true, bool ignore=true, bool ws=true,
        bool klammers=true, bool eval=true, bool cond=true, bool read=true);

    void expand_constant_klammers(katom_list& katoms, const Katom& op, const Katom& cl);
    void add_definition(katom_list& katoms, const Katom& op, const Katom& cl);
    void load_klammerset_files(const Klammerset& klammerset, katom_iter insert_at);
    // `tolerant` is for kdiag ONLY.  It dissects the structure and typing of
    // the katom list, and allows modes of processing that would be errors in
    // ktext: run one definition tier without the other and a definition may
    // be unregisterable (a klammer names a target that "--system" would have
    // registered).  Tolerant then SKIPS that definition instead of throwing,
    // leaving its katoms unconsumed and therefore visible -- which is the
    // report.  ktext must keep throwing: there, a document naming an
    // undefined target is a genuine error, and skipping it silently would
    // render a wrong document.
    void add_machine_definition(const std::string& name, katom_iter begin,
                                katom_iter end, bool& rescan);
    void extract_machine_definitions(bool tolerant = false);
    void extract_klammer_definitions(bool tolerant = false);
    void extract_klammer_definitions(katom_list katoms);
    void update_state(const std::map<std::string, std::string>& arg_map);

    katom_list apply_klammer(Klammer& klammer, const std::string& target, katom_iter arguments_begin, katom_iter arguments_end);

    void apply_klammer_registry(Klammer_registry& klammer_registry,
               katom_list& katoms, const std::string& target, katom_iter begin, katom_iter end);

    // Returns the number of klammers applied in this pass.  The top-level
    // fixed point loops while that count is nonzero: reduction is detected by
    // a redex having been reduced, not by the katom list having grown.
    int apply(Klammer_registry& klammer_registry, katom_list& katoms, const std::string& target);

    std::string run_phase_functions();
    std::string apply(const std::string& target_name, bool final_processing=true, bool escape_characters=true);

    Argtype_registry m_argtypes {};
    State m_state {};
    Target_registry m_targets {};
    Klammer_registry m_klammers {};
    Klammerset_registry m_klammersets {};
    Option_set_registry m_option_sets {};
    input_sources_t m_sources {};
    std::string m_result {};
    std::vector<Katom> m_katoms {};
    // False on the sub-Machine that re-reads an @eval result (Eval::eval):
    // result text is machine-generated -- a renderer's raw target markup
    // (e.g. a LaTeX column spec "@{}...") legitimately fails katom parsing
    // and must not produce "Word not parsed" warnings.
    bool m_warn_unparsed = true;
};

/*
class Machine {
private:
    std::string m_name;
    int m_id;
    std::vector<std::string> m_sources;
    bool m_active;
    double m_value;

public:
    // Default constructor
    Machine() : m_name(""), m_id(0), m_sources(), m_active(false), m_value(0.0) {}
    
    // Copy constructor
    Machine(const Machine& other) 
        : m_name(other.m_name)           // Copy name
        , m_id(other.m_id)               // Copy id
        , m_sources()                     // Initialize m_sources as empty
        , m_active(other.m_active)        // Copy active status
        , m_value(other.m_value)          // Copy value
    {
        // m_sources is now empty, ready for new initialization
    }
    
    // Assignment operator (if needed)
    Machine& operator=(const Machine& other) {
        if (this != &other) {
            m_name = other.m_name;
            m_id = other.m_id;
            m_sources.clear();  // Clear and leave empty
            m_active = other.m_active;
            m_value = other.m_value;
        }
        return *this;
    }
};
*/
