import { z } from 'zod';
import { NormalizedSymbolKindSchema, ParserCapabilitiesSchema, ParserDiagnosticSchema, ParserSyntaxStatusSchema, } from '../types.js';
import { FormatEntityKindSchema } from '../formats/types.js';
import { OccTestReasonSchema } from '../tests.js';
import { ENTITY_CST_PROOF, ENTITY_SIGNATURE_PROOF, WHOLE_DIFF_GRAMMAR_MANIFEST_DIGEST, grammarManifestEntry, } from './grammar-manifest.js';
/** Cache format version. A bump rebuilds persisted evidence, not lineage ids. */
export const ENTITY_CACHE_SCHEMA_VERSION = 4;
/**
 * Lineage ids are a public durable identity, independent from the replaceable
 * cache encoding. Cache V4 rebuilds parser/template evidence without renaming the same
 * observed births.
 */
export const ENTITY_LINEAGE_ID_VERSION = 1;
/** A code entity as it exists at one revision. */
/**
 * What an entity can be: a code symbol, or a data-format node.
 *
 * One union rather than two parallel record types, because lineage, diff,
 * blame, and history are identical for both — only the extraction differs.
 */
/**
 * Test blocks, which are entities but not declarations.
 *
 * In the dominant JS/TS style a test body is an anonymous callback passed to
 * `it(...)`, so it has no declared name and no `NormalizedSymbolKind` fits it.
 * Without a kind of its own, everything it calls attributes to the module and
 * reverse traversal cannot reach the test at all.
 */
export const EntityTestKindSchema = z.enum(['test', 'test_suite']);
export const EntityKindSchema = z.union([NormalizedSymbolKindSchema, FormatEntityKindSchema, EntityTestKindSchema]);
export const EntitySourceSpanV2Schema = z
    .object({
    version: z.literal(2),
    encoding: z.literal('utf8'),
    startLine: z.number().int().positive(),
    startColumn: z.number().int().positive(),
    endLine: z.number().int().positive(),
    endColumn: z.number().int().positive(),
    /** Zero-based inclusive UTF-8 byte offset. */
    startByte: z.number().int().nonnegative(),
    /** Zero-based exclusive UTF-8 byte offset. */
    endByte: z.number().int().nonnegative(),
    exact: z.literal(true),
})
    .superRefine((span, context) => {
    if (span.endByte < span.startByte) {
        context.addIssue({ code: 'custom', message: 'endByte must not precede startByte.' });
    }
    if (span.endLine < span.startLine) {
        context.addIssue({ code: 'custom', message: 'endLine must not precede startLine.' });
    }
});
function validatePinnedGrammarEvidence(evidence, context) {
    if (evidence.manifestDigest !== WHOLE_DIFF_GRAMMAR_MANIFEST_DIGEST) {
        context.addIssue({ code: 'custom', message: 'Evidence manifestDigest is not the checked-in manifest.' });
    }
    if (grammarManifestEntry(evidence.grammar)?.wasmSha256 !== evidence.grammarDigest) {
        context.addIssue({ code: 'custom', message: 'Evidence grammarDigest is not pinned for this grammar.' });
    }
}
export const EntitySyntaxEvidenceV2Schema = z
    .object({
    version: z.literal(2),
    proof: z.literal(ENTITY_CST_PROOF),
    grammar: z.string(),
    grammarDigest: z.string().regex(/^[0-9a-f]{64}$/),
    manifestDigest: z.string().regex(/^[0-9a-f]{64}$/),
    primaryHash: z.string().regex(/^[0-9a-f]{64}$/),
    commentsElidedHash: z.string().regex(/^[0-9a-f]{64}$/),
    complete: z.literal(true),
})
    .superRefine(validatePinnedGrammarEvidence);
