#include "document_class.h"
#include "latex_util.h"
#include "machine.h"
#include "show.h"
#include "log.h"

// extern "C" is needed for dlsym name lookup. Returning std::string from
// C-linkage functions is technically non-standard but works correctly on
// Linux (Itanium ABI) where C and C++ calling conventions are identical.
#if defined(__clang__)
#pragma clang diagnostic ignored "-Wreturn-type-c-linkage"
#endif

extern "C"
std::string document(Machine& machine)
{
    (void)K::log(3);
    try {
        Document_class D(machine);
        return D.result();
    }
    catch (Error& err) {
        err.print_message();
        std::cout << "\n";
        exit(1);
    }
}

// Print a console warning for each KT-WIDE-TABLE marker a table's runtime
// width check (tex_width_check() in table.py) left in the xelatex log,
// followed by a short :column_width primer.  Plain line scanning -- no
// std::regex over the (arbitrarily large) log text.
// One of the two surviving WARNINGS (see the output policy in CLAUDE.md; the
// other is warn_unparsed_katoms).  It stays a warning because the judgment is
// a HEURISTIC and a question of layout QUALITY rather than well-formedness:
// the check carries a 2pt tolerance for exactly-full tables, and a document
// with one over-wide table still renders.  Halting would turn a false positive
// into a blocker.  Promote it to an error when the measurement is exact.
static void warn_wide_tables(const std::string& xelatex_log)
{
    bool any = false;
    std::istringstream lines(xelatex_log);
    std::string line;
    while (std::getline(lines, line)) {
        auto pos = line.find("KT-WIDE-TABLE ");
        if (pos == std::string::npos) continue;
        if (!any) {
            std::cerr << yellow
                      << "Warning: a table is wider than the text column "
                      << "and extends past the right margin:\n";
            any = true;
        }
        std::string detail = line.substr(pos + 14);
        // The log's newline encoding can leave a trailing backslash.
        while (!detail.empty() &&
               (detail.back() == '\\' || detail.back() == ' '))
            detail.pop_back();
        std::cerr << "  " << detail << "\n";
    }
    if (any) {
        std::cerr <<
            "  The :column_width values and how they interact:\n"
            "    fit    the column's widest entry, never wrapped\n"
            "    fill   the width left over after the other columns, but\n"
            "           no more than the widest entry; wraps when needed\n"
            "    0.0-1.0  that fraction of the text column width\n"
            "    *      the width left over, unconditionally (the table\n"
            "           always spans the full text column)\n"
            "  A long-text column set to \"fit\" never wraps and pushes\n"
            "  the table off the page; give it \"fill\" instead.\n"
                  << black;
    }
}

extern "C"
std::string tex_to_pdf(Machine& machine)
{
    (void)K::log(3);
    try {
        check_for_xelatex();
        std::string outbase = machine.m_state.value("K_output_dir")
            + "/" + machine.m_state.value("K_output_basename");
        std::string tex_filename = outbase + ".tex";
        for (auto ext : {"aux", "log", "out", "toc"}) {
            fs::remove(outbase + "." + ext);
        }
        // xelatex writes .pdf/.log/.aux/.toc into its cwd unless told otherwise;
        // K_output_dir need not be cwd (it follows the input file, or -o).
        // The embedded paths are quoted defensively: they derive from user
        // filenames, which may contain spaces.
        std::string command = "xelatex -interaction=batchmode -halt-on-error -output-directory=\""
            + machine.m_state.value("K_output_dir") + "\" \"" + tex_filename + "\"";
        string_to_file(tex_filename, machine.m_result);
        std::string xelatex_output = exec(command.c_str());
        std::string xelatex_log = string_from_file(outbase + ".log");
        std::vector<std::string> error_lines = find_latex_error_lines(xelatex_log);
        if (!error_lines.empty()) {
            std::stringstream ss {};
            ss << "Errors reported in xelatex log file:\n";
            for (auto line : error_lines) {
                ss << "  " << line << "\n";
            }
            ss << "Check log file: " << outbase << ".log";
            throw Definition_error(ss.str(), Locator());
        }
        if (std::regex_search(xelatex_log, std::regex("Package rerunfilecheck Warning:"))) {
            (void)K::log(1, "Rerunning xelatex because document structure has changed");
            xelatex_output = exec(command.c_str());
            xelatex_log = string_from_file(outbase + ".log");
            error_lines = find_latex_error_lines(xelatex_log);
            if (!error_lines.empty()) {
                std::stringstream ss {};
                ss << "Errors reported in xelatex log file:\n";
                for (auto line : error_lines) {
                    ss << "  " << line << "\n";
                }
                ss << "Check log file: " << outbase << ".log";
                throw Definition_error(ss.str(), Locator());
            }
        }
        warn_wide_tables(xelatex_log);
        /*
        if (std::stoi(machine.m_state.value("K_verbose_level")) < 2) {
            for (auto ext : word_split("tex out aux log toc")) {
                fs::remove(outbase + "." + ext);
            }
        }
        */
        return "";
    }
    catch (Error& err) {
        std::cout << red;
        err.print_message();
        std::cout << black << "\n";
        exit(1);
    }
}

