import { createSourceBackedBlockDeletion } from './structural-break';
import type { SourceTextEdit } from './text-edit';

export type LatexListStructureOperation = 'delete-item' | 'delete-list';

export type LatexListItemSourceRange = {
  from: number;
  to: number;
  bodyFrom: number;
  bodyTo: number;
};

export type LatexListStructureMutation = {
  edits: readonly SourceTextEdit[];
  focusOffset: number;
};

function clampInteger(value: number, min: number, max: number): number {
  if (!Number.isFinite(value)) return min;
  return Math.max(min, Math.min(max, Math.trunc(value)));
}

function mappedOffsetAfterEdit(offset: number, edit: SourceTextEdit): number {
  if (offset <= edit.from) return offset;
  if (offset >= edit.to) return offset + edit.text.length - (edit.to - edit.from);
  return edit.from + edit.text.length;
}

function validListRange(
  source: string,
  range: { from: number; to: number },
): boolean {
  if (range.from < 0 || range.to <= range.from || range.to > source.length) return false;
  const raw = source.slice(range.from, range.to);
  const begin = /^\\begin\s*\{(itemize|enumerate)\}/u.exec(raw);
  if (!begin) return false;
  const environment = begin[1];
  return new RegExp(`\\\\end\\s*\\{${environment}\\}\\s*$`, 'u').test(raw);
}

function validItems(
  source: string,
  listRange: { from: number; to: number },
  items: readonly LatexListItemSourceRange[],
): boolean {
  if (items.length === 0) return false;
  let previousTo = listRange.from;
  for (const item of items) {
    if (
      item.from < listRange.from ||
      item.to <= item.from ||
      item.to > listRange.to ||
      item.from < previousTo ||
      item.bodyFrom < item.from ||
      item.bodyFrom > item.to ||
      item.bodyTo < item.bodyFrom ||
      item.bodyTo > item.to ||
      !/^\\item(?![A-Za-z@])/u.test(source.slice(item.from, item.to))
    ) {
      return false;
    }
    previousTo = item.to;
  }
  return true;
}

/**
 * Canonical Visual list deletion. Structural list nodes are never removed by a
 * free-form ProseMirror transaction: the operation is expressed as a precise
 * LaTeX source edit and the editor reparses/reprojects afterward.
 *
 * Deleting the entire list materializes exactly one source-backed blank Visual
 * row. Existing linefeeds around the environment are reused so deletion does
 * not manufacture multiple empty rows.
 */
export function createLatexListStructureMutation(
  source: string,
  listRange: { from: number; to: number },
  items: readonly LatexListItemSourceRange[],
  operation: LatexListStructureOperation,
  selectedItem = 0,
): LatexListStructureMutation | undefined {
  if (!validListRange(source, listRange) || !validItems(source, listRange, items)) {
    return undefined;
  }

  if (operation === 'delete-list' || items.length === 1) {
    const deletion = createSourceBackedBlockDeletion(source, listRange);
    if (!deletion) return undefined;
    return { edits: [deletion.edit], focusOffset: deletion.focusOffset };
  }

  const index = clampInteger(selectedItem, 0, items.length - 1);
  const item = items[index]!;
  const edit: SourceTextEdit = { from: item.from, to: item.to, text: '' };
  const next = items[index + 1];
  const previous = items[index - 1];
  const target = next?.bodyFrom ?? previous?.bodyTo ?? item.from;
  return {
    edits: [edit],
    focusOffset: mappedOffsetAfterEdit(target, edit),
  };
}