export const EntitySignatureEvidenceV2Schema = z
    .object({
    version: z.literal(2),
    proof: z.literal(ENTITY_SIGNATURE_PROOF),
    grammar: z.string(),
    grammarDigest: z.string().regex(/^[0-9a-f]{64}$/),
    manifestDigest: z.string().regex(/^[0-9a-f]{64}$/),
    hash: z.string().regex(/^[0-9a-f]{64}$/),
    complete: z.literal(true),
})
    .superRefine(validatePinnedGrammarEvidence);
export const EntityRecordSchema = z.object({
    name: z.string(),
    /** `Container.name` when the entity has a container, else the bare name. */
    qualifiedName: z.string(),
    containerName: z.string().optional(),
    kind: EntityKindSchema,
    relativePath: z.string(),
    startLine: z.number().int().positive(),
    endLine: z.number().int().positive(),
    signature: z.string().optional(),
    isExported: z.boolean(),
    /**
     * Position among same-`(kind, qualifiedName)` entities in this file. What
     * keeps two overloads of one name distinguishable.
     */
    ordinal: z.number().int().nonnegative(),
    /** Position among all entities in the file, in declaration order. */
    declarationIndex: z.number().int().nonnegative(),
    /** False when the parser reported no real span (`endLine === line`). */
    spanExact: z.boolean(),
    /** Exact declaration bytes, when a manifest-pinned CST node was joined. */
    sourceSpan: EntitySourceSpanV2Schema.optional(),
    /** Fail-closed syntax evidence; absent means semantic classification is unknown. */
    syntaxEvidence: EntitySyntaxEvidenceV2Schema.optional(),
    /** Versioned declaration-without-body evidence used for signature comparisons. */
    signatureEvidence: EntitySignatureEvidenceV2Schema.optional(),
    hashes: z.object({
        hashVersion: z.number().int(),
        algo: z.enum(['lexer', 'declaration', 'none']),
        bodyHash: z.string(),
        exactBodyHash: z.string().optional(),
        normalizedHash: z.string().optional(),
        structuralHash: z.string().optional(),
        signatureHash: z.string().optional(),
        tokenCount: z.number().int().nonnegative().optional(),
    }),
});
/** Entities extracted from one blob, keyed in the cache by that blob's object id. */
export const BlobEntitiesSchema = z.object({
    blobSha: z.string(),
    relativePath: z.string(),
    language: z.string(),
    hashVersion: z.number().int(),
    entities: z.array(EntityRecordSchema),
    /** Set when a bound cut the extraction short; entities are then incomplete. */
    truncated: z.boolean(),
    skippedReason: z.string().optional(),
    /** Parser evidence cached with the immutable blob projection. */
    provenance: z
        .object({
        parserName: z.literal('occ.code.parsers'),
        parserVersion: z.number().int().positive(),
        parserTier: z.enum(['typescript-compiler', 'vue-sfc-typescript', 'tree-sitter', 'regex-fallback', 'format']),
        grammar: z.string().optional(),
        syntaxStatus: ParserSyntaxStatusSchema,
        syntaxErrorCount: z.number().int().nonnegative(),
        capabilities: ParserCapabilitiesSchema,
        diagnostics: z.array(ParserDiagnosticSchema).max(100),
    })
        .optional(),
});
/**
 * Why the matcher believes two entities are the same entity. Ordered strongest
 * to weakest; the ladder in `lineage.ts` tries them in exactly this order and
 * stops at the first mutually unique result.
 */
export const LineageMatchReasonSchema = z.enum([
    'exact-identity',
    'git-rename-identity',
    'content-hash',
    'normalized-hash',
    'structural-hash',
    'signature-name',
    'qualified-name',
    'similarity',
    'none',
]);
/** Which candidate pool produced a match. */
export const LineagePoolSchema = z.enum(['same-path', 'git-rename', 'cross-file', 'none']);
/**
 * Persisted and current entity change kinds. `modified_cosmetic` is retained
 * only to decode historical evidence; current producers fail uncertain drift
 * closed as `modified` and never newly emit that arm.
 */
