import { useCallback, useEffect, useRef, useState } from 'react';
import { api } from '@/api/client';
import type { GitDiffMode, GitDiffResult, GitDiscardFileInfo } from '@/api/types';
import { sessionEvents } from './sessionEvents';

export function useGitDiff(projectId: string | null | undefined, hideWhitespace = false) {
  const [mode, setMode] = useState<GitDiffMode>('uncommitted');
  const [pinned, setPinned] = useState(false);
  const [result, setResult] = useState<GitDiffResult | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  // FIX 4: non-null when user has pinned a mode that differs from the server's auto-selected mode.
  const [modeSuggestion, setModeSuggestion] = useState<GitDiffMode | null>(null);

  // Coalescence guard: absorb concurrent refresh triggers into the running request.
  const inFlightRef = useRef(false);

  const refresh = useCallback(() => {
    if (!projectId || inFlightRef.current) return;
    inFlightRef.current = true;
    setLoading(true);
    setError(null);
    // FIX 1: when not pinned, omit mode param so the server auto-selects based on
    // dirty state (dirty → uncommitted, clean → committed).
    api.projects
      .gitDiff(projectId, pinned ? mode : null, hideWhitespace)
      .then((r) => {
        if (!pinned) {
          setMode(r.mode);
        }
        // FIX 4: if pinned and the server's auto-selected mode differs, surface a suggestion.
        if (pinned && r.auto_mode && r.auto_mode !== mode) {
          setModeSuggestion(r.auto_mode);
        } else {
          setModeSuggestion(null);
        }
        setResult(r);
      })
      .catch((err: unknown) => {
        setError(err instanceof Error ? err.message : 'Failed to load diff');
      })
      .finally(() => {
        inFlightRef.current = false;
        setLoading(false);
      });
  }, [projectId, mode, pinned, hideWhitespace]);

  // Re-run refresh when mode changes (user pinned a new mode).
  useEffect(() => {
    if (!projectId) {
      setResult(null);
      return;
    }
    refresh();
  }, [projectId, mode, hideWhitespace]); // eslint-disable-line react-hooks/exhaustive-deps

  // Subscribe to git_diff_refresh events (tab open, message_complete, manual).
  useEffect(() => {
    return sessionEvents.subscribe((event) => {
      if (event.type === 'git_diff_refresh') refresh();
    });
  }, [refresh]);

  const selectMode = useCallback((m: GitDiffMode) => {
    setPinned(true);
    setMode(m);
    setModeSuggestion(null); // FIX 4: clear suggestion on explicit mode pick
  }, []);

  const [mutating, setMutating] = useState(false);
  const [mutateError, setMutateError] = useState<string | null>(null);

  const runMutation = useCallback(
    async (fn: () => Promise<unknown>): Promise<boolean> => {
      if (!projectId) return false;
      setMutating(true);
      setMutateError(null);
      try {
        await fn();
        sessionEvents.emit({ type: 'git_diff_refresh' });
        return true;
      } catch (err) {
        setMutateError(err instanceof Error ? err.message : 'Operation failed');
        return false;
      } finally {
        setMutating(false);
      }
    },
    [projectId],
  );

  const stage = useCallback(
    (files: string[]) => runMutation(() => api.projects.gitStage(projectId!, files)),
    [projectId, runMutation],
  );

  const unstage = useCallback(
    (files: string[]) => runMutation(() => api.projects.gitUnstage(projectId!, files)),
    [projectId, runMutation],
  );

  const commit = useCallback(
    (message: string, files?: string[]) =>
      runMutation(() => api.projects.gitCommit(projectId!, { message, files })),
    [projectId, runMutation],
  );

  const discard = useCallback(
    (files: GitDiscardFileInfo[]) => runMutation(() => api.projects.gitDiscard(projectId!, files)),
    [projectId, runMutation],
  );

  return { result, loading, error, mode, selectMode, refresh, mutating, mutateError, stage, unstage, commit, discard, modeSuggestion };
}
