/**
 * Pipeline executor.
 * Runs content through a sequence of stages defined by a ProxyModel.
 * Each stage receives the output of the previous stage as input.
 */
import type { StageContext, StageResult, StageLogger, Section, ContentType, LLMProvider, CacheProvider, SystemPromptFetcher } from './types.js';
import type { ProxyModelDefinition } from './schema.js';
import { getStage } from './stage-registry.js';
import type { AuditCollector } from '../audit/collector.js';

export interface ExecuteOptions {
  /** The raw content to process. */
  content: string;
  /** What kind of content this is. */
  contentType: ContentType;
  /** Source identifier (e.g. "server/tool"). */
  sourceName: string;
  /** Project this content belongs to. */
  projectName: string;
  /** Session identifier for cache scoping. */
  sessionId: string;
  /** The proxymodel definition controlling the pipeline. */
  proxyModel: ProxyModelDefinition;
  /** LLM provider for stages that need AI. */
  llm: LLMProvider;
  /** Cache provider for stages that cache results. */
  cache: CacheProvider;
  /** Optional logger override (defaults to console). */
  log?: StageLogger;
  /** Optional system prompt fetcher for stages that use editable prompts. */
  getSystemPrompt?: SystemPromptFetcher;
  /** Optional audit collector for pipeline/stage event emission. */
  auditCollector?: AuditCollector;
  /** Server name for per-server audit tagging. */
  serverName?: string;
  /** Correlation ID linking to request-level tracing. */
  correlationId?: string;
}

/**
 * Execute the pipeline defined by a ProxyModel.
 *
 * Stages run in order. Each stage receives the previous stage's content
 * as input. If a stage fails, the pipeline continues with the previous
 * content. Sections and metadata accumulate across stages.
 */
export async function executePipeline(opts: ExecuteOptions): Promise<StageResult> {
  const { content, proxyModel, llm, cache } = opts;
  const log = opts.log ?? consoleLogger('pipeline');

  // Check appliesTo filter
  if (!proxyModel.spec.appliesTo.includes(opts.contentType)) {
    return { content };
  }

  let currentContent = content;
  let sections: Section[] | undefined;
  let metadata: Record<string, unknown> = {};
  const pipelineStart = performance.now();

  // Base fields for audit events (avoids exactOptionalPropertyTypes issues)
  const auditBase = {
    sessionId: opts.sessionId,
    source: 'mcplocal' as const,
    verified: true,
    ...(opts.serverName !== undefined ? { serverName: opts.serverName } : {}),
    ...(opts.correlationId !== undefined ? { correlationId: opts.correlationId } : {}),
  };

  for (const stageSpec of proxyModel.spec.stages) {
    const handler = getStage(stageSpec.type);
    if (!handler) {
      log.warn(`Stage '${stageSpec.type}' not found, skipping`);
      continue;
    }

    const defaultFetcher: SystemPromptFetcher = async (_name, fallback) => fallback;

    const ctx: StageContext = {
      contentType: opts.contentType,
      sourceName: opts.sourceName,
      projectName: opts.projectName,
      sessionId: opts.sessionId,
      originalContent: content,
      llm,
      cache,
      log: consoleLogger(stageSpec.type),
      getSystemPrompt: opts.getSystemPrompt ?? defaultFetcher,
      config: stageSpec.config ?? {},
    };

    try {
      const inputSize = currentContent.length;
      const stageStart = performance.now();
      const result = await handler(currentContent, ctx);
      const durationMs = Math.round(performance.now() - stageStart);
      currentContent = result.content;
      if (result.sections) sections = result.sections;
      if (result.metadata) metadata = { ...metadata, ...result.metadata };

      opts.auditCollector?.emit({
        ...auditBase,
        timestamp: new Date().toISOString(),
        eventKind: 'stage_execution',
        payload: {
          stage: stageSpec.type,
          durationMs,
          inputSize,
          outputSize: result.content.length,
          sectionCount: result.sections?.length ?? 0,
          error: null,
        },
      });
    } catch (err) {
      log.error(`Stage '${stageSpec.type}' failed: ${(err as Error).message}`);

      opts.auditCollector?.emit({
        ...auditBase,
        timestamp: new Date().toISOString(),
        eventKind: 'stage_execution',
        payload: {
          stage: stageSpec.type,
          durationMs: 0,
          inputSize: currentContent.length,
          outputSize: currentContent.length,
          sectionCount: 0,
          error: (err as Error).message,
        },
      });
      // Continue with previous content on error
    }
  }

  const totalDurationMs = Math.round(performance.now() - pipelineStart);

  opts.auditCollector?.emit({
    ...auditBase,
    timestamp: new Date().toISOString(),
    eventKind: 'pipeline_execution',
    payload: {
      totalDurationMs,
      stageCount: proxyModel.spec.stages.length,
      inputSize: content.length,
      outputSize: currentContent.length,
    },
  });

  const result: StageResult = { content: currentContent };
  if (sections) result.sections = sections;
  if (Object.keys(metadata).length > 0) result.metadata = metadata;
  return result;
}

function consoleLogger(prefix: string): StageLogger {
  return {
    debug: (msg: string) => console.debug(`[${prefix}] ${msg}`),
    info: (msg: string) => console.info(`[${prefix}] ${msg}`),
    warn: (msg: string) => console.warn(`[${prefix}] ${msg}`),
    error: (msg: string) => console.error(`[${prefix}] ${msg}`),
  };
}
