#include "util.h"
#include "error.h"
#include "log.h"
#include "kutil.h"
#include "html_util.h"
#include "heading.h"
#include "document_class.h"
#include "character.h"
#include "reference.h"
#include "show.h"
#include "file.h"

std::string unescape_newlines(const std::string& s)
{
    return string_replace(s, " ___NL___ ", "\n");

}

int ID = 0;
std::string BASE {};

static const std::regex newline_rgx(R"(\n)");
static const std::regex element_open_rgx(R"(<([\w-]+)(\s+.*?)?>)");
static const std::regex id_attr_rgx("id=\"(.*?)\"");


std::string insert_missing_ids(std::smatch match)
{
    (void)K::log(3);
    std::string result = match[0];
    std::string tag = match[1];
    if (html::tag_is_block_element(tag)) {
        std::string attrs = match[2];
        std::stringstream ss {};
        if (attrs.find(" id=") == std::string::npos) {
            ss << "id=\"_e" << ID++<< "\"";
        }
        ss << attrs;
        if (!BASE.empty()) {
            ss << " data-basename=\"" << BASE << "\"";
        }
        result = "<" + tag + " " + trim(ss.str()) + ">";
    }
    return result;
}

std::string insert_ids(const std::string& html_text)
{
    (void)K::log(3);
    std::string result = html_text;
    result = freplace(result, element_open_rgx, insert_missing_ids);
    return result;
}

std::string Document_class::make_help_page()
{
    (void)K::log(3);
    std::string kt_filename = klammertext_filename("doc/html_help.kt");
    std::string basename = "help";
    std::string help_page;
    //if (!in_modification_order(kt_filename, output_filename, no_cache)) {
    if (cache_requires_update(m_cache_dir, kt_filename, basename)) {
        Machine Mh = m_machine;
        Mh.read(fs::path(klammertext_filename("doc/html_help.kt")));
        help_page = Mh.apply("html");
        help_page += "\n<div class=\"endspace\"></div>\n";
        help_page = html::make_paragraphs(help_page);
        write_to_cache(m_cache_dir, basename, help_page);
    } else {
        help_page = read_from_cache(m_cache_dir, basename);
    }
    return help_page;
}

std::string Document_class::color_definitions()
{
    std::stringstream ss {};
    ss << ":root {\n"
       << "    --frame_background_color: " << frame_background_color << ";\n"
       << "    --frame_text_color: " << frame_text_color << ";\n"
       << "    --nav_background_color: " << nav_background_color << ";\n"
       << "    --nav_text_color: " << nav_text_color << ";\n}\n";
    return ss.str();
}

strings_t Document_class::resolved_font_names()
{
    strings_t result {};
    auto add = [&](const Resolved_font& rf) {
        if (!rf.family_name.empty())
            result.push_back(rf.dir_name);
    };
    add(m_resolved_serif);
    add(m_resolved_sans);
    add(m_resolved_mono);
    return result;
}

std::string Document_class::font_definitions()
{
    std::stringstream ss {};
    if (!m_serif_font.empty() || !m_sans_font.empty() || !m_mono_font.empty()) {
        // Family names are quoted: an unquoted name with a digit-initial
        // word ("Source Sans 3") is invalid CSS, and a font-family using
        // var() with such a value computes to inherit, silently losing
        // the font.
        ss << ":root {\n";
        if (!m_serif_font.empty())
            ss << "    --serif: \"" << m_serif_font << "\", serif;\n";
        if (!m_sans_font.empty())
            ss << "    --sans-serif: \"" << m_sans_font << "\", sans-serif;\n";
        if (!m_mono_font.empty())
            ss << "    --monospace: \"" << m_mono_font << "\", monospace;\n";
        ss << "}\n";
    }
    // Font-size normalization across families is no longer emitted from
    // here (2026-08-22): font.css declares "font-size-adjust: ex-height 0.5"
    // on body, and the browser renders every font at the same x-height --
    // the same computation the former --sans-serif-scale/--monospace-scale
    // factors did from build-time OS/2 metrics, but applied to EVERY family
    // switch instead of the four CSS sites that remembered to multiply.
    // The metric extraction in the font store remains (the tex path and
    // kdesc --font still use it).
    // Global font scale: applied to body font-size
    if (m_font_scale != 1.0f) {
        char buf[16];
        std::snprintf(buf, sizeof(buf), "%.4f", m_font_scale);
        ss << "body { font-size: " << buf << "rem; }\n";
    }
    return ss.str();
}

