import { context, metrics, SpanStatusCode, trace } from '@opentelemetry/api';
import { getErrorMessage } from '../../../shared/utils/index.js';
import { getWorkflowStepKind } from '../step-kind.js';
const tracer = trace.getTracer('takt.workflow');
const WORKFLOW_RUN_COUNTER_OPTIONS = {
    description: 'Workflow executions by status',
};
const WORKFLOW_DURATION_HISTOGRAM_OPTIONS = {
    description: 'Workflow execution duration',
    unit: 'ms',
};
const STEP_RUN_COUNTER_OPTIONS = {
    description: 'Workflow step executions by status',
};
const STEP_DURATION_HISTOGRAM_OPTIONS = {
    description: 'Workflow step execution duration',
    unit: 'ms',
};
const PHASE_RUN_COUNTER_OPTIONS = {
    description: 'Workflow phase executions by status',
};
const PHASE_DURATION_HISTOGRAM_OPTIONS = {
    description: 'Workflow phase execution duration',
    unit: 'ms',
};
const JUDGE_STAGE_COUNTER_OPTIONS = {
    description: 'Workflow judge stage executions by status',
};
export async function runWithWorkflowSpan(params, execute, getOutcome) {
    if (!params.enabled) {
        return execute();
    }
    const startedAt = Date.now();
    return runInSpan(buildSpanName('workflow', params.workflowName), buildWorkflowAttributes(params), async (span) => {
        try {
            const result = await execute();
            const outcome = getOutcome(result);
            recordWorkflowOutcome(span, params, outcome);
            recordWorkflowMetrics(params, outcome, Date.now() - startedAt);
            return result;
        }
        catch (error) {
            recordWorkflowMetrics(params, {
                status: 'error',
                abortReason: getErrorMessage(error),
            }, Date.now() - startedAt);
            throw error;
        }
    });
}
export async function runWithStepSpan(params, execute) {
    if (!params.enabled) {
        return execute();
    }
    const startedAt = Date.now();
    return runInSpan(buildSpanName('step', params.step.name), buildStepAttributes(params), async (span) => {
        try {
            const result = await execute();
            recordStepResult(span, params, result);
            recordStepMetrics(params, result, Date.now() - startedAt);
            return result;
        }
        catch (error) {
            recordStepMetrics(params, undefined, Date.now() - startedAt, getErrorMessage(error));
            throw error;
        }
    });
}
export async function runWithPhaseSpan(params, execute, getOutcome) {
    if (!params.enabled) {
        return execute();
    }
    const startedAt = Date.now();
    return runInSpan(buildPhaseSpanName(params), buildPhaseAttributes(params), async (span) => {
        try {
            const result = await execute();
            const outcome = getOutcome(result);
            recordPhaseOutcome(span, params, outcome);
            recordPhaseMetrics(params, outcome, Date.now() - startedAt);
            return result;
        }
        catch (error) {
            const outcome = { status: 'error', error: getErrorMessage(error) };
            recordPhaseOutcome(span, params, outcome);
            recordPhaseMetrics(params, outcome, Date.now() - startedAt);
            throw error;
        }
    });
}
export function recordJudgeStageSpan(params) {
    if (!params.enabled) {
        return;
    }
    const span = tracer.startSpan(buildJudgeStageSpanName(params), {
        attributes: buildJudgeStageAttributes(params),
    });
    try {
        recordJudgeStageMetrics(params);
        if (params.entry.status === 'error') {
            span.setStatus({ code: SpanStatusCode.ERROR, message: `judge stage ${params.entry.status}` });
        }
    }
    finally {
        span.end();
    }
}
function buildWorkflowAttributes(params) {
    return compactAttributes({
        'takt.run.id': params.runId,
        'takt.workflow.name': params.workflowName,
        'takt.workflow.initial_step': params.initialStep,
        'takt.workflow.step_count': params.stepCount,
        'takt.workflow.max_steps': params.maxSteps,
        'takt.workflow.run_mode': params.runMode,
        'takt.workflow.resume_depth': params.resumeDepth,
    });
}
function buildStepAttributes(params) {
    return compactAttributes({
        'takt.run.id': params.runId,
        'takt.workflow.name': params.workflowName,
        ...workflowStackAttributes(params.workflowStack),
        'takt.step.name': params.step.name,
        'takt.step.persona': params.step.personaDisplayName,
        'takt.step.type': getWorkflowStepKind(params.step),
        'takt.step.iteration': params.iteration,
        'takt.step.local_iteration': params.stepIteration,
        'takt.step.instruction': sanitizeSpanText(params.sanitizeText, params.instruction),
        ...providerAttributes(params.providerInfo),
        ...providerOptionsAttributes(params.providerInfo),
    });
}
function buildPhaseAttributes(params) {
    return compactAttributes({
        'takt.run.id': params.runId,
        'takt.workflow.name': params.workflowName,
        ...workflowStackAttributes(params.workflowStack),
        'takt.step.name': params.step.name,
        'takt.step.persona': params.step.personaDisplayName,
        'takt.step.type': getWorkflowStepKind(params.step),
        'takt.step.iteration': params.iteration,
        'takt.phase.number': params.phase,
        'takt.phase.name': params.phaseName,
        'takt.phase.execution_id': params.phaseExecutionId,
        'takt.phase.instruction': sanitizeSpanText(params.sanitizeText, params.instruction),
        ...providerAttributes(params.providerInfo),
    });
}
function buildJudgeStageAttributes(params) {
    return compactAttributes({
        'takt.run.id': params.runId,
        'takt.workflow.name': params.workflowName,
        ...workflowStackAttributes(params.workflowStack),
        'takt.step.name': params.step.name,
        'takt.step.persona': params.step.personaDisplayName,
        'takt.step.type': getWorkflowStepKind(params.step),
        'takt.step.iteration': params.iteration,
        'takt.phase.number': 3,
        'takt.phase.name': 'judge',
        'takt.phase.execution_id': params.phaseExecutionId,
        'takt.judge.stage': params.entry.stage,
        'takt.judge.method': params.entry.method,
        'takt.judge.status': params.entry.status,
        'takt.judge.instruction': sanitizeSpanText(params.sanitizeText, params.entry.instruction),
        'takt.judge.response': sanitizeSpanText(params.sanitizeText, params.entry.response),
    });
}
async function runInSpan(name, attributes, execute) {
    const span = tracer.startSpan(name, { attributes });
    return context.with(trace.setSpan(context.active(), span), async () => {
        try {
            return await execute(span);
        }
        catch (error) {
            recordSpanError(span, error);
            throw error;
        }
        finally {
            span.end();
        }
    });
}
function recordWorkflowOutcome(span, params, outcome) {
    const attributes = compactAttributes({
        'takt.workflow.status': outcome.status,
        'takt.workflow.abort.kind': outcome.abortKind,
        'takt.workflow.abort.reason': sanitizeSpanText(params.sanitizeText, outcome.abortReason),
        'takt.workflow.next_step': outcome.nextStep,
        'takt.workflow.iterations': outcome.iterations,
    });
    span.setAttributes(attributes);
    if (outcome.status && outcome.status !== 'completed' && outcome.status !== 'running') {
        span.setStatus({ code: SpanStatusCode.ERROR, message: `workflow ${outcome.status}` });
    }
}
function recordWorkflowMetrics(params, outcome, durationMs) {
    const attributes = compactAttributes({
        'takt.run.id': params.runId,
        'takt.workflow.name': params.workflowName,
        'takt.workflow.status': outcome.status ?? 'unknown',
        'takt.workflow.abort.kind': outcome.abortKind,
        'takt.workflow.run_mode': params.runMode,
    });
    const meter = metrics.getMeter('takt.workflow');
    meter.createCounter('takt.workflow.runs', WORKFLOW_RUN_COUNTER_OPTIONS).add(1, attributes);
    meter.createHistogram('takt.workflow.duration', WORKFLOW_DURATION_HISTOGRAM_OPTIONS).record(durationMs, attributes);
}
function recordStepResult(span, params, result) {
    const finalStepIteration = params.getFinalStepIteration?.();
    span.setAttributes(compactAttributes({
        'takt.step.local_iteration': finalStepIteration ?? params.stepIteration,
        'takt.step.status': result.response.status,
        'takt.step.result.persona': result.response.persona,
        'takt.step.result.content': sanitizeSpanText(params.sanitizeText, result.response.content),
        'takt.step.result.error': sanitizeSpanText(params.sanitizeText, result.response.error),
        'takt.step.result.failure_category': result.response.failureCategory,
        'takt.step.result.matched_rule_index': result.response.matchedRuleIndex,
        'takt.step.result.matched_rule_method': result.response.matchedRuleMethod,
        'takt.step.result.match_method': toJudgmentMatchMethod(result.response.matchedRuleMethod),
        'takt.step.result.timestamp': result.response.timestamp.toISOString(),
        ...providerAttributes(result.providerInfo ?? params.providerInfo),
    }));
    if (result.response.status === 'error' || result.response.status === 'rate_limited') {
        span.setStatus({ code: SpanStatusCode.ERROR, message: `step ${result.response.status}` });
    }
}
function recordStepMetrics(params, result, durationMs, errorMessage) {
    const providerInfo = result?.providerInfo ?? params.providerInfo;
    const attributes = compactAttributes({
        'takt.run.id': params.runId,
        'takt.workflow.name': params.workflowName,
        'takt.step.name': params.step.name,
        'takt.step.type': getWorkflowStepKind(params.step),
        'takt.step.status': result?.response.status ?? (errorMessage ? 'error' : 'unknown'),
        'takt.step.result.failure_category': result?.response.failureCategory,
        ...providerAttributes(providerInfo),
    });
    const meter = metrics.getMeter('takt.workflow');
    meter.createCounter('takt.workflow.step.runs', STEP_RUN_COUNTER_OPTIONS).add(1, attributes);
    meter.createHistogram('takt.workflow.step.duration', STEP_DURATION_HISTOGRAM_OPTIONS).record(durationMs, attributes);
}
function recordPhaseOutcome(span, params, outcome) {
    const promptParts = params.getPromptParts?.();
    span.setAttributes(compactAttributes({
        'takt.phase.status': outcome.status,
        'takt.phase.system_prompt': sanitizeSpanText(params.sanitizeText, promptParts?.systemPrompt),
        'takt.phase.user_instruction': sanitizeSpanText(params.sanitizeText, promptParts?.userInstruction),
        'takt.phase.result.content': sanitizeSpanText(params.sanitizeText, outcome.content),
        'takt.phase.result.error': sanitizeSpanText(params.sanitizeText, outcome.error),
        'takt.phase.result.matched_rule_index': outcome.matchedRuleIndex,
        'takt.phase.result.matched_rule_method': outcome.matchedRuleMethod,
    }));
    if (outcome.status === 'error' || outcome.status === 'rate_limited') {
        span.setStatus({ code: SpanStatusCode.ERROR, message: `phase ${outcome.status}` });
    }
}
function recordPhaseMetrics(params, outcome, durationMs) {
    const attributes = compactAttributes({
        'takt.run.id': params.runId,
        'takt.workflow.name': params.workflowName,
        'takt.step.name': params.step.name,
        'takt.step.type': getWorkflowStepKind(params.step),
        'takt.phase.number': params.phase,
        'takt.phase.name': params.phaseName,
        'takt.phase.status': outcome.status ?? 'unknown',
        ...providerAttributes(params.providerInfo),
    });
    const meter = metrics.getMeter('takt.workflow');
    meter.createCounter('takt.workflow.phase.runs', PHASE_RUN_COUNTER_OPTIONS).add(1, attributes);
    meter.createHistogram('takt.workflow.phase.duration', PHASE_DURATION_HISTOGRAM_OPTIONS).record(durationMs, attributes);
}
function recordJudgeStageMetrics(params) {
    metrics.getMeter('takt.workflow').createCounter('takt.workflow.judge_stage.runs', JUDGE_STAGE_COUNTER_OPTIONS).add(1, compactAttributes({
        'takt.run.id': params.runId,
        'takt.workflow.name': params.workflowName,
        'takt.step.name': params.step.name,
        'takt.step.type': getWorkflowStepKind(params.step),
        'takt.phase.number': 3,
        'takt.phase.name': 'judge',
        'takt.judge.stage': params.entry.stage,
        'takt.judge.method': params.entry.method,
        'takt.judge.status': params.entry.status,
    }));
}
const REDACTED_PLACEHOLDER = '[redacted]';
function sanitizeSpanText(sanitizeText, text) {
    if (text === undefined) {
        return undefined;
    }
    // Fail closed: observability span attributes must never carry raw text when
    // no sanitizer was threaded to the call site. Returning the raw text here was
    // a silent leak if any future call site forgot to pass sanitizeText.
    return sanitizeText ? sanitizeText(text) : REDACTED_PLACEHOLDER;
}
function workflowStackAttributes(stack) {
    if (!stack || stack.length === 0) {
        return {};
    }
    return {
        'takt.workflow.current_name': stack[stack.length - 1]?.workflow,
        'takt.workflow.stack': JSON.stringify(stack.map((entry) => ({
            workflow: entry.workflow,
            ...(entry.workflow_ref ? { workflow_ref: entry.workflow_ref } : {}),
            step: entry.step,
            kind: entry.kind,
        }))),
    };
}
function providerAttributes(providerInfo) {
    return {
        'takt.provider.name': providerInfo?.provider,
        'takt.provider.source': providerInfo?.providerSource,
        'takt.model.name': providerInfo?.model,
        'takt.model.source': providerInfo?.modelSource,
    };
}
// Step-span only: the canonical step_start record carries providerOptions /
// providerOptionsSources. Span attributes cannot hold objects, so serialize
// them as JSON and parse back in the mapper. Kept out of providerAttributes()
// so metric attributes (which feed cardinality) never get a JSON blob.
function providerOptionsAttributes(providerInfo) {
    return {
        'takt.provider.options': providerInfo?.providerOptions !== undefined
            ? JSON.stringify(providerInfo.providerOptions)
            : undefined,
        'takt.provider.options_sources': providerInfo?.providerOptionsSources !== undefined
            ? JSON.stringify(providerInfo.providerOptionsSources)
            : undefined,
    };
}
function toJudgmentMatchMethod(matchedRuleMethod) {
    if (!matchedRuleMethod) {
        return undefined;
    }
    if (matchedRuleMethod === 'structured_output') {
        return 'structured_output';
    }
    if (matchedRuleMethod === 'ai_judge' || matchedRuleMethod === 'ai_judge_fallback') {
        return 'ai_judge';
    }
    if (matchedRuleMethod === 'phase3_tag' || matchedRuleMethod === 'phase1_tag') {
        return 'tag_fallback';
    }
    return undefined;
}
function recordSpanError(span, error) {
    const message = getErrorMessage(error);
    span.recordException(error instanceof Error ? error : message);
    span.setStatus({ code: SpanStatusCode.ERROR, message });
}
function buildSpanName(prefix, name) {
    return `${prefix}.${name || 'unknown'}`;
}
function buildPhaseSpanName(params) {
    return `phase.${params.step.name || 'unknown'}.${params.phaseName}`;
}
function buildJudgeStageSpanName(params) {
    return `judge_stage.${params.step.name || 'unknown'}.${params.entry.stage}.${params.entry.method}`;
}
function compactAttributes(attributes) {
    const compacted = {};
    for (const [key, value] of Object.entries(attributes)) {
        if (value !== undefined) {
            compacted[key] = value;
        }
    }
    return compacted;
}
//# sourceMappingURL=workflowSpans.js.map