import { escapeRegexLiteral, removeMarkdownCodeFenceBlocks } from "@best-agent/foundation";

export type TextualToolCallExtraction = {
  known: boolean;
  toolName: string;
};

export type ControlPayloadClassifierOptions = {
  ignoreMarkdownCodeFences?: boolean;
};

export type ControlPayloadPrefixRisk =
  | "none"
  | "ambiguous-provider-native-json-prefix"
  | "ambiguous-runtime-kind-json-prefix"
  | "standalone-open-json-prefix"
  | "strong-control-prefix";

const MAX_JSON_BLOCK_PARSE_CHARS = 8_192;
// This is a hard safety window for regex evidence scanning, not a complete JSON
// parser for arbitrarily large business payloads. Streaming gates must enforce
// their own standalone JSON buffer limits before final classification.
const MAX_CONTROL_EVIDENCE_SCAN_CHARS = 128 * 1_024;

export function containsControlPayloadFinal(
  text: string,
  toolNames: readonly string[] = [],
  options: ControlPayloadClassifierOptions = {},
): boolean {
  const stripped = normalizeControlPayloadText(text, options).trim();
  if (!stripped) {
    return false;
  }
  if (containsDecisionControlLeakFinal(stripped, toolNames)) {
    return true;
  }
  if (hasToolCallMarkup(stripped)) {
    return true;
  }
  if (hasProviderInternalPayload(stripped)) {
    return true;
  }
  return extractTextualToolCallName(stripped, toolNames) !== undefined;
}

export function containsControlPayloadPrefix(
  text: string,
  toolNames: readonly string[] = [],
  options: ControlPayloadClassifierOptions = {},
): boolean {
  const stripped = normalizeControlPayloadText(text, options).trim();
  if (!stripped) {
    return false;
  }
  if (containsDecisionControlLeakPrefix(stripped, toolNames)) {
    return true;
  }
  if (hasToolCallMarkup(stripped) || hasToolCallMarkupPrefix(stripped)) {
    return true;
  }
  if (hasProviderInternalPayload(stripped) || /^<\|?\/?think\b/i.test(stripped)) {
    return true;
  }
  return extractTextualToolCallName(stripped, toolNames) !== undefined;
}

export function classifyControlPayloadPrefixRisk(
  text: string,
  toolNames: readonly string[] = [],
  options: ControlPayloadClassifierOptions = {},
): ControlPayloadPrefixRisk {
  const raw = text.trimStart();
  if ((options.ignoreMarkdownCodeFences ?? true) && raw.startsWith("```")) {
    return "none";
  }
  const stripped = normalizeControlPayloadText(text, options).trimStart();
  if (!stripped) {
    return "none";
  }
  if (isPotentialControlMarkupPrefix(stripped)) {
    return "strong-control-prefix";
  }
  if (containsControlPayloadPrefix(stripped, toolNames, { ignoreMarkdownCodeFences: false })) {
    return "strong-control-prefix";
  }
  if (stripped.startsWith("{") || stripped.startsWith("[")) {
    if (jsonValueClosed(stripped)) {
      try {
        JSON.parse(stripped);
        return "none";
      } catch {
        return "none";
      }
    }
    if (looksLikeIncompleteAmbiguousRuntimeKindJsonPrefix(stripped)) {
      return "ambiguous-runtime-kind-json-prefix";
    }
    if (looksLikeIncompleteAmbiguousProviderNativeJsonPrefix(stripped)) {
      return "ambiguous-provider-native-json-prefix";
    }
    return "standalone-open-json-prefix";
  }
  if (
    /^<\|?\/?(?:think|tool|tool_call|tool_calls_section|tool_call_begin|tool_call_argument_begin|tool_call_end|arg_key|arg_value|arg)/iu.test(
      stripped,
    )
  ) {
    return "strong-control-prefix";
  }
  return stripped === "<" || stripped === "</" || stripped === "<|"
    ? "strong-control-prefix"
    : "none";
}

export function shouldHoldControlPayloadPrefix(text: string): boolean {
  return classifyControlPayloadPrefixRisk(text) !== "none";
}

function normalizeControlPayloadText(
  text: string,
  options: ControlPayloadClassifierOptions,
): string {
  return (options.ignoreMarkdownCodeFences ?? true) ? removeMarkdownCodeFenceBlocks(text) : text;
}

