// v2.8 Collision detection — pure functions that find file overlaps between
// worktrees/agents editing the same files concurrently. Advisory only; writes
// are never blocked, but the collision info surfaces in the UI and logs.
//
// Severity levels:
//   same_line     — the same file, exact same line region
//   adjacent_line — the same file, lines touch or are within 5 lines
//   different_area — the same file, distant lines
//
// Pure functions, no side effects. Testable in isolation.

export type ConflictSeverity = 'same_line' | 'adjacent_line' | 'different_area';

export interface ConflictVerdict {
  filePath: string;
  worktrees: string[];
  severity: ConflictSeverity;
  agents: string[];
}

/**
 * Registry entry for a single file change recorded by a worktree.
 * Stored in the ConflictIndex Map value for each file path.
 */
export interface ConflictEntry {
  worktreeId: string;
  agent: string;
  /**
   * Approximate line range touched by the change. undefined when the change
   * creates or deletes the file (full-file collision vs. same-line).
   */
  lineRange?: { start: number; end: number };
  status: 'pending' | 'applied' | 'reverted';
  timestamp: number;
}

/**
 * Shape of the conflict index consumed by findConflicts.
 * File path → set of entries from different worktrees/agents.
 */
export type ConflictIndexData = ReadonlyMap<string, ReadonlySet<ConflictEntry>>;

/**
 * Find file overlaps between `changedFiles` and the conflict index, excluding
 * the caller's own worktree.
 *
 * Returns one ConflictVerdict per file that has entries from other worktrees.
 * Severity is the highest found (same_line > adjacent_line > different_area).
 */
export function findConflicts(
  changedFiles: string[],
  worktreeId: string,
  /** Approximate line range for the proposed changes, keyed by file path */
  changedRanges: Map<string, { start: number; end: number }>,
  conflictIndex: ConflictIndexData,
): ConflictVerdict[] {
  const verdicts: ConflictVerdict[] = [];

  for (const filePath of changedFiles) {
    const entries = conflictIndex.get(filePath);
    if (!entries || entries.size === 0) continue;

    // Filter to entries from OTHER worktrees
    const otherEntries = [...entries].filter((e) => e.worktreeId !== worktreeId);
    if (otherEntries.length === 0) continue;

    const myRange = changedRanges.get(filePath);
    let severity: ConflictSeverity = 'different_area';

    for (const entry of otherEntries) {
      if (!myRange || !entry.lineRange) {
        // Full-file changes (create/delete) always hit at least different_area
        continue;
      }
      const sev = lineOverlapSeverity(myRange, entry.lineRange);
      if (sev === 'same_line') {
        severity = 'same_line';
        break; // Can't get higher than this
      }
      if (sev === 'adjacent_line' && severity === 'different_area') {
        severity = 'adjacent_line';
      }
    }

    const worktrees = [...new Set(otherEntries.map((e) => e.worktreeId))];
    const agents = [...new Set(otherEntries.map((e) => e.agent))];

    verdicts.push({ filePath, worktrees, severity, agents });
  }

  return verdicts;
}

const ADJACENT_LINE_THRESHOLD = 5;

/**
 * Determine severity of overlap between two line ranges.
 */
function lineOverlapSeverity(
  a: { start: number; end: number },
  b: { start: number; end: number },
): ConflictSeverity {
  // Same_line: ranges intersect
  if (a.start <= b.end && b.start <= a.end) {
    return 'same_line';
  }

  // Adjacent: ranges are within ADJACENT_LINE_THRESHOLD lines of each other
  const gap = a.start > b.end ? a.start - b.end : b.start - a.end;
  if (gap <= ADJACENT_LINE_THRESHOLD) {
    return 'adjacent_line';
  }

  return 'different_area';
}
