#!/usr/bin/env bash

# Author: Santhosh Siva
# Date Created: 03-08-2025

# Description:
# A script to compare changes between two git branches.

# Resolve script directory (works with symlinks for npm global install)
SOURCE="${BASH_SOURCE[0]}"
while [ -L "$SOURCE" ]; do
  DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
  SOURCE="$(readlink "$SOURCE")"
  [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
done
SCRIPT_DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
source "$SCRIPT_DIR/utils"

target_branch=""
source_branch=""
full_diff=false
files_only=false
output_file=""

set_flags() {
	while [ $# -gt 0 ]; do
		case "$1" in
		-h | --help)
			echo "g-diff - compare changes between two git branches"
			echo " "
			echo "g-diff [options]"
			echo " "
			echo "options:"
			echo "-h, --help                                                             show brief help"
			echo "--target-branch BRANCH, --target-branch=BRANCH, -t=BRANCH, -t BRANCH   specify the target branch"
			echo "--source-branch BRANCH, --source-branch=BRANCH, -s=BRANCH, -s BRANCH   specify the source branch (defaults to current branch)"
			echo "-f, --full                                                             show full diff and copy to clipboard"
			echo "--files-only                                                           show only file names (no stats, no clipboard)"
		echo "-o FILE, --output-file FILE, --output-file=FILE                        write output to file instead of clipboard"
		exit 0
			;;
		-f | --full)
			full_diff=true
			;;
		--files-only)
			files_only=true
			;;
		-o=* | --output-file=*)
			output_file="${1#*=}"
			if [ -z "$output_file" ]; then
				print_message "" -1
				print_message "${RED}Error: No output file specified.${NC}" -1
				exit 1
			fi
			;;
		-o | --output-file)
			shift
			if [ $# -gt 0 ]; then
				output_file="$1"
			else
				print_message "" -1
				print_message "${RED}Error: No output file specified.${NC}" -1
				exit 1
			fi
			;;
		-t=* | --target-branch=*)
			target_branch="${1#*=}"
			if [ -z "$target_branch" ]; then
				print_message "" -1
				print_message "${RED}Error: No target branch specified.${NC}" -1
				exit 1
			fi
			;;
		-t | --target-branch)
			shift
			if [ $# -gt 0 ]; then
				target_branch="$1"
			else
				print_message "" -1
				print_message "${RED}Error: No target branch specified.${NC}" -1
				exit 1
			fi
			;;
		-s=* | --source-branch=*)
			source_branch="${1#*=}"
			if [ -z "$source_branch" ]; then
				print_message "" -1
				print_message "${RED}Error: No source branch specified.${NC}" -1
				exit 1
			fi
			;;
		-s | --source-branch)
			shift
			if [ $# -gt 0 ]; then
				source_branch="$1"
			else
				print_message "" -1
				print_message "${RED}Error: No source branch specified.${NC}" -1
				exit 1
			fi
			;;
		*)
			print_message "" -1
			print_message "${RED}Unknown option:${NC} $1" -1
			exit 1
			;;
		esac
		shift
	done
}

format_diff_output() {
	echo ""
	local current_file=""
	local in_hunk=false
	local skip_next_line=false

	# Process git diff output line by line
	git diff "origin/${target_branch}..${source_branch}" | while IFS= read -r line; do
		if [[ "$line" =~ ^diff\ --git\ a/(.+)\ b/(.+)$ ]]; then
			# New file section
			if [ -n "$current_file" ]; then
				echo ""
			fi
			current_file="${BASH_REMATCH[1]}"
			echo "${BLUE}file:${NC} ${current_file}"

				# Get stats for this file
			local adds dels numstat_output
			numstat_output=$(git diff --numstat "origin/${target_branch}..${source_branch}" -- "$current_file" 2>&1)
			local numstat_exit=$?
			if [ $numstat_exit -eq 0 ]; then
				adds=$(echo "$numstat_output" | awk '{print $1}')
				dels=$(echo "$numstat_output" | awk '{print $2}')
				if [ -n "$adds" ] && [ -n "$dels" ]; then
					echo "${BLUE}stats:${NC} ${GREEN}+${adds}${NC} ${RED}-${dels}${NC}"
				fi
			fi
			echo "${BLUE}changes:${NC}"
			echo ""
			in_hunk=false
		elif [[ "$line" =~ ^index\ .* ]]; then
			# Skip index line
			continue
		elif [[ "$line" =~ ^---\  ]]; then
			# Skip --- line
			continue
		elif [[ "$line" =~ ^\+\+\+\  ]]; then
			# Skip +++ line
			continue
		elif [[ "$line" =~ ^@@ ]]; then
			# Hunk header
			in_hunk=true
			echo "   ${line}"
		elif $in_hunk; then
			# Inside a hunk, show the actual changes
			# Colorize the line
			if [[ "$line" =~ ^\+ ]]; then
				echo "   ${GREEN}${line}${NC}"
			elif [[ "$line" =~ ^- ]]; then
				echo "   ${RED}${line}${NC}"
			else
				echo "   ${line}"
			fi
		fi
	done
	echo ""
}

