/**
 * Sampling Strategies
 * Control which spans are exported for production optimization
 */
import { SpanStatus } from "../types/spanTypes.js";
/**
 * Always sample all spans
 */
export class AlwaysSampler {
    name = "always";
    shouldSample(_span) {
        return true;
    }
    getDescription() {
        return "Samples 100% of spans";
    }
}
/**
 * Never sample any spans
 */
export class NeverSampler {
    name = "never";
    shouldSample(_span) {
        return false;
    }
    getDescription() {
        return "Samples 0% of spans";
    }
}
/**
 * Sample spans based on a probability ratio
 */
export class RatioSampler {
    name = "ratio";
    ratio;
    constructor(ratio) {
        if (ratio < 0 || ratio > 1) {
            throw new Error("Ratio must be between 0 and 1");
        }
        this.ratio = ratio;
    }
    shouldSample(_span) {
        return Math.random() < this.ratio;
    }
    getDescription() {
        return `Samples ${this.ratio * 100}% of spans`;
    }
}
/**
 * Sample based on trace ID for consistent sampling across a trace
 */
export class TraceIdRatioSampler {
    name = "trace-id-ratio";
    ratio;
    upperBound;
    constructor(ratio) {
        if (ratio < 0 || ratio > 1) {
            throw new Error("Ratio must be between 0 and 1");
        }
        this.ratio = ratio;
        this.upperBound = Math.floor(ratio * 0xffffffff);
    }
    shouldSample(span) {
        // Use first 8 chars of trace ID as hash
        const hash = parseInt(span.traceId.substring(0, 8), 16);
        return !isNaN(hash) && hash < this.upperBound;
    }
    getDescription() {
        return `Samples ${this.ratio * 100}% of traces (consistent per trace)`;
    }
}
/**
 * Sample based on span attributes (e.g., errors, specific providers)
 */
export class AttributeBasedSampler {
    name = "attribute-based";
    rules;
    defaultSampler;
    constructor(rules, defaultSampler = new RatioSampler(0.1)) {
        // Sort rules by priority (higher first)
        this.rules = [...rules].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
        this.defaultSampler = defaultSampler;
    }
    shouldSample(span) {
        for (const rule of this.rules) {
            if (this.matchesRule(span, rule)) {
                return rule.sample;
            }
        }
        return this.defaultSampler.shouldSample(span);
    }
    matchesRule(span, rule) {
        for (const [key, value] of Object.entries(rule.conditions)) {
            const spanValue = span.attributes[key];
            // Support wildcard matching
            if (value === "*" && spanValue !== undefined) {
                continue;
            }
            if (spanValue !== value) {
                return false;
            }
        }
        return true;
    }
    getDescription() {
        return `Attribute-based sampling with ${this.rules.length} rules`;
    }
}
/**
 * Priority-based sampler - always sample high-priority spans
 */
export class PrioritySampler {
    name = "priority";
    highPriorityTypes;
    fallbackSampler;
    constructor(highPriorityTypes = ["model.generation", "tool.call"], fallbackSampler = new RatioSampler(0.1)) {
        this.highPriorityTypes = highPriorityTypes;
        this.fallbackSampler = fallbackSampler;
    }
    shouldSample(span) {
        // Always sample errors
        if (span.status === SpanStatus.ERROR) {
            return true;
        }
        // Always sample high-priority span types
        if (this.highPriorityTypes.includes(span.type)) {
            return true;
        }
        return this.fallbackSampler.shouldSample(span);
    }
    getDescription() {
        return `Priority sampling for ${this.highPriorityTypes.join(", ")} spans`;
    }
}
/**
 * Error-only sampler - only sample error spans
 */
export class ErrorOnlySampler {
    name = "error-only";
    shouldSample(span) {
        return span.status === SpanStatus.ERROR;
    }
    getDescription() {
        return "Samples only error spans";
    }
}
/**
 * Composite sampler that combines multiple samplers
 */
export class CompositeSampler {
    name = "composite";
    samplers;
    totalWeight;
    constructor(samplers) {
        this.samplers = samplers;
        this.totalWeight = samplers.reduce((sum, s) => sum + s.weight, 0);
    }
    shouldSample(span) {
        let random = Math.random() * this.totalWeight;
        for (const { sampler, weight } of this.samplers) {
            random -= weight;
            if (random <= 0) {
                return sampler.shouldSample(span);
            }
        }
        return this.samplers[this.samplers.length - 1].sampler.shouldSample(span);
    }
    getDescription() {
        return `Composite of ${this.samplers.length} samplers`;
    }
}
/**
 * Custom sampler that uses a user-provided function
 */
export class CustomSampler {
    name = "custom";
    sampleFn;
    description;
    constructor(sampleFn, description = "Custom sampling function") {
        this.sampleFn = sampleFn;
        this.description = description;
    }
    shouldSample(span) {
        return this.sampleFn(span);
    }
    getDescription() {
        return this.description;
    }
}
/**
 * Factory for creating samplers from configuration
 */
export class SamplerFactory {
    static create(config) {
        switch (config.type) {
            case "always":
                return new AlwaysSampler();
            case "never":
                return new NeverSampler();
            case "ratio":
                return new RatioSampler(config.ratio ?? 0.1);
            case "trace-id-ratio":
                return new TraceIdRatioSampler(config.ratio ?? 0.1);
            case "attribute-based":
                return new AttributeBasedSampler(config.rules ?? [], config.defaultRatio !== undefined
                    ? new RatioSampler(config.defaultRatio)
                    : undefined);
            case "priority":
                return new PrioritySampler();
            case "error-only":
                return new ErrorOnlySampler();
            default:
                return new RatioSampler(0.1);
        }
    }
}
