import { type CooperativeOption } from '../utils.js';
import type { CodeNode, CodebaseIndex, ParsedFile } from './types.js';
export interface StructuralLanguage {
    name: string;
    grammar: string;
    extensions: readonly string[];
}
export declare function structuralLanguages(): readonly StructuralLanguage[];
export declare function resolveStructuralLanguage(language: string): StructuralLanguage | null;
export declare function inferStructuralLanguage(filePath: string): StructuralLanguage | null;
export interface StructuralQueryLineRange {
    /** 1-based first line a capture may start on. */
    startLine: number;
    /** 1-based last line a capture may end on (inclusive). */
    endLine: number;
}
export interface StructuralQueryOptions {
    language: string;
    query: string;
    limit?: number;
    mapTokens?: number;
    tokenizer?: string;
    countTokens?: (text: string) => number;
    /**
     * Stop after the first capture whose serialized payload exceeds the
     * remaining token budget. This bounds work but can skip a smaller later
     * capture that the default full scan would admit.
     */
    budgetEarlyExit?: boolean;
    signal?: AbortSignal;
    cooperative?: CooperativeOption;
    maxQueryBytes?: number;
    maxSourceBytes?: number;
    maxNodesPerFile?: number;
    maxDiagnostics?: number;
    maxJoinCandidates?: number;
    maxJoinNodes?: number;
    /**
     * Restrict the query to files whose repo-relative path equals one of these
     * prefixes or sits under it as a directory. Empty/omitted means every file
     * the language claims.
     */
    files?: readonly string[];
    /**
     * Only return matches fully contained in this line range, enforced by the
     * query cursor itself rather than post-filtering, so bounded scans spend
     * their budget inside the range.
     */
    containingLines?: StructuralQueryLineRange;
}
export interface StructuralQueryCandidate {
    nodeId: string;
    name: string;
    type: CodeNode['type'];
    line?: number;
    endLine?: number;
}
export interface StructuralQueryHit {
    capture: string;
    nodeType: string;
    text: string;
    relativePath: string;
    startLine: number;
    startColumn: number;
    endLine: number;
    endColumn: number;
    nodeId?: string;
    symbol?: string;
    /** How the capture-to-index join was decided. */
    joinStatus?: 'resolved' | 'ambiguous' | 'unresolved';
    /** Exact columns are preferred; older compiler-indexed nodes may only have lines. */
    joinBasis?: 'exact-span' | 'line-span';
    /** Bounded candidates are present only when an equally-small join is ambiguous. */
    nodeCandidates?: StructuralQueryCandidate[];
}
export interface StructuralQueryResult {
    status: 'resolved';
    language: string;
    grammar: string;
    query: string;
    /** Present when the query was scoped; echoes the restriction that was applied. */
    scope?: {
        files?: string[];
        containingLines?: StructuralQueryLineRange;
    };
    hits: StructuralQueryHit[];
    totalCaptures: number;
    omitted: number;
    truncated: boolean;
    filesParsed: number;
    filesSkipped: number;
    mapTokens: number;
    tokensUsed: number;
    tokenizer: string;
    diagnostics: string[];
    honesty: {
        freshness: 'unverified';
        stalenessReason: string;
        coverage: 'full' | 'partial';
        truncated: boolean;
        omitted: number;
        filesSkipped: number;
        unjoinedCaptures: number;
        ambiguousSpanJoins: number;
        eligibleFiles?: number;
        filesQueried?: number;
        syntaxErrorFiles?: number;
        parserCoveragePartialFiles?: number;
        nodeLimitFiles?: number;
        joinSearchTruncated?: number;
        diagnosticsOmitted?: number;
        matchLimitHit?: boolean;
        captureLimitHit?: boolean;
        /** The opt-in budget work bound stopped the scan before all captures were observed. */
        budgetEarlyExitHit?: boolean;
    };
}
export interface IndexedJoinNode {
    node: CodeNode;
    exactStartByte?: number;
    exactEndByte?: number;
}
/**
 * A matched source region in join coordinates: canonical UTF-8 byte offsets
 * plus 1-based lines and 1-based UTF-16 columns. Both the tsquery and the
 * ast-grep surfaces reduce their engine-native offsets to this shape before
 * joining against indexed nodes.
 */
export interface JoinCaptureSpan {
    startByte: number;
    endByte: number;
    startLine: number;
    startColumn: number;
    endLine: number;
    endColumn: number;
}
export interface CaptureJoin {
    node?: CodeNode;
    ambiguous: boolean;
    basis?: 'exact-span' | 'line-span';
    candidates?: StructuralQueryCandidate[];
    searchTruncated: boolean;
}
export declare function joinSpanToNode(nodes: readonly IndexedJoinNode[], span: JoinCaptureSpan, maxJoinNodes: number, maxCandidates: number): CaptureJoin;
/**
 * Bounded single-line rendering of a matched region. Avoids materializing the
 * full text of a capture: one match can span a multi-megabyte file, and a
 * broad query can return tens of thousands of them. Offsets are UTF-16 —
 * exactly what String#slice expects — never UTF-8 bytes.
 */
export declare function compactSourceText(source: string, startIndex: number, endIndex: number): string;
export declare function matchesLanguagePath(relativePath: string, language: StructuralLanguage): boolean;
export declare function filesForLanguage(index: CodebaseIndex, language: StructuralLanguage): ParsedFile[];
export declare function normalizeScopePrefixes(files: readonly string[] | undefined): string[] | undefined;
export declare function matchesScope(relativePath: string, prefixes: readonly string[] | undefined): boolean;
/**
 * Sorted per-path join candidates for capture-to-node containment joins.
 * Exact spans come from the node itself or, as a fallback, from the parsed
 * file's symbol table keyed by nodeId.
 */
export declare function indexJoinNodesByPath(index: CodebaseIndex): Map<string, IndexedJoinNode[]>;
export declare function queryTreeSitter(index: CodebaseIndex, options: StructuralQueryOptions): Promise<StructuralQueryResult>;
export interface StructuralAstOptions {
    language: string;
    source: string;
    sourceKind?: 'snippet' | 'file';
    relativePath?: string;
    mapTokens?: number;
    tokenizer?: string;
    countTokens?: (text: string) => number;
    sliceTokens?: (text: string, start: number, end: number) => string;
    signal?: AbortSignal;
    cooperative?: CooperativeOption;
    maxSourceBytes?: number;
    maxNodes?: number;
    maxDiagnostics?: number;
    maxRenderCharacters?: number;
}
export interface StructuralAstResult {
    status: 'resolved';
    language: string;
    grammar: string;
    source: {
        kind: 'snippet' | 'file';
        relativePath?: string;
    };
    tree: string;
    truncated: boolean;
    mapTokens: number;
    tokensUsed: number;
    tokenizer: string;
    diagnostics?: string[];
    honesty: {
        freshness: 'source-input';
        coverage: 'full' | 'partial';
        truncated: boolean;
        ambiguityCount: 0;
        nodesVisited?: number;
        nodeLimitHit?: boolean;
        renderLimitHit?: boolean;
        tokenLimitHit?: boolean;
        syntaxErrors?: number;
        diagnosticsOmitted?: number;
    };
}
/**
 * Render a bounded tree-sitter concrete syntax tree (parse tree).
 *
 * The historical `Ast` name is retained as a compatibility alias for the CLI
 * and facade even though tree-sitter exposes a CST rather than a semantic AST.
 */
export declare function parseTreeSitterAst(options: StructuralAstOptions): Promise<StructuralAstResult>;
