/** Unified hook for git repository operations.
 *
 * Centralizes branch fetching, status, history, and git actions
 * so components don't duplicate this logic.
 */

import { useState, useEffect, useCallback } from "react";
import {
	listRepositoryBranches,
	getRepositoryStatus,
	getRepositoryHistory,
	getCommitDetail,
	commitChanges,
	pushRepository,
	pullRepository,
	fetchRepository,
	checkoutBranch,
	createBranch,
	deleteBranch,
	mergeBranches,
	type Branch,
	type CommitHistoryResponse,
	type CommitDetail,
} from "../api/git-repositories";

export interface GitStatus {
	branch: string;
	modified: string[];
	added: string[];
	deleted: string[];
	untracked: string[];
	renamed: string[];
	ahead: number;
	behind: number;
}

export interface UseGitRepoResult {
	/** Available branch names. */
	branches: string[];
	/** The repo's default branch. */
	defaultBranch: string;
	/** Current working-directory status. */
	status: GitStatus | null;
	/** Commit history. */
	history: CommitHistoryResponse | null;
	/** Selected commit detail. */
	commitDetail: CommitDetail | null;
	/** True while any async operation is in flight. */
	loading: boolean;
	/** Error message from the last failed operation. */
	error: string | null;
	/** Refresh branches list. */
	refreshBranches: () => Promise<void>;
	/** Refresh working-directory status. */
	refreshStatus: () => Promise<void>;
	/** Refresh commit history. */
	refreshHistory: (branch?: string, limit?: number) => Promise<void>;
	/** Fetch a single commit's details. */
	loadCommitDetail: (hash: string) => Promise<void>;
	/** Stage + commit changes. */
	commit: (message: string, files?: string[]) => Promise<void>;
	/** Push current branch (or named branch) to remote. */
	push: (branch?: string) => Promise<void>;
	/** Pull from remote. */
	pull: (branch?: string) => Promise<void>;
	/** Fetch from remote. */
	fetch: () => Promise<void>;
	/** Checkout an existing branch. */
	checkout: (branch: string) => Promise<void>;
	/** Create and checkout a new branch. */
	createBranch: (name: string, baseBranch?: string) => Promise<void>;
	/** Delete a branch. */
	deleteBranch: (name: string, force?: boolean) => Promise<void>;
	/** Merge source into current (or target) branch. */
	merge: (
		sourceBranch: string,
		targetBranch?: string,
		message?: string,
	) => Promise<void>;
	/** Clear the current error. */
	clearError: () => void;
}