void Document_class::write_basenames_js_file(const std::string& filename, const std::vector<std::string>& basenames)
{
    (void)K::log(3);
    std::string tab("        ");
    std::stringstream ss {};
    ss << "function pages_basenames()\n{\n    return [\n";
    int i = 0;
    int last = basenames.size() - 1;
    for (std::string base : basenames) {
        ss << tab << "\"" << base << "\"";
        if (i < last) {
            ss << ",";
        }
        ss << "\n";
        i++;
    }
    ss << "    ];\n}\n";
    write(filename, ss.str());
}

std::string Document_class::create_html_output_directories()
{
    (void)K::log(3);
    std::string output_directory =
        absolute_pathname(get("K_output_dir")) + "/" + get("K_output_basename");
    if (!m_toc_only) {
        if (!file_exists(output_directory)) {
            fs::create_directory(output_directory);
        }
        install_resolved_font(m_resolved_serif, output_directory);
        install_resolved_font(m_resolved_sans, output_directory);
        install_resolved_font(m_resolved_mono, output_directory);
    }
    m_cache_dir = cache_directory("_html_pages");
    if (m_no_cache && fs::exists(m_cache_dir)) {
        for (auto& entry : std::filesystem::directory_iterator(m_cache_dir)) {
            // What ":cache false" actually removed: a derived consequence of
            // an option, so "-v 1".
            (void)K::log(1, "Removing cache directory:", entry.path().string());
            std::filesystem::remove_all(entry);
        }
    }
    if (!m_toc_only) {
        if (!file_exists(output_directory, false, true)) {
            fs::create_directory(output_directory);
        }
    }
    return output_directory;
}

strings_t get_css_files(const strings_t& custom_css_files)
{
    (void)K::log(3);
    strings_t result = sks_files_of_type("css");
    result.insert(result.end(), custom_css_files.begin(), custom_css_files.end());
    return result;
}


std::vector<std::string> js_basenames(bool nav)
{
    if (nav) {
        return { "kutil", "document", "image", "resize_window", "resize_text", "resize_toc",
            "toc", "level", "next_last", "help", "show_links", "search", "state", "code", "link" };
    } else {
        return {"kutil", "document", "image"};
    }
}    

strings_t Document_class::get_js_files(
    bool nav, bool include_sks_js,
    const std::string& pages_basenames_filename)
{
    (void)K::log(3);
    strings_t result {};
    if (include_sks_js) {
        for (auto b : js_basenames(nav)) {
            std::vector<fs::path> js_files =
                find_file_recursive(klammertext_dir() + "/sks", b + ".js");
            if (js_files.size() == 0) {
                throw File_error("File " + q_(b) + " not found.", Locator());
            }
            result.push_back(js_files[0].string());
        }
        if (!pages_basenames_filename.empty()) {
            result.push_back(pages_basenames_filename);
        }
    }
    return result;
}


strings_t link_filenames(strings_t& full_filenames, const std::string& page_dir)
{
    strings_t result {};
    fs::path page_dir_path(page_dir);
    // int i = 0;
    std::transform(full_filenames.begin(), full_filenames.end(), std::back_inserter(result),
                   [&](const std::string& f) {
                       fs::path full(f);
                       //msg() << i << full << " " << full.filename() << "\n";
                       fs::path path(page_dir_path / full.filename());
                       //std::cout << i++ << path << "\n";
                       return path.string(); });
    return result;
}

std::pair<std::vector<std::string>, std::vector<std::string>>
Document_class::html_auxiliary_files(const std::string& output_directory, const std::string& pages_basenames_filename,
                                     const std::vector<std::string>& custom_css_files)
{
    (void)K::log(3);
    strings_t all_css_files = custom_css_files;
    strings_t css_link_filenames {};
    if (write_files && m_include_sks_css) {
        all_css_files = get_css_files(m_css_filenames);
        //output_dir, color_definitions(), css_files, write_files);
    }
    if (write_files) {
        copy_preserving_basename(
            all_css_files, output_directory, "css");
        css_link_filenames = link_filenames(all_css_files, "css");
    }

    strings_t all_js_files {};
    strings_t js_link_filenames {};
    if (write_files) {
        bool nav = m_structure == docstruct_t::book;
        all_js_files = get_js_files(
            nav, m_include_sks_js,
            pages_basenames_filename); // , output_dir, write_files);
        copy_preserving_basename(
            all_js_files, output_directory, "js");
        js_link_filenames = link_filenames(all_js_files, "js");
    }

    // msg() << "all_css_files:\n  " << join(all_css_files, "\n  ") << "\n\n";
    // msg() << "all_js_files:\n  " << join(all_js_files, "\n  ") << "\n\n";

    return {css_link_filenames, js_link_filenames};
}


