import { AgentProfileFileMount } from '@tangle-network/sandbox';

/**
 * Unified skill + corpus mounter for agent products.
 *
 * Every agent product hand-rolls the same two file-mount systems and then
 * drifts on the seams between them. (1) An ALWAYS-MOUNTED markdown corpus —
 * (`skills/<slug>/SKILL.md`, `doctrine` and `knowledge` markdown trees) — discovered
 * by a Vite `?raw` glob in the Worker bundle and by Node `fs` under the eval
 * CLI, then projected into `resources.files`. (2) A TIER-GATED installable
 * registry — a hand-authored array of `SkillEntry` whose free tier mounts at
 * the harness skill-discovery path and whose paid tier is installed on demand.
 * Both ride the same `resources.files` channel but use different provenance
 * (file-backed vs inline), different mount paths (relative corpus path vs
 * `~/.claude/skills/<id>/SKILL.md`), and different selection rules. This module
 * makes both DATA: a corpus loader that accepts a Vite glob-result map (or an
 * fs fallback), a registry adapter that tier-gates, and a single
 * `composeShellResources` that projects either onto the SDK file-mount shape.
 *
 * Substrate-free over storage, exact over the SDK boundary: the only inbound
 * seam is the glob-result map the consumer passes in (its call site keeps the
 * literal `import.meta.glob` Vite must static-analyze); the only outbound seam
 * is `@tangle-network/sandbox`'s `AgentProfileFileMount[]`, the exact shape the
 * agent profile's `resources.files` consumes. Node builtins are resolved lazily
 * via `process.getBuiltinModule` so a static `node:*` import never reaches the
 * Vite SSR bundle.
 */

/** A Vite eager `?raw` glob result: glob key -> raw file body. The consumer
 *  produces this by calling `import.meta.glob('<lit>', { eager: true, query:
 *  '?raw', import: 'default' })` at its own call site — the literal must stay
 *  literal so Vite can static-analyze it; passing the result here keeps that
 *  constraint at the edge and the loader substrate-free. */
type GlobModules = Record<string, string>;
/** One markdown document discovered from the corpus. */
interface CorpusEntry {
    /** Slug derived from the glob key (folder slug for `SKILL.md` layouts, or the
     *  normalized relative path for flat `*.md` layouts). */
    id: string;
    /** Glob/fs key the entry was loaded from, normalized to a stable relative
     *  form (leading `./` and absolute prefixes stripped). */
    key: string;
    /** Raw markdown body (including any frontmatter). */
    content: string;
}
/** A hand-authored, tier-gated installable skill. Mirrors the per-product
 *  registry entry (gtm/insurance `SkillEntry`); the runtime's certified `skill`
 *  artifact kind is unrelated. `skillMd` is the inline body — file provenance
 *  does not apply to the registry. */
interface SkillEntry {
    id: string;
    name: string;
    description: string;
    author?: {
        name: string;
        url?: string;
    };
    source?: string;
    category?: string;
    tags?: string[];
    /** Gate keyword. `composeShellResources`/`registrySkills` treat `free` as
     *  always-mounted; everything else is install-on-demand. */
    tier: string;
    skillMd: string;
}
/** Harness skill-discovery path the Claude Code / OpenCode backend reads
 *  natively. The registry mounts here; the corpus mounts at its relative path. */
declare function skillMountPath(id: string): string;
/** Options for {@link loadMarkdownCorpus}. */
interface LoadCorpusOptions {
    /** The anchor folder name that appears in both glob keys and fs paths
     *  (`skills`, `doctrine`, `knowledge`). Used to normalize keys + derive ids. */
    anchor: string;
    /** Vite glob-result map. When present and non-empty it is authoritative and
     *  the fs path is skipped. Omit it (or pass an empty map) only outside Vite. */
    globModules?: GlobModules;
    /** Absolute or `import.meta.url`-relative base dir the fs fallback walks when
     *  `globModules` is empty. Required for the fs path to run; without it the fs
     *  fallback returns no entries (Workers never need it). */
    fsBaseDir?: string;
    /** Walk strategy for the fs fallback. `nested` finds `<dir>/<slug>/SKILL.md`
     *  one level deep; `flat` recurses for every `*.md`. Default: `flat`. */
    fsLayout?: 'nested' | 'flat';
    /** Drop an entry by its normalized key after load. Covers the per-product
     *  skip lists (corpus index/log files, scaffold templates, allow-lists). */
    skip?: (normalizedKey: string) => boolean;
}
/** Outcome of {@link loadMarkdownCorpus}: the entries plus which path produced
 *  them, so a caller can fail loud when both are empty rather than silently
 *  mounting nothing. */
interface CorpusLoadResult {
    source: 'vite' | 'fs' | 'empty';
    entries: CorpusEntry[];
}
/**
 * Load a markdown corpus, preferring a Vite glob-result map and falling back to
 * a Node fs walk. Selection is by non-empty glob result — never an env flag.
 * Entries are normalized, optionally skip-filtered, and sorted by id for
 * determinism. The `import.meta.glob` literal stays at the CONSUMER call site
 * (passed in as `globModules`); this loader never constructs a glob.
 */
declare function loadMarkdownCorpus(options: LoadCorpusOptions, importMetaUrl?: string): CorpusLoadResult;
/** Project corpus entries onto SDK file mounts at a relative path under
 *  `<anchor>/`. Always-mounted: the corpus is the agent's baseline knowledge. */
declare function corpusSkills(corpus: CorpusEntry[], anchor: string): AgentProfileFileMount[];
/** Project the registry's free-tier (or `tier`-matched) entries onto SDK file
 *  mounts at the harness skill-discovery path. Tier-gating is the registry's
 *  only selection rule — paid skills are installed on demand, not at boot. */
declare function registrySkills(registry: SkillEntry[], tier?: string): AgentProfileFileMount[];
/** Inputs to {@link composeShellResources}. Each channel is optional so a
 *  product mounts only the systems it has — corpus-only, registry-only, or
 *  both — without conflating them. */
interface ComposeShellResourcesInput {
    /** Corpus mounts (always-mounted baseline). Pass the result of
     *  {@link corpusSkills}, or a hand-built mount list. */
    skills?: AgentProfileFileMount[];
    /** Knowledge-corpus mounts (a second always-mounted corpus, e.g. a domain
     *  knowledge pack distinct from the skills corpus). */
    knowledge?: AgentProfileFileMount[];
    /** Evolvable / learned-guidance mounts (single-file corpora). */
    evolvable?: AgentProfileFileMount[];
    /** Registry mounts (tier-gated). Pass the result of {@link registrySkills}. */
    registry?: AgentProfileFileMount[];
    /** Final skip filter applied to the composed mount list by mount `path`. */
    predicate?: (mount: AgentProfileFileMount) => boolean;
}
/**
 * Compose every mount channel into one `resources.files`-ready array. Corpus
 * channels come first (baseline), the tier-gated registry last (so a registry
 * entry can override a corpus entry that mounts at the same path). The result
 * is exactly `AgentProfileFileMount[]` — assign it straight into
 * `profile.resources.files` with no cast.
 */
declare function composeShellResources(input: ComposeShellResourcesInput): AgentProfileFileMount[];

export { type ComposeShellResourcesInput, type CorpusEntry, type CorpusLoadResult, type GlobModules, type LoadCorpusOptions, type SkillEntry, composeShellResources, corpusSkills, loadMarkdownCorpus, registrySkills, skillMountPath };