export function useGitRepo(
	projectId: string | undefined,
	repoId: string | undefined,
): UseGitRepoResult {
	const [branches, setBranches] = useState<string[]>([]);
	const [defaultBranch, setDefaultBranch] = useState("");
	const [status, setStatus] = useState<GitStatus | null>(null);
	const [history, setHistory] = useState<CommitHistoryResponse | null>(null);
	const [commitDetail, setCommitDetail] = useState<CommitDetail | null>(null);
	const [loading, setLoading] = useState(false);
	const [error, setError] = useState<string | null>(null);

	const extractError = (err: unknown): string => {
		if (typeof err === "object" && err !== null) {
			const e = err as Record<string, unknown>;
			const response = e.response as Record<string, unknown> | undefined;
			const data = response?.data as Record<string, unknown> | undefined;
			if (typeof data?.detail === "string") return data.detail;
			if (typeof data?.message === "string") return data.message;
			if (typeof e.message === "string") return e.message;
		}
		return "Git operation failed";
	};

	const withLoading = useCallback(
		async <T>(fn: () => Promise<T>): Promise<T> => {
			setLoading(true);
			setError(null);
			try {
				return await fn();
			} catch (err) {
				setError(extractError(err));
				throw err;
			} finally {
				setLoading(false);
			}
		},
		[],
	);

	const refreshBranches = useCallback(async () => {
		if (!projectId || !repoId) return;
		const data = await withLoading(() =>
			listRepositoryBranches(projectId, repoId),
		);
		setBranches(data.branches.map((b: Branch) => b.name));
		setDefaultBranch(data.default_branch ?? "");
	}, [projectId, repoId, withLoading]);

	const refreshStatus = useCallback(async () => {
		if (!projectId || !repoId) return;
		const data = await withLoading(() =>
			getRepositoryStatus(projectId, repoId),
		);
		setStatus({
			branch: data.branch,
			modified: data.modified,
			added: data.added,
			deleted: data.deleted,
			untracked: data.untracked,
			renamed: data.renamed ?? [],
			ahead: data.ahead,
			behind: data.behind,
		});
	}, [projectId, repoId, withLoading]);

	const refreshHistory = useCallback(
		async (branch?: string, limit = 50) => {
			if (!projectId || !repoId) return;
			const data = await withLoading(() =>
				getRepositoryHistory(projectId, repoId, branch, limit),
			);
			setHistory(data);
		},
		[projectId, repoId, withLoading],
	);

	const loadCommitDetail = useCallback(
		async (hash: string) => {
			if (!projectId || !repoId) return;
			const data = await withLoading(() =>
				getCommitDetail(projectId, repoId, hash),
			);
			setCommitDetail(data);
		},
		[projectId, repoId, withLoading],
	);

	const commit = useCallback(
		async (message: string, files?: string[]) => {
			if (!projectId || !repoId) return;
			await withLoading(() => commitChanges(projectId, repoId, message, files));
			await refreshStatus();
		},
		[projectId, repoId, withLoading, refreshStatus],
	);

	const push = useCallback(
		async (branch?: string) => {
			if (!projectId || !repoId) return;
			await withLoading(() => pushRepository(projectId, repoId, branch));
			await refreshStatus();
		},
		[projectId, repoId, withLoading, refreshStatus],
	);

	const pull = useCallback(
		async (branch?: string) => {
			if (!projectId || !repoId) return;
			await withLoading(() => pullRepository(projectId, repoId, branch));
			await refreshStatus();
		},
		[projectId, repoId, withLoading, refreshStatus],
	);

	const fetch = useCallback(async () => {
		if (!projectId || !repoId) return;
		await withLoading(() => fetchRepository(projectId, repoId));
		await refreshStatus();
	}, [projectId, repoId, withLoading, refreshStatus]);

	const checkout = useCallback(
		async (branch: string) => {
			if (!projectId || !repoId) return;
			await withLoading(() => checkoutBranch(projectId, repoId, branch));
			await refreshStatus();
			await refreshBranches();
		},
		[projectId, repoId, withLoading, refreshStatus, refreshBranches],
	);

	const createBranchFn = useCallback(
		async (name: string, baseBranch = "HEAD") => {
			if (!projectId || !repoId) return;
			await withLoading(() =>
				createBranch(projectId, repoId, name, baseBranch),
			);
			await refreshBranches();
			await refreshStatus();
		},
		[projectId, repoId, withLoading, refreshBranches, refreshStatus],
	);

	const deleteBranchFn = useCallback(
		async (name: string, force = false) => {
			if (!projectId || !repoId) return;
			await withLoading(() => deleteBranch(projectId, repoId, name, force));
			await refreshBranches();
		},
		[projectId, repoId, withLoading, refreshBranches],
	);

	const merge = useCallback(
		async (sourceBranch: string, targetBranch?: string, message?: string) => {
			if (!projectId || !repoId) return;
			await withLoading(() =>
				mergeBranches(projectId, repoId, sourceBranch, targetBranch, message),
			);
			await refreshStatus();
			await refreshHistory();
		},
		[projectId, repoId, withLoading, refreshStatus, refreshHistory],
	);

	// Auto-refresh branches when projectId/repoId become valid
	useEffect(() => {
		if (projectId && repoId) {
			void refreshBranches();
		} else {
			setBranches([]);
			setDefaultBranch("");
		}
	}, [projectId, repoId, refreshBranches]);

	return {
		branches,
		defaultBranch,
		status,
		history,
		commitDetail,
		loading,
		error,
		refreshBranches,
		refreshStatus,
		refreshHistory,
		loadCommitDetail,
		commit,
		push,
		pull,
		fetch,
		checkout,
		createBranch: createBranchFn,
		deleteBranch: deleteBranchFn,
		merge,
		clearError: () => setError(null),
	};
}
