#pragma once

#include <iosfwd>
#include <string>
#include <vector>

#include "util.h"      // strings_t

class Machine;

// Target coverage: which targets a klammer can actually render to.
//
// Coverage is a FACT about a klammer, distinct from a klammer set's claim
// about what it supports and from what happens when a document meets a
// target.  This module computes the fact, and computes only what can be
// computed -- it changes nothing about how the Machine behaves.  See
// notes/target_coverage.md for why the fact has to come first.
//
// Three rules, and the whole of the analysis is deciding which one applies:
//
//   DERIVED   where coverage is structurally determined.  A general
//             definition (no target suffix) whose body is text and nothing
//             else covers every target.  One whose body calls other klammers
//             covers the INTERSECTION of what those klammers cover -- a
//             klammer can only render where everything it is made of renders.
//
//   DECLARED  where coverage depends on something the engine cannot
//             interpret.  A general body holding an @eval is the main case:
//             deciding which targets a Python function answers for is
//             undecidable, so the targets have to be written down (which is
//             what the comma-separated target list is for).  A general body
//             holding a ^'...'^ literal span is the same problem wearing
//             different clothes -- the span exists precisely to carry raw
//             target markup past the escaping pass, so a body containing one
//             is target-specific with nothing for the intersection rule to
//             see.  @read is included: its content is not known statically.
//
//   DECLARED-ALL  a definition written ".*" asserts that the klammer works for
//             EVERY target, including targets that do not exist yet.  A list
//             of the targets defined today cannot say that.  It is the one
//             target declaration a machine could later falsify: a ".*"
//             klammer whose implementation branches per target is
//             contradicting itself, which is a structural property.
//
//   UNKNOWN   where neither applies -- no definition at all.  Absence means
//             "not decided yet", never "deliberately unavailable": the SKS is
//             incomplete on schedule rather than by design, so nothing may
//             read a missing definition as a statement of intent.
//
// The intersection is computed as a GREATEST FIXPOINT rather than by
// recursion: general klammers may call each other, and a cycle would not
// terminate.  Every general klammer starts at "all targets" and the rule is
// applied until nothing shrinks, which terminates because the sets only ever
// lose members.  The pass runs over the whole registry AFTER loading, not at
// definition time -- definitions load in file order, so a body may call a
// klammer defined later.
enum class coverage_t {
    all_declared, // written ".*": every target, including ones not yet defined
    all,          // general body, no klammer calls: every target
    derived,      // general body of klammer calls: their intersection
    declared,     // written per target, no general body to derive from
    undecidable,  // general body holding @eval, @read, or a literal span
    none,         // declared (.k) but never defined
};

struct Klammer_coverage
{
    std::string m_name {};
    coverage_t m_kind { coverage_t::none };
    // The targets this klammer can render to, as computed.
    std::vector<std::string> m_targets {};
    // Targets the Machine currently offers it for.  These differ exactly
    // where a general body is copied to targets it cannot really serve, which
    // is the hazard the report exists to surface.
    std::vector<std::string> m_effective {};
    // Targets named in a written definition (the comma-list, or one per
    // definition), empty for a purely general klammer.
    std::vector<std::string> m_written {};
    // For `derived`: the klammers the body calls.  For `undecidable`: why.
    std::vector<std::string> m_from {};
    std::string m_reason {};
    // Whether a ".k" declaration exists.  A klammer without one still works
    // -- its parameters can be declared on the definition itself -- but it
    // has no DESCRIPTION, so kdesc can say nothing about what it does and
    // "kdesc -k <text>" can only find it by name.
    bool m_declared { false };
    // The file(s) the klammer is written in, in definition order.  Usually
    // one -- a klammer's targets are declared together -- but a klammer whose
    // definitions are spread over several files lists them all.  Shown by
    // "--coverage -v", and only on a row that names ONE klammer.
    std::vector<std::string> m_files {};
};

// Compute the coverage of every klammer the machine has loaded.  Analysis
// only: nothing in the Machine is modified.
// `defined_outside` restricts the RESULT to klammers with a definition from a
// file not in the list.  The ANALYSIS still sees every klammer: a general
// body's coverage is the intersection of what the klammers it calls cover, and
// most of those come from the klammerset.  So the klammersets are loaded, used
// to derive, and left out of the report -- which is what "kdesc -i" wants.
std::vector<Klammer_coverage> klammer_coverage(const Machine& machine,
                                               const strings_t& defined_outside = {});

// The report behind "kdesc --coverage".  `full` ("--coverage all") adds the
// source-file column and shows every "Needs attention" category, including
// the empty ones; without it those categories appear only when they have
// entries, so a klammer set with nothing wrong produces a short report.
//
// It is an argument rather than a verbosity level because the two are
// independent: -v says how much to show about the command's PROCESSING, and
// this says what the command's RESULT contains.
//
// Audiences, in the project's terms: an AUTHOR runs it to see what is
// available for a target; a DESIGNER runs "--coverage all" to be reminded of
// the full set of categories while building a klammer set.
void report_coverage(const Machine& machine,
                     const std::vector<Klammer_coverage>& coverage,
                     bool full, std::ostream& os);
