#include "util.h"
#include "machine.h"
#include "error.h"
#include "kutil.h"
#include "html_util.h"
#include "latex_util.h"
#include "locator.h"
// #include "state.h"
#include "document_class.h"
#include "show.h"
#include "log.h"
#include "file.h"

bool strbool(const std::string& s, const Locator& loc)
{
    std::vector<std::string> values = {"false", "False", "0", "true", "True", "1"};
    if (is_not_in(s, values)) {
        std::stringstream ss {};
        ss << "The value \"" << s << "\" is not a Boolean value. Possible values are:\n  "
           << join(values, ", ");
        throw Argument_error(ss.str(), loc);
    }
    bool result = (find(values.begin(), values.end(), s) - values.begin()) > 2;
    return result;
}


Document_class::Document_class(Machine& machine) : Klammer_base(machine)
{
    (void)K::log(3);
    // show("document in main SKS");
    m_machine = machine;
    sloc = get("K_loc");
    m_title = get("title");
    m_subtitle = get("subtitle");
    m_page_title = get("page_title");
    m_author = get("author");
    m_date = get("date");

    // m_nav = strbool(get("nav"), loc);
    // m_toc = strbool(get("toc"), loc);

    m_structure = docstruct(get("structure"));

    m_date = get("date");
    m_version = get("version");
    m_logo = get("logo");

    m_text = get("text");
    // Filename lists: standalone-"/" separation, existence rescue for
    // spaces, ~ expansion (resolve_filename_list in mac/file.cpp); the
    // existence checks resolve relative names against the input directory,
    // as parse_input_filename() will.
    // Filename-bearing values arrive ESCAPED for the target (the state
    // stores escaped values so they flow correctly into output); a filename
    // is programmatic use, so decode the markers first -- the C++ mirror of
    // the Python-side unescape_ktesc() rule.  Found 2026-08-22: under tex,
    // ":files my_chapter" searched for "myKTESC005fKTESCchapter".
    m_files = resolve_filename_list(ktesc_resolve(get("files")), get("K_input_dir"));

    m_css_text = get("css_text");
    m_css_filenames = resolve_filename_list(ktesc_resolve(get("css_files")), get("K_input_dir"));
    m_include_sks_css = strbool(get("include_sks_css"), loc);
    frame_background_color = get("frame_background_color");
    frame_text_color = get("frame_text_color");
    nav_background_color = get("nav_background_color");
    nav_text_color = get("nav_text_color");

    js_text = get("js_text");
    m_js_filenames = resolve_filename_list(ktesc_resolve(get("js_files")), get("K_input_dir"));
    m_include_sks_js = strbool(get("include_sks_js"), loc);

    // font_dirs = word_split(get("font_dirs"));

    auto strip_quotes = [](std::string s) {
        if (s.size() >= 2 && s.front() == '"' && s.back() == '"')
            s = s.substr(1, s.size() - 2);
        return s;
    };
    m_serif_font = strip_quotes(get("serif_font"));
    m_sans_font = strip_quotes(get("sans_font"));
    m_mono_font = strip_quotes(get("mono_font"));
    m_font_scale = stof(get("font_scale"));

    if (!m_serif_font.empty())
        m_resolved_serif = resolve_font(m_serif_font);
    if (!m_sans_font.empty())
        m_resolved_sans = resolve_font(m_sans_font);
    if (!m_mono_font.empty())
        m_resolved_mono = resolve_font(m_mono_font);
    paper_size = get("paper_size");
    landscape = strbool(get("landscape"), loc);
    leading = stof(get("leading"));
    point_size = stoi(get("point_size"));
    ragged_right = strbool(get("ragged_right"), loc);
    cover = get("cover");
    prolog = get("prolog");
    m_two_column = strbool(get("two_column"), loc);

    m_copyright = get("copyright");
    m_bottom = get("bottom");

    create_output_directory = strbool(get("create_output_directory"), loc);
    output_directory_name = get("output_directory_name");

    resources_in_file = strbool(get("resources_in_file"), loc);

    //use_pages_dir = strbool(get("use_pages_dir"), loc);

    // m_toc_only = strbool(get("K_toc_only"), loc);
    m_kt_root_filename = get("K_input_filename");

    m_no_cache = !strbool(get("cache"), loc);

    write_files = !strbool(get("K_stdout_only"), loc);
    m_input_dir = get("K_input_dir");

    if (m_text.size() == 0 and m_files.size() == 0) {
        throw Argument_error(
            "Neither the :text or :files options have values"); //,
        //get("K_loc"));
    }
    /*
    for (std::string file : m_files) {
        sources.push_back(string_from_file(find_kt_file(file)));
    }
    */
    // std::cout << "IN DOCUMENT CLASS:\n" << m_state << "\n";
}


