export type DiffFileStatus = 'added' | 'deleted' | 'modified' | 'renamed';

export type ParsedDiffFile = {
  id: string;
  oldPath: string;
  newPath: string;
  /** Path to show in the UI (new path, or old path for deletions). */
  displayPath: string;
  status: DiffFileStatus;
  additions: number;
  deletions: number;
  isBinary: boolean;
  /** The full per-file unified diff section, fed as-is to the diff renderer. */
  hunkText: string;
};

const stripABPrefix = (value: string) => value.replace(/^[ab]\//, '');

/**
 * Splits a raw unified git diff into structured, per-file entries. Replaces the
 * "one giant blob" rendering: each file can be shown, counted, and highlighted
 * independently.
 */
export const parseDiffFiles = (diff: string | undefined): ParsedDiffFile[] => {
  if (!diff?.trim()) return [];
  const sections: string[][] = [];
  let current: string[] | null = null;
  for (const line of diff.split('\n')) {
    if (line.startsWith('diff --git ')) {
      if (current) sections.push(current);
      current = [line];
      continue;
    }
    current?.push(line);
  }
  if (current) sections.push(current);

  return sections.map((sectionLines, index) => {
    const header = sectionLines[0] ?? '';
    const gitMatch = /^diff --git a\/(.+?) b\/(.+)$/.exec(header);
    let oldPath = gitMatch?.[1] ?? '';
    let newPath = gitMatch?.[2] ?? oldPath;
    let status: DiffFileStatus = 'modified';
    let isBinary = false;
    let additions = 0;
    let deletions = 0;
    let renameFrom = '';
    let renameTo = '';

    for (const line of sectionLines) {
      if (line.startsWith('new file mode')) status = 'added';
      else if (line.startsWith('deleted file mode')) status = 'deleted';
      else if (line.startsWith('rename from ')) {
        renameFrom = line.slice('rename from '.length);
      } else if (line.startsWith('rename to ')) {
        renameTo = line.slice('rename to '.length);
      } else if (
        line.startsWith('Binary files') ||
        line.startsWith('GIT binary patch')
      ) {
        isBinary = true;
      } else if (line.startsWith('--- ')) {
        const value = line.slice(4).trim();
        if (value !== '/dev/null') oldPath = stripABPrefix(value);
      } else if (line.startsWith('+++ ')) {
        const value = line.slice(4).trim();
        if (value !== '/dev/null') newPath = stripABPrefix(value);
      } else if (line.startsWith('+') && !line.startsWith('+++')) {
        additions += 1;
      } else if (line.startsWith('-') && !line.startsWith('---')) {
        deletions += 1;
      }
    }

    if (renameFrom || renameTo) {
      status = 'renamed';
      oldPath = renameFrom || oldPath;
      newPath = renameTo || newPath;
    }

    const displayPath = status === 'deleted' ? oldPath : newPath;
    return {
      id: `${index}-${displayPath}`,
      oldPath,
      newPath,
      displayPath,
      status,
      additions,
      deletions,
      isBinary,
      hunkText: sectionLines.join('\n'),
    };
  });
};

/** Returns the single parsed file matching a path, if present in the diff. */
export const parseDiffFileForPath = (
  diff: string | undefined,
  filePath: string,
): ParsedDiffFile | undefined => {
  const normalized = filePath.replace(/^\.\/+/, '');
  return parseDiffFiles(diff).find(
    (file) =>
      file.displayPath === normalized ||
      file.newPath === normalized ||
      file.oldPath === normalized,
  );
};

export const extractFileDiff = (diff: string | undefined, filePath: string) => {
  if (!diff?.trim() || filePath === '.') return '';
  const lines = diff.split('\n');
  const sections: string[][] = [];
  let current: string[] | null = null;
  for (const line of lines) {
    if (line.startsWith('diff --git ')) {
      if (current) sections.push(current);
      current = [line];
      continue;
    }
    current?.push(line);
  }
  if (current) sections.push(current);
  const normalizedPath = filePath.replace(/^\.\/+/, '');
  const section = sections.find((item) => {
    const header = item[0] ?? '';
    return (
      header.includes(` a/${normalizedPath} `) ||
      header.endsWith(` a/${normalizedPath}`) ||
      header.includes(` b/${normalizedPath}`) ||
      header.endsWith(` b/${normalizedPath}`)
    );
  });
  return section?.join('\n') ?? '';
};