export function shouldHoldTextualToolInvocationPrefix(
  text: string,
  toolNames: readonly string[],
  options: { holdSingleCharacterPrefix?: boolean } = {},
): boolean {
  const stripped = removeMarkdownCodeFenceBlocks(text).trimStart();
  const candidate = stripped.trim();
  if (!candidate || /\s/u.test(candidate)) {
    return false;
  }
  if (toolNames.length === 0) {
    return false;
  }
  if (/^(?:[a-z][\w:-]*\.)+$/iu.test(candidate)) {
    return true;
  }
  if (candidate.length < 2) {
    return options.holdSingleCharacterPrefix === true ||
      toolNames.some((toolName) => toolName.length === 1)
      ? toolNames.some((toolName) => toolName.toLowerCase().startsWith(candidate.toLowerCase()))
      : false;
  }
  return toolNames.some((toolName) => toolName.toLowerCase().startsWith(candidate.toLowerCase()));
}

export function classifyStandaloneRuntimeDecisionJsonKind(
  text: string,
): "phase_result" | "plan_update" | "respond" | "tool" | undefined {
  const normalized = text.trim();
  if (!normalized.startsWith("{") && !normalized.startsWith("[")) {
    return undefined;
  }
  if (normalized.length > MAX_JSON_BLOCK_PARSE_CHARS) {
    return jsonValueClosed(normalized)
      ? classifyRuntimeDecisionJsonPrefixKind(normalized)
      : undefined;
  }
  try {
    return classifyRuntimeDecisionJsonValueKind(JSON.parse(normalized));
  } catch {
    return jsonValueClosed(normalized)
      ? classifyRuntimeDecisionJsonPrefixKind(normalized)
      : undefined;
  }
}

export function looksLikeStandaloneRuntimeDecisionJson(text: string): boolean {
  const normalized = text.trim();
  if (!normalized.startsWith("{") && !normalized.startsWith("[")) {
    return false;
  }
  if (normalized.length > MAX_JSON_BLOCK_PARSE_CHARS) {
    return (
      jsonValueClosed(normalized) && classifyRuntimeDecisionJsonPrefixKind(normalized) !== undefined
    );
  }
  try {
    return looksLikeRuntimeDecisionJsonValue(JSON.parse(normalized));
  } catch {
    return (
      jsonValueClosed(normalized) && classifyRuntimeDecisionJsonPrefixKind(normalized) !== undefined
    );
  }
}

export function looksLikeStandaloneRuntimeControlJson(text: string): boolean {
  const normalized = text.trim();
  if (!normalized.startsWith("{") && !normalized.startsWith("[")) {
    return false;
  }
  if (normalized.length > MAX_JSON_BLOCK_PARSE_CHARS) {
    return (
      jsonValueClosed(normalized) &&
      looksLikeRuntimeControlJsonPrefix(controlEvidenceScanWindow(normalized))
    );
  }
  try {
    return looksLikeRuntimeControlJsonValue(JSON.parse(normalized));
  } catch {
    return (
      jsonValueClosed(normalized) &&
      looksLikeRuntimeControlJsonPrefix(controlEvidenceScanWindow(normalized))
    );
  }
}

export function looksLikeStandaloneProviderNativeToolCallJson(text: string): boolean {
  const normalized = text.trim();
  if (!normalized.startsWith("{") && !normalized.startsWith("[")) {
    return false;
  }
  if (normalized.length > MAX_JSON_BLOCK_PARSE_CHARS) {
    return (
      jsonValueClosed(normalized) &&
      looksLikeProviderNativeToolCallJsonPrefix(controlEvidenceScanWindow(normalized))
    );
  }
  try {
    return looksLikeProviderNativeToolCallJsonValue(JSON.parse(normalized));
  } catch {
    return (
      jsonValueClosed(normalized) &&
      looksLikeProviderNativeToolCallJsonPrefix(controlEvidenceScanWindow(normalized))
    );
  }
}

function looksLikeRuntimeDecisionObject(value: unknown): boolean {
  if (!value || typeof value !== "object" || Array.isArray(value)) {
    return false;
  }
  const parsed = value as Record<string, unknown>;
  const kind = parsed.kind;
  if (kind === "tool") {
    return (
      "toolName" in parsed || "providerToolName" in parsed || "input" in parsed || "args" in parsed
    );
  }
  if (kind === "plan_update") {
    return "plan" in parsed || "todos" in parsed;
  }
  if (kind === "phase_result") {
    return (
      "phaseName" in parsed ||
      "phaseResult" in parsed ||
      "result" in parsed ||
      "candidateVerdict" in parsed
    );
  }
  return kind === "respond" && "text" in parsed;
}