void Document_class::process_input_files()
{
    (void)K::log(3);
    m_file_components.clear();
    size_t basename_width = 0;
    for (auto input_filename : m_files) {
        fs::path inpath = input_filename;
        if (!file_exists(inpath)) {
            inpath = parse_input_filename(input_filename, m_input_dir);
        }
        std::string basename = file_basename(input_filename);
        basename_width = std::max(basename_width, basename.size());
        std::string html_text;
        //if (true || !in_modification_order(inpath, cached_filename, m_no_cache)) {
        if (m_no_cache || cache_requires_update(m_cache_dir, inpath, basename)) {
            Machine M = m_machine;
            M.read(inpath);
            html_text = trim(M.apply("html"));
            html_text = trim(html::make_paragraphs(html_text)) + "\n";
            html_text = unescape_newlines(html_text);
            BASE = basename;
            html_text = insert_ids(html_text);
            m_file_components.push_back({basename, html_text});
            write_to_cache(m_cache_dir, basename, html_text);
        } else {
            html_text = read_from_cache(m_cache_dir, basename);
            m_file_components.push_back({basename, html_text});
        }
    }
    // for (auto [src, result] : m_file_components) {
    //    msg() << std::left << std::setw(basename_width) << src << "  " << abbrev(result, 96) << "\n";
    // }
}

std::string increment_level(int level, std::vector<int> &levels)
{
    levels[level]++;
    int zero_offset = level == 0 ? 2 : 1;
    for (unsigned int i = level + zero_offset; i < levels.size(); i++) {
        levels[i] = 0;
    }
    std::stringstream result {};
    if (level == 0) {
        result << levels[0];
    } else { // Parts not included in numbering
        for (auto i = 1; i <= level; i++) {
            result << levels[i];
            if (i < level) {
                result << ".";
            }
        }
    }
    // msg() << level << sp_arrow << result.str() << sp_arrow << "\n";
    return result.str();
}


std::string extract_id(const std::string& attr)
{
    std::smatch match {};
    if (!std::regex_match(attr, match, id_attr_rgx)) {
        throw Internal_error("Pattern for id attribute incorrect: " + q_(attr));
    }
    return match[1];
}

std::vector<Heading>
Document_class::insert_section_numbers(const std::string& marker, bool add_to_toc)
{
    std::vector<int> levels(9, 0);
    std::smatch match {};
    // The heading text (group 4) runs to the heading element's OWN closing
    // tag (the \1 backreference), not to the first "<": a nested element in
    // a section title -- @c's <span class="code">, an @i's <em> -- would
    // otherwise end the capture early and silently truncate the title in
    // BOTH tables of contents (article's single_page_toc and book's
    // navigation TOC read the same capture).  Found 2026-08-22 via a @c in
    // an @s2 title; heading.cpp's section_rgx already used the
    // close-on-own-tag idiom.
    std::string pattern = R"(<([\w-]+)\s*(.*?)>(.*?)MARKER\s*</span>\s*(.*?)</\1>.*)";
    pattern = string_replace(pattern, "MARKER", marker);
    std::regex heading_rgx(pattern);
    std::vector<std::pair<std::string, std::string>> modified_components {};
    std::vector<Heading> headings;
    bool include_toc = m_structure != docstruct_t::plain;
    for (auto [src, result] : m_file_components) {
        std::stringstream modified {};
        for (std::string line : regex_split(result, newline_rgx, false)) {
            if (std::regex_match(line, match, heading_rgx)) {
                // xheading
                std::string tag = match[1];
                std::string id = extract_id(match[2]);
                std::string title = match[3];
                std::string heading = match[4];
                const int level = html_tags[match[1]];
                // msg() << line << sp_arrow << broken_bar << tag << broken_bar
                //     << level << broken_bar << "ATTR" << broken_bar << title << broken_bar
                //     << heading << broken_bar << "\n";
                std::string number = increment_level(level, levels);
                line = string_replace(line, marker, number);
                if (include_toc) {
                    line = "<a href=\"#_c" + id + "\" draggable=\"false\">" + line + "</a>";
                }
                //msg() << attr << " " << number << " " << heading << "\n";
                if (add_to_toc) {
                    m_toc_components.push_back({tag, id, number, heading});
                }
                Heading hd(level, src, number, id, number, heading);
                headings.push_back(hd);
                // msg() << line << "\n\n";

            }
            modified << line << "\n";
        }
        modified_components.push_back({src, modified.str()});
    }
    m_file_components = modified_components;
    return headings;
}    