export const EntityChangeTypeSchema = z.enum([
    'added',
    'modified',
    'modified_logic',
    'modified_cosmetic',
    'deleted',
    'moved',
    'renamed',
    'reordered',
    'orphan',
]);
/**
 * Secondary facts, preserved so a classification never discards what a
 * lower-precedence rule would have reported. A rename that also changed logic
 * is `renamed` with `logicChanged: true`, not one fact at the expense of the
 * other.
 */
export const EntityChangeFlagsSchema = z.object({
    pathChanged: z.boolean().optional(),
    nameChanged: z.boolean().optional(),
    containerChanged: z.boolean().optional(),
    kindChanged: z.boolean().optional(),
    bodyChanged: z.boolean().optional(),
    logicChanged: z.boolean().optional(),
    cosmeticOnly: z.boolean().optional(),
    orderChanged: z.boolean().optional(),
    signatureChanged: z.boolean().optional(),
    signatureEvidenceUnavailable: z.boolean().optional(),
    exportChanged: z.boolean().optional(),
    spanInexact: z.boolean().optional(),
    gitRenameApplied: z.boolean().optional(),
    /** Two candidates were too close to separate, so no match was made. */
    ambiguityRefused: z.boolean().optional(),
    /** Hashes were not comparable, so content evidence was unavailable. */
    evidenceUnavailable: z.boolean().optional(),
    /** A resource bound cut this entity's evidence; never report it as `added`. */
    boundHit: z.boolean().optional(),
    /** The entity was already alive at the oldest indexed commit, so its birth is provisional. */
    birthAtWindowBoundary: z.boolean().optional(),
    semanticBasis: z
        .enum([
        'nontrivia-syntax-change',
        'whitespace-significance-unverified',
        'comment-only-change',
        'syntax-evidence-unavailable',
        'grammar-verification-refused',
    ])
        .optional(),
});
export const LineageEvidenceSchema = z.object({
    pool: LineagePoolSchema,
    hashesComparable: z.boolean(),
    bodyHashEqual: z.boolean().optional(),
    exactBodyHashEqual: z.boolean().optional(),
    normalizedHashEqual: z.boolean().optional(),
    structuralHashEqual: z.boolean().optional(),
    signatureHashEqual: z.boolean().optional(),
    signatureEvidenceEqual: z.boolean().optional(),
    similarity: z.number().min(0).max(1).optional(),
    runnerUpSimilarity: z.number().min(0).max(1).optional(),
    reverseRunnerUpSimilarity: z.number().min(0).max(1).optional(),
    tiebreakSimilarity: z.number().min(0).max(1).optional(),
    /** How many candidates were in play. `> 1` with no match means a refusal. */
    candidateCount: z.number().int().nonnegative(),
    gitRenameSimilarity: z.number().int().optional(),
});
/** One step in an entity's life, recorded per commit. */
export const LineageEventSchema = z.object({
    lineageId: z.string(),
    commit: z.string(),
    commitOrder: z.number().int().nonnegative(),
    authoredAt: z.string(),
    committedAt: z.string(),
    authorName: z.string(),
    authorEmail: z.string(),
    subject: z.string(),
    changeType: EntityChangeTypeSchema,
    matchReason: LineageMatchReasonSchema,
    confidence: z.number().min(0).max(1),
    previousName: z.string().optional(),
    currentName: z.string().optional(),
    previousQualifiedName: z.string().optional(),
    currentQualifiedName: z.string().optional(),
    previousContainerName: z.string().optional(),
    currentContainerName: z.string().optional(),
    previousPath: z.string().optional(),
    currentPath: z.string().optional(),
    previousKind: EntityKindSchema.optional(),
    currentKind: EntityKindSchema.optional(),
    previousStartLine: z.number().int().positive().optional(),
    currentStartLine: z.number().int().positive().optional(),
    previousEndLine: z.number().int().positive().optional(),
    currentEndLine: z.number().int().positive().optional(),
    previousSignature: z.string().optional(),
    currentSignature: z.string().optional(),
    /**
     * The remaining two fields of the entity's state key, and its export surface.
     *
     * An event already describes both sides almost completely; without the ordinal
     * it stops just short of identifying which of two same-named overloads it was
     * about, and the event log cannot then be replayed back to the state at an
     * arbitrary commit. That replay is what answers a question asked `--ref`.
     */
    previousOrdinal: z.number().int().nonnegative().optional(),
    currentOrdinal: z.number().int().nonnegative().optional(),
    previousIsExported: z.boolean().optional(),
    currentIsExported: z.boolean().optional(),
    previousBodyHash: z.string().optional(),
    currentBodyHash: z.string().optional(),
    previousExactBodyHash: z.string().optional(),
    currentExactBodyHash: z.string().optional(),
    previousStructuralHash: z.string().optional(),
    currentStructuralHash: z.string().optional(),
    previousSourceSpan: EntitySourceSpanV2Schema.optional(),
    currentSourceSpan: EntitySourceSpanV2Schema.optional(),
    previousSyntaxEvidence: EntitySyntaxEvidenceV2Schema.optional(),
    currentSyntaxEvidence: EntitySyntaxEvidenceV2Schema.optional(),
    previousSignatureEvidence: EntitySignatureEvidenceV2Schema.optional(),
    currentSignatureEvidence: EntitySignatureEvidenceV2Schema.optional(),
    evidence: LineageEvidenceSchema,
    flags: EntityChangeFlagsSchema,
});
/** The live state of one lineage: where it is now and what it looks like. */
export const LineageHeadSchema = z.object({
    lineageId: z.string(),
    record: EntityRecordSchema,
    birthCommit: z.string(),
    birthPath: z.string(),
    birthQualifiedName: z.string(),
    lastCommit: z.string(),
    lastCommitOrder: z.number().int().nonnegative(),
    eventCount: z.number().int().nonnegative(),
    birthAtWindowBoundary: z.boolean(),
});
export const EntityDiagnosticCodeSchema = z.enum([
    'commit-window-truncated',
    'commit-files-truncated',
    'blob-missing',
    'blob-too-large',
    'blob-too-many-lines',
    'blob-binary',
    'blob-unsupported',
    'parse-failed',
    'blob-budget-exhausted',
    'entities-truncated',
    'entity-budget-exhausted',
    'event-budget-exhausted',
    'cross-file-skipped',
    'similarity-skipped',
    'ambiguity-refused',
    'lineage-accept-declined',
    'evidence-unavailable',
    'inline-context-stale',
    'grammar-verification-refused',
    'shallow-history',
    'unborn-repo',
    'merge-side-changes-skipped',
    'index-unmerged',
    'index-visibility-hidden',
    'cache-rebuilt',
    'cache-rewound',
    'history-rewritten',
]);
export const EntityDiagnosticSchema = z.object({
    code: EntityDiagnosticCodeSchema,
    message: z.string(),
    commit: z.string().optional(),
    relativePath: z.string().optional(),
    blobSha: z.string().optional(),
    grammar: z.string().optional(),
    /** Evidence-ladder rung that deliberately rejected an otherwise unique pair. */
    rung: LineageMatchReasonSchema.optional(),
    kept: z.number().int().nonnegative().optional(),
    total: z.number().int().nonnegative().optional(),
    limit: z.number().int().nonnegative().optional(),
});
/**
 * Whether a change altered structure, or `null` when that is not knowable.
 *
 * `null` is a real answer rather than a missing one: without complete,
 * compatible CST evidence OCC cannot distinguish unsupported drift from a
 * proved structural change, and reporting `false` would assert a semantic
 * no-op it has no evidence for.
 */