function looksLikeProviderNativeToolCallObject(value: unknown): boolean {
  if (!value || typeof value !== "object" || Array.isArray(value)) {
    return false;
  }
  const record = value as Record<string, unknown>;
  const toolCalls = Array.isArray(record.tool_calls)
    ? record.tool_calls
    : Array.isArray(record.toolCalls)
      ? record.toolCalls
      : undefined;

  return (
    (toolCalls?.some(looksLikeProviderNativeToolCallEntry) ?? false) ||
    looksLikeProviderNativeFunctionCall(record.function_call) ||
    looksLikeProviderNativeFunctionCall(record.functionCall)
  );
}

function looksLikeProviderNativeToolCallEntry(value: unknown): boolean {
  if (!value || typeof value !== "object" || Array.isArray(value)) {
    return false;
  }
  const record = value as Record<string, unknown>;
  if (typeof record.name === "string" && ("arguments" in record || "input" in record)) {
    return true;
  }
  if (typeof record.toolName === "string" && ("arguments" in record || "input" in record)) {
    return true;
  }
  return looksLikeProviderNativeFunctionCall(record.function);
}

function looksLikeProviderNativeFunctionCall(value: unknown): boolean {
  if (!value || typeof value !== "object" || Array.isArray(value)) {
    return false;
  }
  const record = value as Record<string, unknown>;
  return typeof record.name === "string" && ("arguments" in record || "input" in record);
}

function looksLikeRuntimeDecisionJsonValue(value: unknown): boolean {
  if (looksLikeRuntimeDecisionObject(value)) {
    return true;
  }
  if (Array.isArray(value)) {
    return value.some((item) => looksLikeRuntimeDecisionJsonValue(item));
  }
  return false;
}

function looksLikeRuntimeControlJsonValue(value: unknown): boolean {
  if (looksLikeRuntimeDecisionObject(value) || looksLikeProviderNativeToolCallJsonValue(value)) {
    return true;
  }
  if (Array.isArray(value)) {
    return value.some((item) => looksLikeRuntimeControlJsonValue(item));
  }
  return false;
}

function looksLikeProviderNativeToolCallJsonValue(value: unknown): boolean {
  if (looksLikeProviderNativeToolCallObject(value)) {
    return true;
  }
  if (Array.isArray(value)) {
    return value.some((item) => looksLikeProviderNativeToolCallJsonValue(item));
  }
  return false;
}

function classifyRuntimeDecisionJsonValueKind(
  value: unknown,
): "phase_result" | "plan_update" | "respond" | "tool" | undefined {
  if (looksLikeRuntimeDecisionObject(value)) {
    return (value as { kind: "phase_result" | "plan_update" | "respond" | "tool" }).kind;
  }
  if (Array.isArray(value)) {
    for (const item of value) {
      const kind = classifyRuntimeDecisionJsonValueKind(item);
      if (kind) {
        return kind;
      }
    }
  }
  return undefined;
}

export function looksLikeStandaloneRuntimeToolDecisionJson(text: string): boolean {
  return (
    looksLikeStandaloneRuntimeDecisionJson(text) &&
    classifyStandaloneRuntimeDecisionJsonKind(text) === "tool"
  );
}

export function extractStandaloneRuntimeDecisionJsonBlocks(text: string): string[] {
  return extractStandaloneRuntimeControlJsonBlocks(text).filter((candidate) => {
    const normalized = candidate.trim();
    if (normalized.length > MAX_JSON_BLOCK_PARSE_CHARS) {
      return classifyRuntimeDecisionJsonPrefixKind(normalized) !== undefined;
    }
    return looksLikeStandaloneRuntimeDecisionJson(normalized);
  });
}

export function extractStandaloneRuntimeControlJsonBlocks(
  text: string,
  options: ControlPayloadClassifierOptions = {},
): string[] {
  const stripped = normalizeControlPayloadText(text, options);
  const candidates = new Set<string>();
  const full = stripped.trim();
  if (full) {
    candidates.add(full);
  }
  for (const line of stripped.split(/\r?\n/u)) {
    const candidate = line.trim();
    if (candidate.startsWith("{") || candidate.startsWith("[")) {
      candidates.add(candidate);
    }
  }
  for (const candidate of extractStandaloneJsonLineBlocks(stripped)) {
    candidates.add(candidate);
  }
  return [...candidates].filter((candidate) => {
    const normalized = candidate.trim();
    if (normalized.length > MAX_JSON_BLOCK_PARSE_CHARS) {
      return looksLikeRuntimeControlJsonPrefix(controlEvidenceScanWindow(normalized));
    }
    return looksLikeStandaloneRuntimeControlJson(normalized);
  });
}

