const compactWhitespace = (value: string) => value.replace(/\s+/g, " ").trim();
const MAX_SPEECH_CHUNK_LENGTH = 520;
const MIN_SPEECH_CHUNK_LENGTH = 180;
const SPEECH_SENTENCE_PATTERN = /[^。！？!?；;\n]+(?:[。！？!?；;]+|(?=\n|$))/g;

export type AgentSpeechState = "idle" | "loading" | "playing" | "paused";

export type AgentSpeakOptions = {
  startOffset?: number;
};

const normalizeWithOffsetMap = (value: string) => {
  let normalized = "";
  const offsetMap: number[] = [];
  let previousWasWhitespace = false;

  Array.from(value).forEach((character, index) => {
    if (/\s/u.test(character)) {
      if (!previousWasWhitespace && normalized.length > 0) {
        normalized += " ";
        offsetMap.push(index);
      }
      previousWasWhitespace = true;
      return;
    }

    normalized += character;
    offsetMap.push(index);
    previousWasWhitespace = false;
  });

  return { normalized: normalized.trimEnd(), offsetMap };
};

export function stripSpeechMarkdown(markdown: string) {
  return markdown
    .replace(/```[\s\S]*?```/g, "")
    .replace(/`([^`]+)`/g, "$1")
    .replace(/!\[.*?\]\(.*?\)/g, "")
    .replace(/\[([^\]]+)\]\(.*?\)/g, "$1")
    .replace(/#{1,6}\s+/g, "")
    .replace(/\*{1,3}(.+?)\*{1,3}/g, "$1")
    .replace(/~~(.+?)~~/g, "$1")
    .replace(/^>\s+/gm, "")
    .replace(/^[-*+]\s+/gm, "")
    .replace(/^\d+\.\s+/gm, "")
    .replace(/<[^>]+>/g, "")
    .replace(/\n{2,}/g, "\n")
    .trim();
}

export function findSpeechSelectionStartOffset(text: string, selectedText: string): number | null {
  const needle = selectedText.trim();
  if (!needle) return null;

  const exactIndex = text.indexOf(needle);
  if (exactIndex >= 0) return exactIndex;

  const normalizedNeedle = compactWhitespace(needle);
  if (!normalizedNeedle) return null;

  const haystack = normalizeWithOffsetMap(text);
  const normalizedIndex = haystack.normalized.indexOf(normalizedNeedle);
  if (normalizedIndex < 0) return null;

  return haystack.offsetMap[normalizedIndex] ?? null;
}

export function splitSpeechTextIntoChunks(text: string): string[] {
  const normalizedText = text.trim();
  if (!normalizedText) return [];

  const segments = Array.from(normalizedText.matchAll(SPEECH_SENTENCE_PATTERN), (match) =>
    compactWhitespace(match[0])
  ).filter(Boolean);
  const sourceSegments = segments.length > 0 ? segments : [normalizedText];
  const chunks: string[] = [];
  let currentChunk = "";

  const flush = () => {
    if (!currentChunk) return;
    chunks.push(currentChunk);
    currentChunk = "";
  };

  for (const segment of sourceSegments) {
    if (segment.length > MAX_SPEECH_CHUNK_LENGTH) {
      flush();
      for (let offset = 0; offset < segment.length; offset += MAX_SPEECH_CHUNK_LENGTH) {
        chunks.push(segment.slice(offset, offset + MAX_SPEECH_CHUNK_LENGTH));
      }
      continue;
    }

    const candidate = currentChunk ? `${currentChunk}${segment}` : segment;
    if (
      currentChunk &&
      candidate.length > MAX_SPEECH_CHUNK_LENGTH &&
      currentChunk.length >= MIN_SPEECH_CHUNK_LENGTH
    ) {
      flush();
      currentChunk = segment;
      continue;
    }

    currentChunk = candidate;
  }

  flush();
  return chunks;
}
