export type SourceMutationOrigin =
  | 'source'
  | 'visual'
  | 'visual-structure'
  | 'mathlive'
  | 'citation'
  | 'ai'
  | 'asset'
  | 'git'
  | 'refactor'
  | 'review'
  | 'terminal'
  | 'collaboration'
  | 'system';

export type SourceTextEdit = {
  from: number;
  to: number;
  text: string;
};

export function applySourceTextEdit(source: string, edit: SourceTextEdit): string {
  if (
    !Number.isInteger(edit.from) ||
    !Number.isInteger(edit.to) ||
    edit.from < 0 ||
    edit.to < edit.from ||
    edit.to > source.length
  ) {
    throw new RangeError(
      `Invalid source edit range ${edit.from}..${edit.to} for ${source.length} characters`,
    );
  }

  return source.slice(0, edit.from) + edit.text + source.slice(edit.to);
}


export function applySourceTextEdits(
  source: string,
  edits: readonly SourceTextEdit[],
): string {
  const ordered = [...edits].sort((a, b) => b.from - a.from || b.to - a.to);
  let previousFrom = source.length + 1;
  let result = source;
  for (const edit of ordered) {
    if (edit.to > previousFrom) {
      throw new RangeError(`Overlapping source edits at ${edit.from}..${edit.to}`);
    }
    result = applySourceTextEdit(result, edit);
    previousFrom = edit.from;
  }
  return result;
}


/** Returns the smallest single splice that transforms current into next. */
export function computeMinimalSourceTextEdit(
  current: string,
  next: string,
): SourceTextEdit | undefined {
  if (current === next) return undefined;
  let prefix = 0;
  const maxPrefix = Math.min(current.length, next.length);
  while (prefix < maxPrefix && current.charCodeAt(prefix) === next.charCodeAt(prefix)) prefix += 1;

  let currentSuffix = current.length;
  let nextSuffix = next.length;
  while (
    currentSuffix > prefix &&
    nextSuffix > prefix &&
    current.charCodeAt(currentSuffix - 1) === next.charCodeAt(nextSuffix - 1)
  ) {
    currentSuffix -= 1;
    nextSuffix -= 1;
  }
  return { from: prefix, to: currentSuffix, text: next.slice(prefix, nextSuffix) };
}