export function structuralChangeFrom(flags) {
    if (flags.evidenceUnavailable)
        return null;
    if (flags.logicChanged)
        return true;
    if (flags.cosmeticOnly)
        return false;
    return null;
}
/**
 * Change kinds relevant to conservative attribution. An unproved `modified`
 * event is retained; only explicit historical cosmetic evidence and pure
 * reorderings are skipped.
 */
export const SEMANTIC_CHANGE_TYPES = new Set([
    'added',
    'modified',
    'modified_logic',
    'deleted',
    'moved',
    'renamed',
    'orphan',
]);
export const DEFAULT_ENTITY_BOUNDS = {
    maxCommits: 1_000,
    maxFilesPerCommit: 500,
    maxBlobBytes: 2_097_152,
    maxBlobLines: 25_000,
    maxTotalBlobBytes: 268_435_456,
    maxBatchBytes: 33_554_432,
    maxEntitiesPerFile: 2_000,
    maxEntitiesTotal: 200_000,
    maxLiveEntities: 200_000,
    maxLineageEvents: 500_000,
    maxCrossFileCandidates: 500,
    maxSimilarityCandidates: 200,
};
export const DEFAULT_LINEAGE_THRESHOLDS = {
    similarityMin: 0.75,
    similarityMargin: 0.1,
    similarityMinTokens: 12,
    similarityLengthRatioMin: 0.5,
    shingleWidth: 5,
};
/** Confidence attached to a match, by the rung that produced it. */
export const MATCH_CONFIDENCE = {
    'exact-identity': 1,
    'git-rename-identity': 0.99,
    'content-hash': 0.98,
    'normalized-hash': 0.95,
    'structural-hash': 0.9,
    'signature-name': 0.85,
    'qualified-name': 0.8,
    similarity: 0.75,
    none: 0,
};
export function createEntityCounters() {
    return {
        gitInvocations: 0,
        gitBytesRead: 0,
        blobsRequested: 0,
        blobsFromCache: 0,
        blobsRead: 0,
        blobsParsed: 0,
        blobsSkipped: 0,
        entitiesExtracted: 0,
        commitsWalked: 0,
        cacheHits: 0,
        cacheMisses: 0,
        cacheWrites: 0,
        cacheBytesRead: 0,
        lineageComparisons: 0,
        lineageMatches: 0,
        lineageAcceptDeclines: 0,
        similarityComparisons: 0,
        ambiguitiesRefused: 0,
    };
}
export function bumpCounter(counters, key, by = 1) {
    if (counters)
        counters[key] += by;
}
/** One commit in the indexed window. */
export const EntityCommitRecordSchema = z.object({
    sha: z.string(),
    parents: z.array(z.string()),
    order: z.number().int().nonnegative(),
    authorName: z.string(),
    authorEmail: z.string(),
    authoredAt: z.string(),
    committedAt: z.string(),
    subject: z.string(),
    isMerge: z.boolean(),
});
/**
 * Repository identity, used to bind a cache to the repo it was built from.
 *
 * The first-parent root commit is stable across renames, relocation, and
 * remote changes, and differs between two unrelated repos that happen to share
 * a path. `--first-parent` keeps the walk linear so exactly one root is
 * reported, and avoids traversing the whole DAG.
 */
export const EntityRepoIdentitySchema = z.object({
    rootCommit: z.string().optional(),
    shallow: z.boolean(),
    /** Fallback identity for an unborn repository, which has no commits at all. */
    fallback: z.string().optional(),
});
export const ENTITY_TEST_REASON_SCHEMA = OccTestReasonSchema;
/** How a cache open resolved against the current head. */
export const EntityIndexModeSchema = z.enum(['fresh', 'unchanged', 'extended', 'rewound', 'rebuilt']);
export const EntityIndexStatusSchema = z.object({
    mode: EntityIndexModeSchema,
    head: z.string().optional(),
    commitsIndexed: z.number().int().nonnegative(),
    eventsRecorded: z.number().int().nonnegative(),
    lineageCount: z.number().int().nonnegative(),
    truncated: z.boolean(),
    diagnostics: z.array(EntityDiagnosticSchema),
});
//# sourceMappingURL=types.js.map