export function isUnsafeRegex(pattern: string): boolean {
  const groups = findQuantifiedGroups(pattern);
  return groups.some((group) => containsQuantifier(group) || containsOverlappingAlternation(group));
}

function containsOverlappingAlternation(pattern: string): boolean {
  const branches = splitTopLevelAlternation(stripGroupPrefix(pattern));
  if (branches.length < 2) return false;

  for (let i = 0; i < branches.length; i++) {
    const current = branches[i]!;
    if (current === "") continue;
    for (let j = i + 1; j < branches.length; j++) {
      const next = branches[j]!;
      if (next === "") continue;
      if (current === next || current.startsWith(next) || next.startsWith(current)) return true;
    }
  }
  return false;
}

function containsQuantifier(pattern: string): boolean {
  const input = stripGroupPrefix(pattern);
  let inCharClass = false;

  for (let i = 0; i < input.length; i++) {
    const char = input[i]!;
    if (isEscaped(input, i)) continue;
    if (char === "[") {
      inCharClass = true;
      continue;
    }
    if (char === "]") {
      inCharClass = false;
      continue;
    }
    if (inCharClass) continue;
    if (char === "*" || char === "+" || char === "?") return true;
    if (char === "{" && readQuantifierBody(input, i) !== null) return true;
  }

  return false;
}

function findQuantifiedGroups(pattern: string): string[] {
  const groups: string[] = [];
  const stack: number[] = [];
  let inCharClass = false;

  for (let i = 0; i < pattern.length; i++) {
    const char = pattern[i]!;
    if (isEscaped(pattern, i)) continue;
    if (char === "[") {
      inCharClass = true;
      continue;
    }
    if (char === "]") {
      inCharClass = false;
      continue;
    }
    if (inCharClass) continue;

    if (char === "(") {
      stack.push(i);
      continue;
    }

    if (char === ")") {
      const start = stack.pop();
      if (start === undefined) continue;
      if (hasRepeatingQuantifierAt(pattern, i + 1)) {
        groups.push(pattern.slice(start + 1, i));
      }
    }
  }

  return groups;
}

function hasRepeatingQuantifierAt(pattern: string, index: number): boolean {
  const char = pattern[index];
  if (char === "*" || char === "+") return true;
  if (char !== "{") return false;

  const body = readQuantifierBody(pattern, index);
  if (body === null) return false;
  const parts = body.split(",");
  if (parts.length === 1) return Number(parts[0]) > 1;
  if (parts[1] === "") return true;
  return Number(parts[1]) > 1;
}

function isEscaped(pattern: string, index: number): boolean {
  let slashCount = 0;
  for (let i = index - 1; i >= 0 && pattern[i] === "\\"; i--) {
    slashCount++;
  }
  return slashCount % 2 === 1;
}

function readQuantifierBody(pattern: string, index: number): null | string {
  const end = pattern.indexOf("}", index + 1);
  if (end === -1) return null;

  const body = pattern.slice(index + 1, end);
  return /^\d+(?:,\d*)?$/.test(body) ? body : null;
}

function splitTopLevelAlternation(pattern: string): string[] {
  const branches: string[] = [];
  let start = 0;
  let depth = 0;
  let inCharClass = false;

  for (let i = 0; i < pattern.length; i++) {
    const char = pattern[i]!;
    if (isEscaped(pattern, i)) continue;
    if (char === "[") {
      inCharClass = true;
      continue;
    }
    if (char === "]") {
      inCharClass = false;
      continue;
    }
    if (inCharClass) continue;
    if (char === "(") {
      depth++;
      continue;
    }
    if (char === ")") {
      depth = Math.max(0, depth - 1);
      continue;
    }
    if (char === "|" && depth === 0) {
      branches.push(pattern.slice(start, i));
      start = i + 1;
    }
  }

  branches.push(pattern.slice(start));
  return branches;
}

function stripGroupPrefix(pattern: string): string {
  if (pattern.startsWith("?:") || pattern.startsWith("?=") || pattern.startsWith("?!")) return pattern.slice(2);
  if (pattern.startsWith("?<=") || pattern.startsWith("?<!")) return pattern.slice(3);

  const namedCapture = /^\?<[^>]+>/.exec(pattern);
  return namedCapture ? pattern.slice(namedCapture[0].length) : pattern;
}
