import {
  chunkContent
} from "./chunk-4WMCPJWX.js";

// src/semantic-chunking.ts
var DEFAULT_SEMANTIC_CHUNKING_CONFIG = {
  targetTokens: 200,
  minTokens: 100,
  maxTokens: 400,
  smoothingWindowSize: 3,
  boundaryThresholdStdDevs: 1,
  embeddingBatchSize: 32,
  fallbackToRecursive: true
};
function cosineSimilarity(a, b) {
  if (a.length !== b.length) {
    throw new Error(
      `cosineSimilarity: vector length mismatch (${a.length} vs ${b.length})`
    );
  }
  if (a.length === 0) return 0;
  let dot = 0;
  let magA = 0;
  let magB = 0;
  for (let i = 0; i < a.length; i++) {
    dot += a[i] * b[i];
    magA += a[i] * a[i];
    magB += b[i] * b[i];
  }
  const denom = Math.sqrt(magA) * Math.sqrt(magB);
  if (denom === 0) return 0;
  return dot / denom;
}
function mean(series) {
  if (series.length === 0) return 0;
  let sum = 0;
  for (const v of series) sum += v;
  return sum / series.length;
}
function stddev(series) {
  if (series.length === 0) return 0;
  const m = mean(series);
  let sumSq = 0;
  for (const v of series) {
    const d = v - m;
    sumSq += d * d;
  }
  return Math.sqrt(sumSq / series.length);
}
function movingAverage(series, windowSize) {
  if (series.length === 0) return [];
  if (windowSize < 1) windowSize = 1;
  if (windowSize % 2 === 0) windowSize = windowSize + 1;
  const halfW = Math.floor(windowSize / 2);
  const result = new Array(series.length);
  for (let i = 0; i < series.length; i++) {
    const lo = Math.max(0, i - halfW);
    const hi = Math.min(series.length - 1, i + halfW);
    let sum = 0;
    for (let j = lo; j <= hi; j++) sum += series[j];
    result[i] = sum / (hi - lo + 1);
  }
  return result;
}
function findLocalMinima(series, threshold) {
  if (series.length <= 2) return [];
  const minima = [];
  for (let i = 1; i < series.length - 1; i++) {
    if (series[i] < series[i - 1] && series[i] < series[i + 1] && series[i] < threshold) {
      minima.push(i);
    }
  }
  return minima;
}
function splitSentences(text) {
  const sentences = [];
  const sentenceRegex = /[^.!?]*[.!?]+(?:\s+|$)/g;
  let match;
  let lastIndex = 0;
  while ((match = sentenceRegex.exec(text)) !== null) {
    sentences.push(match[0].trim());
    lastIndex = sentenceRegex.lastIndex;
  }
  if (lastIndex < text.length) {
    const remaining = text.slice(lastIndex).trim();
    if (remaining) {
      sentences.push(remaining);
    }
  }
  return sentences.filter((s) => s.length > 0);
}
function estimateTokens(text) {
  return Math.ceil(text.length / 4);
}
async function batchEmbed(sentences, embedFn, batchSize) {
  const allEmbeddings = [];
  for (let i = 0; i < sentences.length; i += batchSize) {
    const batch = sentences.slice(i, i + batchSize);
    const batchResult = await embedFn(batch);
    for (const vec of batchResult) {
      allEmbeddings.push(vec);
    }
  }
  return allEmbeddings;
}
function buildSegments(sentences, boundaries) {
  const sorted = [...boundaries].sort((a, b) => a - b);
  const segments = [];
  let start = 0;
  for (const b of sorted) {
    const splitPoint = b + 1;
    if (splitPoint > start && splitPoint <= sentences.length) {
      segments.push(sentences.slice(start, splitPoint));
      start = splitPoint;
    }
  }
  if (start < sentences.length) {
    segments.push(sentences.slice(start));
  }
  return segments;
}
function mergeShortSegments(segments, minTokens) {
  if (segments.length <= 1) return segments;
  const merged = [];
  let buffer = [];
  for (let i = 0; i < segments.length; i++) {
    buffer = [...buffer, ...segments[i]];
    const tokenCount = estimateTokens(buffer.join(" "));
    if (tokenCount >= minTokens || i === segments.length - 1) {
      merged.push(buffer);
      buffer = [];
    }
  }
  if (buffer.length > 0) {
    if (merged.length > 0) {
      merged[merged.length - 1] = [...merged[merged.length - 1], ...buffer];
    } else {
      merged.push(buffer);
    }
  }
  return merged;
}
function splitLongSegment(segment, maxTokens, targetTokens) {
  const text = segment.join(" ");
  const cappedTarget = Math.min(targetTokens, maxTokens);
  const result = chunkContent(text, {
    targetTokens: cappedTarget,
    minTokens: Math.min(cappedTarget, maxTokens),
    overlapSentences: 0
  });
  return result.chunks.map((c) => ({
    content: c.content,
    index: c.index,
    tokenCount: c.tokenCount,
    boundaryScore: 0
  }));
}
async function semanticChunkContent(content, embedFn, config) {
  const cfg = {
    ...DEFAULT_SEMANTIC_CHUNKING_CONFIG,
    ...config
  };
  const batchSize = Math.max(1, cfg.embeddingBatchSize);
  if (!content || content.trim().length === 0) {
    return {
      chunked: false,
      chunks: [],
      boundaries: [],
      method: "semantic"
    };
  }
  const sentences = splitSentences(content);
  if (sentences.length <= 1) {
    const tokenCount = estimateTokens(content);
    return {
      chunked: false,
      chunks: [
        {
          content: content.trim(),
          index: 0,
          tokenCount,
          boundaryScore: 1
        }
      ],
      boundaries: [],
      method: "semantic"
    };
  }
  const totalTokens = estimateTokens(content);
  if (totalTokens <= cfg.minTokens) {
    return {
      chunked: false,
      chunks: [
        {
          content: content.trim(),
          index: 0,
          tokenCount: totalTokens,
          boundaryScore: 1
        }
      ],
      boundaries: [],
      method: "semantic"
    };
  }
  let embeddings;
  try {
    embeddings = await batchEmbed(sentences, embedFn, batchSize);
  } catch {
    if (cfg.fallbackToRecursive) {
      return buildRecursiveFallback(content, cfg);
    }
    throw new Error(
      "Semantic chunking failed: embedding function threw and fallbackToRecursive is disabled"
    );
  }
  if (embeddings.length !== sentences.length) {
    if (cfg.fallbackToRecursive) {
      return buildRecursiveFallback(content, cfg);
    }
    throw new Error(
      `Semantic chunking failed: expected ${sentences.length} embeddings but received ${embeddings.length}`
    );
  }
  const similarities = [];
  for (let i = 0; i < sentences.length - 1; i++) {
    similarities.push(cosineSimilarity(embeddings[i], embeddings[i + 1]));
  }
  if (similarities.length <= 1) {
    if (totalTokens > cfg.maxTokens) {
      return buildRecursiveFallback(content, cfg);
    }
    return {
      chunked: false,
      chunks: [
        {
          content: content.trim(),
          index: 0,
          tokenCount: totalTokens,
          boundaryScore: similarities.length === 1 ? similarities[0] : 1
        }
      ],
      boundaries: [],
      method: "semantic"
    };
  }
  const smoothed = movingAverage(similarities, cfg.smoothingWindowSize);
  const m = mean(smoothed);
  const s = stddev(smoothed);
  const threshold = m - cfg.boundaryThresholdStdDevs * s;
  const rawBoundaries = findLocalMinima(smoothed, threshold);
  let segments = buildSegments(sentences, rawBoundaries);
  segments = mergeShortSegments(segments, cfg.minTokens);
  const chunks = [];
  const finalBoundaries = [];
  let sentenceOffset = 0;
  for (let segIdx = 0; segIdx < segments.length; segIdx++) {
    const segment = segments[segIdx];
    const segText = segment.join(" ");
    const segTokens = estimateTokens(segText);
    if (segTokens > cfg.maxTokens) {
      const subChunks = splitLongSegment(segment, cfg.maxTokens, cfg.targetTokens);
      for (const sc of subChunks) {
        chunks.push({
          ...sc,
          index: chunks.length
        });
      }
    } else {
      const trailingSentenceIdx = sentenceOffset + segment.length - 1;
      let bScore = 1;
      if (trailingSentenceIdx < similarities.length && segIdx < segments.length - 1) {
        bScore = smoothed[trailingSentenceIdx] ?? similarities[trailingSentenceIdx] ?? 1;
      }
      chunks.push({
        content: segText,
        index: chunks.length,
        tokenCount: segTokens,
        boundaryScore: bScore
      });
    }
    if (segIdx < segments.length - 1) {
      finalBoundaries.push(sentenceOffset + segment.length - 1);
    }
    sentenceOffset += segment.length;
  }
  return {
    chunked: chunks.length > 1,
    chunks,
    boundaries: finalBoundaries,
    method: "semantic"
  };
}
function buildRecursiveFallback(content, cfg) {
  const cappedTarget = Math.min(cfg.targetTokens, cfg.maxTokens);
  const result = chunkContent(content, {
    targetTokens: cappedTarget,
    minTokens: Math.min(cfg.minTokens, cappedTarget),
    overlapSentences: 0
  });
  return {
    chunked: result.chunked,
    chunks: result.chunks.map((c) => ({
      ...c,
      boundaryScore: 0
    })),
    boundaries: [],
    method: "recursive-fallback"
  };
}

export {
  DEFAULT_SEMANTIC_CHUNKING_CONFIG,
  cosineSimilarity,
  mean,
  stddev,
  movingAverage,
  findLocalMinima,
  semanticChunkContent
};
//# sourceMappingURL=chunk-KVE7R4CG.js.map