export function extractTextualToolCallName(
  text: string,
  toolNames: readonly string[],
): TextualToolCallExtraction | undefined {
  const markupToolName = text.match(
    /(?:<tool_call(?:\s[^>]*)?>|<\|tool_call\b)\s*(?:[a-z][\w:-]*\.)?([A-Za-z0-9_:-]+)/iu,
  )?.[1];
  if (markupToolName) {
    return normalizeToolNameExtraction(markupToolName, toolNames);
  }

  if (toolNames.length === 0) {
    return undefined;
  }
  const invocation = text.match(textualToolInvocationPattern(toolNames));
  const toolName = invocation?.groups?.toolName;
  return toolName ? { known: true, toolName } : undefined;
}

export function hasToolCallMarkup(text: string): boolean {
  return (
    /<\/?(?:tool_call|tool_calls_section|tool_call_begin|tool_call_argument_begin|tool_call_end|arg_key|arg_value)(?:\s[^>]*)?(?:>|\|>)/i.test(
      text,
    ) ||
    /<\|(?:tool_call|tool_calls_section|tool_call_begin|tool_call_argument_begin|tool_call_end)\b/i.test(
      text,
    ) ||
    /\b(?:tool_calls_section_begin|tool_calls_section_end|tool_call_begin|tool_call_argument_begin|tool_call_end)\b/i.test(
      text,
    )
  );
}

export function hasToolCallMarkupPrefix(text: string): boolean {
  return (
    /^<\|?\/?tool(?:\b|_|$)/i.test(text) ||
    /^<\|?(?:tool_call|tool_calls_section|tool_call_begin|tool_call_argument_begin|tool_call_end)\b/i.test(
      text,
    ) ||
    /^<\/?(?:tool_call|tool_calls_section|tool_call_begin|tool_call_argument_begin|tool_call_end|arg_key|arg_value)(?:\s[^>]*)?(?:>|\|>)?/i.test(
      text,
    ) ||
    /\b(?:tool_calls_section_begin|tool_calls_section_end|tool_call_begin|tool_call_argument_begin|tool_call_end)\b/i.test(
      text,
    )
  );
}

export function hasProviderInternalPayload(text: string): boolean {
  return /<\/?think(?:\s[^>]*)?>/i.test(text);
}

export function hasPhaseOrPlanPayload(text: string): boolean {
  return /\b(?:plan_update|phase_result)\b/i.test(text);
}

function containsDecisionControlLeakFinal(
  text: string,
  toolNames: readonly string[] = [],
): boolean {
  if (
    looksLikeStandaloneRuntimeControlJson(text) ||
    extractStandaloneRuntimeControlJsonBlocks(text, { ignoreMarkdownCodeFences: false }).length > 0
  ) {
    return true;
  }
  return extractTextualToolCallName(text, toolNames) !== undefined;
}

function containsDecisionControlLeakPrefix(
  text: string,
  toolNames: readonly string[] = [],
): boolean {
  if (
    extractStandaloneRuntimeControlJsonBlocks(text, { ignoreMarkdownCodeFences: false }).length >
      0 ||
    extractStandaloneRuntimeControlJsonPrefixCandidates(text).some(
      (candidate) =>
        looksLikeStandaloneRuntimeControlJson(candidate) ||
        looksLikeRuntimeControlJsonPrefix(candidate),
    )
  ) {
    return true;
  }
  return extractTextualToolCallName(text, toolNames) !== undefined;
}

function looksLikeRuntimeControlJsonPrefix(text: string): boolean {
  const normalized = text.trim();
  if (!normalized.startsWith("{") && !normalized.startsWith("[")) {
    return false;
  }
  if (looksLikeProviderNativeToolCallJsonPrefix(normalized)) {
    return true;
  }
  const match = normalized.match(/"kind"\s*:\s*"(tool|respond|plan_update|phase_result)"/i);
  const kind = match?.[1]?.toLowerCase();
  if (kind === "tool") {
    return /"(?:toolName|providerToolName|input|args)"\s*:/i.test(normalized);
  }
  if (kind === "plan_update") {
    return /"(?:plan|todos)"\s*:/i.test(normalized);
  }
  if (kind === "phase_result") {
    return /"(?:phaseName|phaseResult|result|candidateVerdict)"\s*:/i.test(normalized);
  }
  return kind === "respond" && /"text"\s*:/i.test(normalized);
}