int get_caption_number(std::map<std::string, int>& numbers, const std::string& label)
{
    int result;
    if (numbers.count(label) == 0) {
        result = 1;
        numbers[label] = 2;
    } else {
        result = numbers[label];
        numbers[label]++;
    }
    return result;
}

void Document_class::insert_html_caption_numbers()
{
    // msg() << "insert_html_caption_numbers\n";
    std::regex chapter_rgx(R"(.*sectionnumber">(\d+)</span>.*)");
    std::regex caption_rgx(R"(__CAPTION__(.*?)__CAPTION__(.*?)__CAPTION__)");
    std::string current_chapter {};
    std::map<std::string, int> current_caption_number {};
    std::vector<std::pair<std::string, std::string>> modified_components {};
    
    for (auto [src, result] : m_file_components) {
        std::stringstream modified {};
        //std::vector<std::string> references {};
        for (std::string line : regex_split(result, newline_rgx, false)) {
            std::smatch chapter_match {};
            if (std::regex_match(line, chapter_match, chapter_rgx)) {
                current_chapter = chapter_match[1];
                // msg() << "Chapter " << current_chapter << " " << line << "\n";
                current_caption_number.clear();
            }
            // msg() << "Line: " << line << "\n";
            std::smatch caption_match {};
            if (std::regex_search(line, caption_match, caption_rgx)) {
                std::string label = caption_match[1];
                std::string ctext = caption_match[2];
                int caption_number = get_caption_number(current_caption_number, label);
                // msg() << "  caption: " << current_chapter << "." << caption_number << "\n";
                // msg() << "Label: " << label << " Text: " << ctext << "\n";
                std::stringstream ss {};
                ss << label << " ";
                if (!current_chapter.empty()) {
                    ss << current_chapter << ".";
                }
                ss << caption_number << ctext;
                line = std::regex_replace(line, caption_rgx, trim(ss.str()));
                // msg() << "line: " << line << "\n";
                // references.push_back(line);
            }
            // if (std::regex_search(line, reference_rgx)) {
                // references.push_back(line);
            // }
            modified << line << "\n"; 
        }
        modified_components.push_back({src, modified.str()});
        //m_reference_sequences.push_back(references);
    }
    m_file_components = modified_components;
}


std::vector<std::tuple<std::string, std::string, std::string>>
Document_class::reference_list(const std::string& target_marker)
{
    std::regex reference_rgx("__REF__");
    std::regex target_rgx("<div id=\"(.*?)\" data-label=\"(.*?)\" class=\"caption_");
    std::vector<std::tuple<std::string, std::string, std::string>> references {};
    for (auto [src, result] : m_file_components) {
        for (std::string line : regex_split(result, newline_rgx, false)) {
            if (std::regex_search(line, reference_rgx)) {
                references.push_back({target_marker, src, line});
            } else {
                std::smatch match {};
                if (std::regex_search(line, match, target_rgx)) {
                    references.push_back({"", match[2], match[1]});
                }
            }
        }
    }
    return references;
}
    
std::map<std::string, std::string> Document_class::id_to_caption_number()
{
    std::regex target_rgx("<div id=\"(.*?)\" data-label=\"(.*?)\" class=\"caption_");
    std::regex caption_rgx(R"(<div id=.*?>(\w+\s+[\w.]+))");
    std::map<std::string, std::string> result {};
    for (auto [src, ftext] : m_file_components) {
        auto lines = regex_split(ftext, newline_rgx, false);
        for (unsigned int i = 0; i < lines.size(); i++) {
            std::smatch match {};
            if (std::regex_search(lines[i], match, target_rgx)) {
                std::string id = match[1];
                unsigned int j = i + 1;
                std::smatch caption_match {};
                while (j < lines.size()) {
                    if (std::regex_search(lines[j], caption_match, caption_rgx)) {
                        std::string caption = caption_match[1];
                        // msg() << id << sp_arrow << caption << "\n";
                        result[id] = caption;
                        break;
                    }
                    j++;
                }
            }
        }
    }
    return result;
}
    