void Document_class::save_string_input_as_file()
{
    if (!m_text.empty()) {
        std::string input_text_filename = m_cache_dir + "/_text.kt";
        // msg() << "Write string input to cache file: "
        //      << input_text_filename << "\n";
        string_to_file(input_text_filename, m_text);
        m_files.insert(m_files.begin(), input_text_filename);
    }
}


fs::path parse_input_filename(const std::string& s, const std::string& input_dir)
{
    // The two-class rule (2026-08-22), replacing a three-stage search whose
    // second stage could quietly shadow a file beside the document:
    //
    //   * a BARE WORD -- no directory separator, no extension -- is the kt/
    //     SHORTCUT: ":files X" MEANS kt/X.kt in the root file's directory,
    //     and nothing else.  Missing is an immediate error whose message
    //     teaches the convention, not a fallback.  The kt/ directory is the
    //     conventional home for a document's input files, and putting the
    //     meaning entirely in the name lets several root documents share it.
    //
    //   * anything else is a real PATHNAME, absolute or resolved against the
    //     input file's directory (K_input_dir), so a document renders
    //     identically wherever ktext is run from.
    //
    // Which file a name landed on is a DERIVED value, so "-v 1" reports it.
    fs::path p(s);
    bool bare = s.find('/') == std::string::npos && p.extension().empty();
    if (bare) {
        fs::path in_kt = fs::path(input_dir) / "kt" / (s + ".kt");
        if (!file_exists(in_kt.string())) {
            throw Argument_error(
                "The \":files\" name \"" + s + "\" is a bare word, which by "
                "convention means the file kt/" + s + ".kt in the root "
                "document's directory:\n"
                "  " + in_kt.string() + "\n"
                "That file does not exist. Create it there, or write a real "
                "pathname (a name with a directory or the \".kt\" extension) "
                "to use a file elsewhere.",
                Locator::none());
        }
        // The resolved path itself shows kt/ -- no label needed.
        (void)K::log(1, "Input file \"" + s + "\": " + in_kt.string());
        return in_kt;
    }
    if (p.extension() != ".kt") {
        p += ".kt";
    }
    if (p.is_absolute()) {
        return p;
    }
    fs::path in_input_dir = fs::path(input_dir) / p;
    (void)K::log(1, "Input file \"" + s + "\": " + in_input_dir.string());
    return in_input_dir;
}

void Document_class::write(const std::string& filename, const std::string& contents)
{
    write_file(filename, contents, write_files);
}


void write_file(const std::string& filename, const std::string& contents, bool write_p)
{
    if (write_p) {
        string_to_file(filename, contents);
    } else {
        // Not writing: name the file that would have been written.  A
        // derived value, so "-v 1" -- and never on stdout, which carries
        // the document itself.
        (void)K::log(1, "Output filename:", filename);
    }
}


/*
void output_msg(const std::string& msg)
{
    std::cout << red;
    (void)K::log(1, msg);
    std::cout << black;
}    

void Document_class::create_directory(const std::string& directory)
{
    if (write_files) {
        fs::create_directories(directory);
    } else {
        output_msg("Output directory: " + directory);
    }
}

*/
