// SPDX-License-Identifier: MIT
#pragma once
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <vector>
#include "cparse/Parser.hh"
namespace fedem
{
namespace fson
{
class Document;
class Object;
// ─────────────────────────────────────────────────────────────────────
// IncludeResolver
//
// Resolves the "%include" directives of a freshly parsed Document into a
// query-visible view, following the Topic 10 decisions:
//
// • merge ("%include \"base.fson\"") — the target's root-object
// members are shallow-merged into the including object as defaults;
// a key already present in the host wins (host defaults over base).
// Only top-level keys are compared — nested objects are not deep-
// merged (a host "logging" wholly shadows the base "logging").
// • alias ("%include \"base.fson\" as v") — the target's root value is
// bound under key "v" (open-root: the target need not be an object),
// unless the host already has an active member "v" (host wins).
//
// Merged/aliased members are appended to the host object with their
// origin set to the target path, so:
// • the query API sees them (merge view), and
// • the writer skips them (they never round-trip into the host file),
// and
// • setPath can route a write back to the origin file.
//
// A disabled directive ("--%include") is preserved structurally but not
// resolved (its defaults are not applied).
//
// Resolution is recursive: a target's own %include directives are
// resolved before its members are merged in, so transitive defaults
// work. Relative target paths are resolved against the directory of the
// including file (not the process CWD), so a file tree is relocatable.
//
// Cycles (a → b → a) are reported as errors and broken (the offending
// directive contributes nothing); no exception is thrown, matching the
// collect-and-continue contract. Every file opened during resolution is
// recorded in dependencies() so a caller (Topic 11) can watch them.
// ─────────────────────────────────────────────────────────────────────
class IncludeResolver final
{
public:
IncludeResolver( );
// Resolves every directive reachable from the document's root value.
// 'basePath' is the path of the file the document was parsed from;
// its parent directory anchors relative include targets. Errors are
// appended to errors(); dependencies are recorded in dependencies().
void resolve( Document& document, std::string const& basePath );
std::vector< parser::ParseError > const& errors( ) const noexcept;
// Every included file opened during resolution, in first-seen order,
// NOT counting the host file itself. This is what LoadResult exposes
// as `dependencies` for backward compatibility.
std::vector< std::string > const& dependencies( ) const noexcept;
// The complete set of files needed to resolve the document — the
// host file PLUS every included file — de-duplicated and sorted.
// This is the set a reload watcher should observe: a change to any
// of them can alter the resolved document.
std::set< std::string > const& dependencySet( ) const noexcept;
private:
std::vector< parser::ParseError > collectedErrors;
std::vector< std::string > dependencyPaths; // includes only
std::set< std::string > allFiles; // host + includes
std::vector< std::string > activeStack; // cycle detection
// Resolves the directives of one object, whose own file is 'owner
// directory' (used to anchor relative targets) and whose canonical
// path is 'ownerCanonical' (used for cycle detection).
void resolveObject( Object& object,
std::string const& ownerDirectory );
// Loads, parses and fully resolves the target file, returning its
// document. Returns nullptr on open/parse failure or on a cycle
// (an error is recorded in either case). 'canonical' receives the
// canonical path actually used.
std::unique_ptr< Document > loadTarget( std::string const& target,
std::string const& ownerDirectory,
std::string& canonical );
void recordDependency( std::string const& canonical );
};
} // end namespace fson
} // end namespace fedem