std::pair<int, int> offset_spec_to_count(const std::string& spec)
{
    std::regex rgx(R"((\w+)\s*(\d*))");
    std::smatch match {};
    int offset = 1;
    int dir = 1;
    if (std::regex_match(spec, match, rgx)) {
        std::string desc = match[1];
        std::string value = match[2];
        if (!value.empty()) {
            offset = std::stoi(value);
        }
        if (desc == "before") {
            dir = -1;
        }
    } else {
        throw Internal_error("Reference offset spec incorrect: " + spec);
    }
    return {offset, dir};
}

std::map<std::string, std::vector<std::pair<std::string, std::string>>>
Document_class::html_references()
{
    std::string target_marker = "@";
    auto references = reference_list(target_marker);
    // msg() << "\n";
    for (unsigned int i = 0; i < references.size(); i++) {
        auto [mark, name, ref] = references[i];
        // msg() << "  " << i << ":  " << mark << " " << name << " " << ref << "\n";
    }
    // msg() << "\n";
    auto id_captions = id_to_caption_number();

    std::regex ref_rgx(R"(__REF__(.*?)__(.*?)__)");
    std::string offset_spec;
    std::string type;

    std::map<std::string, std::vector<std::pair<std::string, std::string>>> result {};

    for (unsigned int i = 0; i < references.size(); i++) {
        auto [mark, name, ref] = references[i];
        // msg() << "  " << i << ":  " << mark << " " << name << " " << ref << "\n";
        if (mark == target_marker) {
            std::smatch match {};
            if (std::regex_search(ref, match, ref_rgx)) {
                offset_spec = match[1];
                type = match[2];
            } else {
                throw Internal_error("Reference form is incorrect: " + ref);
            }
            auto [offset, dir] = offset_spec_to_count(offset_spec);
            int j = i + dir;
            while (offset > 0 && j >= 0 && j < (int)references.size()) {
                // msg() << "   check: " << std::get<2>(references[j]) << "\n";
                if (std::get<1>(references[j]) == type) {
                    offset--;
                }
                if (offset == 0) {
                    break;
                }
                j += dir;
            }
            if (j < 0 || j >= (int)references.size()) {
                // msg() << "Not found: " << std::get<2>(references[i]) << "\n";
            } else {
                std::string id = std::get<2>(references[j]);
                std::string line = std::get<2>(references[i]);
                // msg() << "Ref: " << line << boldblack << " found: " << black << id << "\n";
                std::string link = "<a href=\"#" + id + "\">" + id_captions[id] + "</a>";
                // msg() << link << "\n";
                std::string modified_line = std::regex_replace(line, ref_rgx, link);
                // msg() << "modified_line: " << name << sp_arrow << modified_line << "\n";
                result[name].push_back({line, modified_line});
            }
        }
    }
    return result;
}
    
void Document_class::resolve_html_references()
{
    auto references = html_references();
    std::vector<std::pair<std::string, std::string>> modified_components {};
    for (auto [src, ftext] : m_file_components) {
        // msg() << "resolve " << src << ":\n";
        std::string modified_text = ftext;
        for (auto [old_line, new_line] : references[src]) {
            // msg() << old_line << sp_arrow << new_line << "\n";
            modified_text = string_replace(modified_text, old_line, new_line);
        }
        modified_components.push_back({src, modified_text});
    }
    m_file_components = modified_components;
}

std::string Document_class::single_page_toc()
{
    // <h1 id="_e9"><span class="sectionnumber">2.1</span> Section two.one</h1>
    std::regex id_rgx("id=\"(.*?)\"");
    std::stringstream ss {};
    ss << "<div class=\"toctitle\">Contents</div>\n";
    int minimum_depth = 7;
    // Adjust left minimum margin based on whether parts or chapters are the top level:
    for (auto [tag, id, number, heading] : m_toc_components) {
        minimum_depth = std::min(minimum_depth, html_tags[tag]);
    }
    for (auto [tag, id, number, heading] : m_toc_components) {
        int depth = html_tags[tag];
        // msg() << "TOC: " << depth << " " << id << " " << number << " " << heading << "\n";
        ss << "<div id=\"_c" << id << "\" class=\"tab" << depth - minimum_depth << " toc\"><a href=\"#" << id << "\">"
           << "<span class=\"tocnumber\">" << number << "</span>" << heading << "</a></div>\n";
    }
    // msg() << ss.str();
    return ss.str();
}


