#include "klammer.h"
#include "klammer_registry.h"
#include "show.h"
#include "util.h"
#include "log.h"
#include "error.h"

/*
bool Klammer_registry::has(std::string name, std::string target)
{
    return m_klammers.count(name) > 0;
}
*/

void Klammer_registry::add(
    const Argtype_registry& argtypes, const Target_registry& targets,
    Option_set_registry& option_sets, katom_iter begin, katom_iter end, katom_list& katoms)
{
    (void)K::log(3, *begin, *(end - 1));
    restore_initial_type(begin, end);
    auto [klammer_name, target_names, general_declared] = parse_name(targets, *begin);
    if (is_in(Target_registry::optionset_name, target_names)) {
        // The Machine routes an ".o" definition to the option set registry;
        // reaching here means it did not.  parse_name has already rejected
        // ".o" as a member of a list, so this is the bare ".o" form.
        throw Internal_error(
            "The option set declaration \"" + klammer_name + ".o\" reached the klammer registry",
            begin->m_loc);
    }
    // Find the incoming definition mode
    katom_t incoming_deftype = katom_t::klammer_definition;
    for (auto it = begin + 1; it != end - 1; ++it) {
        if (is_deftype(it->m_type)) {
            incoming_deftype = it->m_type;
            break;
        }
    }
    defmode_t incoming_mode = defmode_from_katom(incoming_deftype);

    if (m_klammers.count(klammer_name) == 0) {
        m_klammers[klammer_name] = Klammer(klammer_name);
    }
    // Sticky: once a definition has written ".*", the klammer carries the
    // claim.  A klammer with both a ".*" body and a target-specific one still
    // asserts that the general body serves everything else.
    if (general_declared) {
        m_klammers[klammer_name].m_general_declared = true;
    }
    // One definition per target named.  A comma-separated list is surface
    // syntax: each target goes through the same registration, including the
    // redefinition transition table, so a list that collides with an existing
    // definition is decided per target -- one member may be silently ignored
    // or rejected while the others are created.
    for (const auto& target_name : target_names) {
        if (m_klammers[klammer_name].m_defloc.count(target_name) > 0) {
            defmode_t existing_mode = m_klammers[klammer_name].m_defmode[target_name];
            const auto& result = defmode_transition(existing_mode, incoming_mode);
            std::string name_target = klammer_name + "." + target_name;
            std::string at_desc = m_klammers[klammer_name].m_defloc[target_name].desc();
            if (!result.replace) {
                if (result.message.empty()) {
                    // Silent ignore (e.g., create + default)
                    continue;
                }
                std::string msg = result.message;
                msg = string_replace(msg, "NAME", q_(name_target));
                msg = string_replace(msg, "AT", at_desc);
                throw Definition_error(msg, begin->m_loc);
            }
            if (result.warn) {
                std::string msg = result.message;
                msg = string_replace(msg, "NAME", q_(name_target));
                msg = string_replace(msg, "AT", at_desc);
                // An override is not a warning: ":::" exists to override, and
                // a klammer set a user did not write may be overridden by
                // design (TODO #33).  A warning nobody can act on is not a
                // warning.  It IS worth reporting at "-v 1", because the
                // definition being replaced usually lives in another file, in
                // another klammerset, which the user cannot see from what they
                // wrote (Andy, 2026-08-15).
                (void)K::log(1, msg + " at " + begin->m_loc.desc());
            }
            m_klammers[klammer_name].remove_target_definition(target_name);
        }
        m_klammers[klammer_name].add_target_definition(
            target_name, argtypes, option_sets, begin + 1, end - 1);

        // This add's target:
        Target target = targets.get(target_name, begin->m_loc);
        for (const auto& provide_name : target.m_provides) {
            if (m_klammers[klammer_name].m_defloc.count(provide_name) > 0) {
                m_klammers[klammer_name].remove_target_definition(provide_name);
            }
            m_klammers[klammer_name].add_target_definition(
                provide_name, argtypes, option_sets, begin + 1, end - 1);
        }
    }

    modify_type(katom_t::replaced, begin, end);
    auto next_iter = end;
    if (next_iter < katoms.end()) {
        // std::cout << "next_iter: " << kindex << *next_iter << "\n";
    } else {
        // std::cout << "next_iter past end. katoms length: " << katoms.size() << "\n";
    }
    ignore_whitespace(next_iter, katoms);
}    

void Klammer_registry::rationalize(const Target_registry& targets)
{
    (void)K::log(3);
    for (auto& [name, klammer] : m_klammers) {
        klammer.rationalize(targets);
    }
}

/*
bool Klammer_registry::has(std::string name, std::string target)
{
    return m_klammers.count(name) > 0;
}
*/

void Klammer_registry::check_klammer(const std::string& name, const std::string& target, const Locator& loc) const
{
    if (m_klammers.count(name) == 0) {
        throw Definition_error("The klammer " + q_(name) + " is not defined for an unspecified target", loc);
    }
    const Klammer& k = m_klammers.at(name);
    if (k.m_defloc.count(target) == 0) {
        std::string desc;
        if (target == Target_registry::general_name) {
            desc = "an unspecified target";
        } else {
            desc = "target " + q_(target);
        }
        throw Definition_error("The " + q_(name) + " klammer is not defined for " + desc, loc);
    }
}


const std::vector<Katom>* Klammer_registry::constant_body(const std::string& name) const
{
    auto it = m_klammers.find(name);
    if (it == m_klammers.end()) return nullptr;
    const Klammer& k = it->second;
    // A constant klammer has no parameters at all — neither in the general
    // definition nor inherited from a .k declaration.  A "::" instance has
    // empty d.parameters (it inherits), so we must also check that no other
    // definition (especially .k) declares parameters for this klammer.
    for (const auto& d : k.m_defs) {
        if (!d.parameters.empty()) return nullptr;
    }
    for (const auto& d : k.m_defs) {
        if (d.target == Target_registry::general_name && d.parameters.empty()) {
            return &d.body;
        }
    }
    return nullptr;
}

int max_length(const std::map<std::string, Klammer>& ss)
{
    size_t result = 0;
    for_each(ss.begin(), ss.end(),
             [&result](const auto& s) { result = std::max(result, s.first.size()); });
    return result;
}

std::string Klammer_registry::instance_list(int margin) const
{
    std::stringstream ss {};
    std::string tab(margin, ' ');
    auto name_width = max_length(m_klammers);
    for (const auto& [name, k] : m_klammers) {
        ss << tab << std::setfill(' ') << std::setw(name_width) << name
           << sp_arrow << k << "\n";
    }
    return ss.str();
}

std::string Klammer_registry::describe(int margin, const std::string& search,
                                       const strings_t& defined_outside) const
{
    /*
    strings_t names {};
    std::vector<strings_t> targets {};
    strings_t locations {};
    */
    std::string result;
    std::string query = collapse_whitespace(search);
    for (const auto& [name, k] : m_klammers) {
        if (!defined_outside.empty() && !k.defined_outside(defined_outside)) {
            continue;
        }
        if (!query.empty() &&
            !contains_fold(name, query) &&
            !contains_fold(collapse_whitespace(k.description_text()), query)) {
            continue;
        }
        result += k.describe(margin) + "\n";
        /*
        names.push_back(name);
        targets.push_back(k.get_target_names());
        std::cout << name << " " << k.get_target_names() << "\n";
        locator_summary(k.get_locations());
        //locations.push_back(
        */
    }
    return result;
}