function classifyRuntimeDecisionJsonPrefixKind(
  text: string,
): "phase_result" | "plan_update" | "respond" | "tool" | undefined {
  const normalized = controlEvidenceScanWindow(text.trim());
  if (!normalized.startsWith("{") && !normalized.startsWith("[")) {
    return undefined;
  }
  const match = normalized.match(/"kind"\s*:\s*"(tool|respond|plan_update|phase_result)"/i);
  const kind = match?.[1]?.toLowerCase();
  if (kind === "tool") {
    return /"(?:toolName|providerToolName|input|args)"\s*:/i.test(normalized) ? "tool" : undefined;
  }
  if (kind === "plan_update") {
    return /"(?:plan|todos)"\s*:/i.test(normalized) ? "plan_update" : undefined;
  }
  if (kind === "phase_result") {
    return /"(?:phaseName|phaseResult|result|candidateVerdict)"\s*:/i.test(normalized)
      ? "phase_result"
      : undefined;
  }
  return kind === "respond" && /"text"\s*:/i.test(normalized) ? "respond" : undefined;
}

function looksLikeIncompleteAmbiguousRuntimeKindJsonPrefix(text: string): boolean {
  return extractStandaloneRuntimeControlJsonPrefixCandidates(text).some((candidate) => {
    const normalized = candidate.trim();
    if (!normalized.startsWith("{") && !normalized.startsWith("[")) {
      return false;
    }
    if (jsonValueClosed(normalized)) {
      try {
        JSON.parse(normalized);
        return false;
      } catch {
        return false;
      }
    }
    return /"kind"\s*:\s*"(?:tool|respond|plan_update|phase_result)"/i.test(normalized);
  });
}

function looksLikeIncompleteAmbiguousProviderNativeJsonPrefix(text: string): boolean {
  return extractStandaloneRuntimeControlJsonPrefixCandidates(text).some((candidate) => {
    const normalized = candidate.trim();
    if (!normalized.startsWith("{") && !normalized.startsWith("[")) {
      return false;
    }
    if (jsonValueClosed(normalized)) {
      try {
        JSON.parse(normalized);
        return false;
      } catch {
        return false;
      }
    }
    return /"(?:tool_calls|toolCalls|function_call|functionCall)"\s*:/i.test(normalized);
  });
}

const CONTROL_MARKUP_PREFIXES = [
  "<tool",
  "<tool_call",
  "</tool_call",
  "<tool_calls_section",
  "<|tool_call",
  "<|tool_calls_section",
  "<think",
  "</think",
];

function isPotentialControlMarkupPrefix(text: string): boolean {
  const stripped = text.trimStart().toLowerCase();
  if (!stripped.startsWith("<")) {
    return false;
  }
  return CONTROL_MARKUP_PREFIXES.some((prefix) => prefix.startsWith(stripped));
}