has_changes() {
	! git diff --quiet "origin/${target_branch}..${source_branch}" 2>&1
}

show_full_diff() {
	if has_changes; then
		# Capture and display formatted output
		local formatted_output
		formatted_output=$(format_diff_output)
		local format_exit=$?
		if [ $format_exit -ne 0 ]; then
			print_message "" -1
			print_message "${RED}Failed to format diff output. [Fail]${NC}" -1
			return 1
		fi
		echo "$formatted_output" | indent

		# Strip color codes for file/clipboard output
		local plain_content
		plain_content=$(sed $'s/\033\[[0-9;]*m//g' <<< "${formatted_output}")
		if [ $? -eq 0 ]; then
			if [ -n "$output_file" ]; then
				echo "$plain_content" > "$output_file"
				print_message "${BLUE}Formatted diff written to ${NC}${output_file}${BLUE}.${NC}"
			else
				copy_to_clipboard "$plain_content" "Formatted diff copied to clipboard."
			fi
		fi
	else
		print_message "${BLUE}No changes found between ${NC}${source_branch}${BLUE} and ${NC}${target_branch}${BLUE}.${NC}"
	fi
}

show_files_only() {
	local filenames
	if ! filenames=$(git diff --name-only "origin/${target_branch}..${source_branch}" 2>&1); then
		echo "$filenames" | indent
		print_message "" -1
		print_message "${RED}Failed to compare branches.${NC}" -1
		exit 1
	fi

	if [ -n "$filenames" ]; then
		echo "$filenames" | indent
		if [ -n "$output_file" ]; then
			echo "$filenames" > "$output_file"
			print_message "${BLUE}Filenames written to ${NC}${output_file}${BLUE}.${NC}"
		else
			copy_to_clipboard "$filenames" "Filenames copied to clipboard."
		fi
	else
		print_message "${BLUE}No changes found between ${NC}${source_branch}${BLUE} and ${NC}${target_branch}${BLUE}.${NC}"
	fi
}

show_stat_summary() {
	local result
	if ! result=$(git -c color.ui=always diff --stat "origin/${target_branch}..${source_branch}" 2>&1); then
		echo "$result" | indent
		print_message "" -1
		print_message "${RED}Failed to compare branches.${NC}" -1
		exit 1
	fi

	if [ -n "$result" ]; then
		echo "$result" | indent

		# Copy stats to clipboard (strip color codes)
		local stats_content
		if ! stats_content=$(git diff --stat "origin/${target_branch}..${source_branch}" 2>&1); then
			print_message "" -1
			print_message "${RED}Failed to generate stats for clipboard. [Fail]${NC}" -1
			return 1
		fi
		if [ -n "$output_file" ]; then
			echo "$stats_content" > "$output_file"
			print_message "${BLUE}Stats written to ${NC}${output_file}${BLUE}.${NC}"
		else
			copy_to_clipboard "$stats_content" "Stats copied to clipboard."
		fi
	else
		print_message "${BLUE}No changes found between ${NC}${source_branch}${BLUE} and ${NC}${target_branch}${BLUE}.${NC}"
	fi
}

compare_branches() {
	local step_number=1

	if [ -z "$source_branch" ]; then
		print_message "${BLUE}Source branch not specified, using current branch...${NC}" $step_number
		get_current_branch source_branch
		step_number=$((step_number + 1))
	fi

	print_message "${BLUE}Fetching latest changes for target branch ${NC}${target_branch}${BLUE}...${NC}" $step_number
	if ! fetch_changes "${target_branch}"; then
		print_message "" -1
		print_message "${RED}Failed to fetch target branch ${NC}${target_branch}${RED}.${NC}" -1
		exit 1
	fi

	step_number=$((step_number + 1))
	print_message "${BLUE}Comparing ${NC}${source_branch}${BLUE} with ${NC}${target_branch}${BLUE}...${NC}" $step_number

	if [ "$full_diff" = true ]; then
		show_full_diff
		return 0
	fi

	if [ "$files_only" = true ]; then
		show_files_only
		return 0
	fi

	show_stat_summary
}

main() {
	set_flags "$@"
	validate_dependencies git figlet lolcat
	print_banner
	check_if_target_branch_is_set $target_branch
	compare_branches
}


# Only run main if script is executed directly (not sourced)
if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
	main "$@"
	exit 0
fi