elements_t Document_class::page(const std::string& body_text, const std::string& output_dir, int max_level)
{
    (void)K::log(3);
    std::string toc {}; // Calculate
    bool text_only = m_structure == docstruct_t::plain;

    // msg() << "output_dir: " << output_dir << "\n";

    auto [css_filenames, js_filenames] =
        html_auxiliary_files(output_dir, "", m_css_filenames);
    
    std::string toc_text {};

    elements_t body {};
    html::add_title(body, m_title, m_logo);
    if (m_structure == docstruct_t::book) {
        html::add_nav(body, max_level);
    }
    html::add_text(body, body_text, toc_text, text_only);
    // html::add_bottom_spacer(body);
    html::add_status_bar(body, m_date, m_version, m_copyright);
    if (!text_only) {
        html::add_page_cache(body);
    }
    html::add_js_links(body, js_filenames, output_dir) ;

    std::string css_text = color_definitions() + font_definitions() + "\n" + m_css_text;

    std::string page_title = m_page_title.empty() ? m_title : m_page_title;

    elements_t page = html::make_page(
        body, page_title,
        css_text, css_filenames, resolved_font_names());

    return page;
}

std::string Document_class::make_single_html_page(const std::string& output_directory)
{
    (void)K::log(3);
    std::stringstream ss {};;

    if (m_structure != docstruct_t::plain) {
        ss << single_page_toc();
    }
    for (auto [basename, page_text] : m_file_components) {
        ss << "<!-- " << basename << ".kt -->\n\n" << page_text << "\n";
    }
    std::string single_page = to_string(page(ss.str(), output_directory, 10));
    std::string output_file = output_directory + "/index.html";
    // msg() << "single_page to " << output_file << "\n";
    string_to_file(output_file, single_page);
    return "";

}

std::string Document_class::make_html_navigation_structure(
    const std::string& output_directory, std::vector<Heading> headings)
{
    // Which structure path was taken is a DERIVED fact -- ":structure book"
    // chose it -- so it belongs at "-v 1".  It was a msg(), which printed it
    // to stdout on every book render, ahead of the document.
    (void)K::log(1, "Navigation format");
    std::vector<std::string> pages_basenames {};

    for (auto [basename, page_text] : m_file_components) {
        pages_basenames.push_back(basename);
        // A trace of the work, not a decision: verbosity 3.
        (void)K::log(3, basename + ": " + abbrev(page_text, 96));
    }
    std::string pages_basenames_filename = m_cache_dir + "/_pages_basenames.js";
    write_basenames_js_file(pages_basenames_filename, pages_basenames);
    std::string help_content = make_help_page();

    // Build embedded pagecache to avoid Same Origin Policy errors with file:// protocol
    std::stringstream pagecache;
    pagecache << "<div id=\"pagecache\" style=\"display:none\">\n";
    for (auto [basename, page_text] : m_file_components) {
        pagecache << "<div id=\"_page_" << basename
                  << "\" class=\"content_page\">"
                  << page_text << "</div>\n";
    }
    pagecache << "<div id=\"help_page\">" << help_content << "</div>\n";
    pagecache << "<div id=\"search_page\"></div>\n";
    pagecache << "</div>\n";

    std::pair<int, std::string> toc_spec = make_navigation_table_of_contents(headings);
    int max_level = std::get<0>(toc_spec);
    std::string toc_body = std::get<1>(toc_spec);
    std::string nav_body = "Navigation";
    auto [all_css_files, all_js_files] =
        html_auxiliary_files(output_directory, pages_basenames_filename, m_css_filenames);
    std::string css_text = color_definitions() + font_definitions() + "\n" + m_css_text;
    std::stringstream t {};
    t << html::page(
        m_title, m_page_title, output_directory, nav_body, max_level, toc_body,
        m_file_components[0].second,
        m_date, m_version, m_copyright,
        css_text, all_css_files, all_js_files,
        resolved_font_names(),
        m_logo);
    std::string result = t.str();
    result = string_replace(result,
        "<div id=\"pagecache\"></div>",
        pagecache.str());
    std::string output_basename = m_machine.m_state.value("K_output_basename");
    std::string output_filename = output_directory + "/index.html";
    write(output_filename, result);
    return "";
}


std::string Document_class::html()
{
    (void)K::log(3);
    // msg() << "HTML document\n";
    // m_cache_dir = cache_directory(kt_root_filename, "_html_pages");
    std::string output_directory = create_html_output_directories();
    save_string_input_as_file();
    process_input_files();
    auto headings = insert_section_numbers();
    insert_section_numbers("___NUM2___", false);
    insert_html_caption_numbers();
    resolve_html_references();
    if (m_structure != docstruct_t::book) {
        make_single_html_page(output_directory);
    } else {
        make_html_navigation_structure(output_directory, headings);
    }
    return "";
}