function looksLikeProviderNativeToolCallJsonPrefix(text: string): boolean {
  const toolCallsMatch = text.match(/"(?:tool_calls|toolCalls)"\s*:\s*\[/i);
  if (toolCallsMatch?.index !== undefined) {
    const toolCallsTail = text.slice(toolCallsMatch.index);
    if (looksLikeProviderNativeToolCallEntryPrefix(toolCallsTail)) {
      return true;
    }
  }

  const functionCallMatch = text.match(/"(?:function_call|functionCall)"\s*:\s*\{/i);
  if (functionCallMatch?.index === undefined) {
    return false;
  }
  return looksLikeProviderNativeFunctionCallPrefix(text.slice(functionCallMatch.index));
}

function looksLikeProviderNativeToolCallEntryPrefix(text: string): boolean {
  return (
    hasProviderNativeNamedArgumentsPrefix(text, ["name", "toolName"]) ||
    /"function"\s*:\s*\{[\s\S]*"name"\s*:\s*"[^"]+"[\s\S]*"(?:arguments|input)"\s*:/i.test(text) ||
    /"function"\s*:\s*\{[\s\S]*"(?:arguments|input)"\s*:[\s\S]*"name"\s*:\s*"[^"]+"/i.test(text)
  );
}

function looksLikeProviderNativeFunctionCallPrefix(text: string): boolean {
  return hasProviderNativeNamedArgumentsPrefix(text, ["name"]);
}

function hasProviderNativeNamedArgumentsPrefix(text: string, nameKeys: readonly string[]): boolean {
  const namePattern = nameKeys.map(escapeRegexLiteral).join("|");
  return (
    new RegExp(
      String.raw`"(?:${namePattern})"\s*:\s*"[^"]+"[\s\S]*"(?:arguments|input)"\s*:`,
      "iu",
    ).test(text) ||
    new RegExp(
      String.raw`"(?:arguments|input)"\s*:[\s\S]*"(?:${namePattern})"\s*:\s*"[^"]+"`,
      "iu",
    ).test(text)
  );
}

function extractStandaloneRuntimeControlJsonPrefixCandidates(text: string): string[] {
  const stripped = text;
  const candidates = new Set<string>();
  const full = stripped.trim();
  if (full) {
    candidates.add(full);
  }
  for (const line of stripped.split(/\r?\n/u)) {
    const candidate = line.trim();
    if (candidate.startsWith("{") || candidate.startsWith("[")) {
      candidates.add(candidate);
    }
  }
  for (const candidate of extractStandaloneJsonLineBlocks(stripped)) {
    candidates.add(candidate);
  }
  return [...candidates];
}

function extractStandaloneJsonLineBlocks(text: string): string[] {
  const lines = text.split(/\r?\n/u);
  const blocks: string[] = [];
  for (let index = 0; index < lines.length; index += 1) {
    const first = lines[index]?.trimStart();
    if (!first?.startsWith("{") && !first?.startsWith("[")) {
      continue;
    }
    let candidate = "";
    for (let end = index; end < lines.length; end += 1) {
      candidate = candidate ? `${candidate}\n${lines[end]}` : (lines[end] ?? "");
      const trimmed = candidate.trim();
      if (trimmed.length > MAX_JSON_BLOCK_PARSE_CHARS) {
        if (looksLikeRuntimeControlJsonPrefix(controlEvidenceScanWindow(trimmed))) {
          blocks.push(trimmed);
          break;
        }
        if (trimmed.length >= MAX_CONTROL_EVIDENCE_SCAN_CHARS || jsonValueClosed(trimmed)) {
          break;
        }
        continue;
      }
      if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
        break;
      }
      try {
        JSON.parse(trimmed);
        blocks.push(trimmed);
        break;
      } catch {
        if (jsonValueClosed(trimmed)) {
          if (looksLikeRuntimeControlJsonPrefix(controlEvidenceScanWindow(trimmed))) {
            blocks.push(trimmed);
          }
          break;
        }
      }
    }
  }
  return blocks;
}

function controlEvidenceScanWindow(text: string): string {
  return text.slice(0, MAX_CONTROL_EVIDENCE_SCAN_CHARS);
}

function jsonValueClosed(text: string): boolean {
  let objectDepth = 0;
  let arrayDepth = 0;
  let escaped = false;
  let inString = false;
  for (const char of text) {
    if (escaped) {
      escaped = false;
      continue;
    }
    if (char === "\\") {
      escaped = true;
      continue;
    }
    if (char === '"') {
      inString = !inString;
      continue;
    }
    if (inString) {
      continue;
    }
    if (char === "{") {
      objectDepth += 1;
    } else if (char === "}") {
      objectDepth -= 1;
    } else if (char === "[") {
      arrayDepth += 1;
    } else if (char === "]") {
      arrayDepth -= 1;
    }
  }
  return objectDepth <= 0 && arrayDepth <= 0;
}

function normalizeToolNameExtraction(
  candidate: string,
  toolNames: readonly string[],
): TextualToolCallExtraction {
  const knownToolName = toolNames.find(
    (toolName) => toolName.toLowerCase() === candidate.toLowerCase(),
  );
  return {
    known: Boolean(knownToolName),
    toolName: knownToolName ?? candidate,
  };
}

function textualToolInvocationPattern(toolNames: readonly string[]): RegExp {
  const toolAlternation = toolNames
    .map((toolName) => escapeRegexLiteral(toolName))
    .sort((left, right) => right.length - left.length)
    .join("|");
  return new RegExp(
    String.raw`(?:^|\n)\s*(?:[a-z][\w:-]*\.)*(?<toolName>${toolAlternation})\s*\{\s*"[^"]+"\s*:`,
    "iu",
  );
}
