import {
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
  type Dispatch,
  type MutableRefObject,
  type SetStateAction,
} from 'react'
import { invoke } from '@tauri-apps/api/core'
import type { GitAuthorIdentity, GitPushResult, GitRemoteStatus, ModifiedFile } from '../types'
import { trackEvent } from '../lib/telemetry'
import { isTauri, mockInvoke } from '../mock-tauri'
import { generateAutomaticCommitMessage } from '../utils/automaticCommitMessage'
import { createTranslator, type AppLocale } from '../lib/i18n'
import type { AiTarget } from '../lib/aiTargets'
import { trackCommitMessageGenerated } from '../lib/productAnalytics'
import { generateCommitMessageDraft } from '../utils/commitMessageDraft'

export type CommitMode = 'push' | 'local'

interface LocalCommitResult {
  status: 'local_only'
  message: string
}

type CommitResult = GitPushResult | LocalCommitResult
type CheckpointAction = 'commit' | 'push_only'

interface AutomaticCheckpointOptions {
  savePendingBeforeCommit?: boolean
}

interface LoadModifiedFilesOptions {
  includeStats?: boolean
}

interface CommitFlowConfig {
  aiFeaturesEnabled?: boolean
  commitMessageTarget?: AiTarget
  commitMessageTargetReady?: boolean
  savePending: () => Promise<void | boolean>
  loadModifiedFiles: () => Promise<void>
  loadModifiedFilesForVaultPath: (vaultPath: string, options?: LoadModifiedFilesOptions) => Promise<ModifiedFile[]>
  resolveRemoteStatusForVaultPath: (vaultPath: string) => Promise<GitRemoteStatus | null>
  setToastMessage: (msg: string | null) => void
  onPushRejected?: () => void
  automaticVaultPaths?: string[]
  locale?: AppLocale
  manualVaultPath?: string
  vaultPath: string
}

interface VaultPathArgs {
  vaultPath: string
}

interface CommitArgs extends VaultPathArgs {
  message: string
}

interface CommitExecutionArgs extends CommitArgs {
  commitMode: CommitMode
}

interface AutomaticCheckpointContext extends VaultPathArgs {
  remoteStatus: GitRemoteStatus | null
}

interface AutomaticCheckpointCommand extends AutomaticCheckpointContext {
  action: CheckpointAction
  message?: string
}

interface ExecutedCheckpoint {
  action: CheckpointAction
  result: CommitResult
}

interface AuthorIdentityLoaderConfig {
  authorIdentityCacheRef: MutableRefObject<Map<string, GitAuthorIdentity>>
  authorIdentityInFlightRef: MutableRefObject<Map<string, Promise<GitAuthorIdentity>>>
  authorIdentityVaultPathRef: MutableRefObject<string | null>
  setAuthorIdentity: (identity: GitAuthorIdentity | null) => void
}

interface RepositoryCheckpointResult {
  action?: CheckpointAction
  error?: unknown
  remoteStatus: GitRemoteStatus | null
  result?: CommitResult
  status: 'executed' | 'failed' | 'skipped'
  vaultPath: string
}

interface CommitMessageDraftSetters {
  setGeneratedCommitMessage: (message: string) => void
  setGeneratedCommitMessageKey: Dispatch<SetStateAction<number>>
  setGeneratingCommitMessage: (generating: boolean) => void
}

interface CommitMessageDraftActionConfig extends Pick<
  CommitFlowConfig,
  | 'aiFeaturesEnabled'
  | 'commitMessageTarget'
  | 'commitMessageTargetReady'
  | 'loadModifiedFilesForVaultPath'
  | 'manualVaultPath'
  | 'savePending'
  | 'setToastMessage'
  | 'vaultPath'
>, CommitMessageDraftSetters {
  commitMessageGenerationRef: MutableRefObject<boolean>
  t: Translator
}

type AutomaticCheckpointRunConfig = Pick<
  CommitFlowConfig,
  | 'loadModifiedFiles'
  | 'loadModifiedFilesForVaultPath'
  | 'onPushRejected'
  | 'resolveRemoteStatusForVaultPath'
  | 'setToastMessage'
  | 'vaultPath'
>

type FinalizeCheckpointConfig = Pick<
  CommitFlowConfig,
  'loadModifiedFiles' | 'resolveRemoteStatusForVaultPath' | 'setToastMessage' | 'onPushRejected'
>
type Translator = ReturnType<typeof createTranslator>

interface FinalizeCheckpointArgs extends FinalizeCheckpointConfig {
  result: CommitResult
  toastMessage: string
  vaultPaths: string[]
}

function commitModeFromRemoteStatus(remoteStatus: GitRemoteStatus | null): CommitMode {
  return remoteStatus?.hasRemote === false ? 'local' : 'push'
}

async function commitLocally({ vaultPath, message }: CommitArgs): Promise<void> {
  if (!isTauri()) {
    await mockInvoke<string>('git_commit', { vaultPath, message })
    return
  }

  await invoke<string>('git_commit', { vaultPath, message })
}

async function loadGitAuthorIdentity({ vaultPath }: VaultPathArgs): Promise<GitAuthorIdentity> {
  if (!isTauri()) {
    return mockInvoke<GitAuthorIdentity>('git_author_identity', { vaultPath })
  }

  return invoke<GitAuthorIdentity>('git_author_identity', { vaultPath })
}

function cachedGitAuthorIdentity({
  authorIdentityCacheRef,
  authorIdentityInFlightRef,
  vaultPath,
}: Pick<AuthorIdentityLoaderConfig, 'authorIdentityCacheRef' | 'authorIdentityInFlightRef'> & VaultPathArgs) {
  const cachedIdentity = authorIdentityCacheRef.current.get(vaultPath)
  if (cachedIdentity) return Promise.resolve(cachedIdentity)

  const inFlightIdentity = authorIdentityInFlightRef.current.get(vaultPath)
  if (inFlightIdentity) return inFlightIdentity

  const request = loadGitAuthorIdentity({ vaultPath }).then((identity) => {
    authorIdentityCacheRef.current.set(vaultPath, identity)
    return identity
  }).finally(() => {
    authorIdentityInFlightRef.current.delete(vaultPath)
  })
  authorIdentityInFlightRef.current.set(vaultPath, request)
  return request
}

async function pushCommittedChanges({ vaultPath }: VaultPathArgs): Promise<GitPushResult> {
  if (!isTauri()) {
    return mockInvoke<GitPushResult>('git_push', { vaultPath })
  }

  return invoke<GitPushResult>('git_push', { vaultPath })
}

async function executeCommitAction({
  vaultPath,
  message,
  commitMode,
}: CommitExecutionArgs): Promise<CommitResult> {
  await commitLocally({ vaultPath, message })
  if (commitMode === 'local') {
    return { status: 'local_only', message: 'Committed locally (no remote configured)' }
  }

  return pushCommittedChanges({ vaultPath })
}

function commitToastMessage(result: CommitResult): string {
  if (result.status === 'ok') return 'Committed and pushed'
  if (result.status === 'local_only') return result.message
  if (result.status === 'rejected') return 'Committed, but push rejected — remote has new commits. Pull first.'
  return result.message
}

function isPushRejected(result: CommitResult): boolean {
  return result.status === 'rejected'
}

function errorMessage(error: unknown): string {
  return error instanceof Error ? error.message : String(error)
}

function isMissingGitAuthorIdentityError(message: string): boolean {
  const normalized = message.toLowerCase()
  return normalized.includes('author identity unknown')
    || normalized.includes('please tell me who you are')
    || normalized.includes('unable to auto-detect email address')
    || (normalized.includes('user.email') && normalized.includes('not set'))
    || (normalized.includes('user.name') && normalized.includes('not set'))
}

function formatCommitFailureToast(error: unknown, t: Translator): string {
  const message = errorMessage(error)
  if (isMissingGitAuthorIdentityError(message)) return t('git.toast.missingAuthor')
  return t('git.toast.commitFailed', { error: message })
}

function formatAutoGitFailureToast(error: unknown, t: Translator): string {
  const message = errorMessage(error)
  if (isMissingGitAuthorIdentityError(message)) return t('git.toast.missingAuthor')
  return t('git.toast.autoGitFailed', { error: message })
}

function shouldRetryPush(remoteStatus: GitRemoteStatus | null): boolean {
  return remoteStatus?.hasRemote === true && remoteStatus.ahead > 0
}

function nothingToCommitToast(remoteStatus: GitRemoteStatus | null): string {
  return remoteStatus?.hasRemote === false ? 'Nothing to commit' : 'Nothing to commit or push'
}

function checkpointToastMessage(result: CommitResult, action: CheckpointAction): string {
  if (action === 'push_only') {
    if (result.status === 'ok') return 'Pushed committed changes'
    if (result.status === 'rejected') return 'Push rejected — remote has new commits. Pull first.'
    return result.message
  }

  return commitToastMessage(result)
}

function createAutomaticCheckpointCommand({
  remoteStatus,
  vaultPath,
  message,
}: AutomaticCheckpointContext & { message: string }): AutomaticCheckpointCommand | null {
  if (message.length > 0) {
    return { action: 'commit', remoteStatus, vaultPath, message }
  }

  if (shouldRetryPush(remoteStatus)) {
    return { action: 'push_only', remoteStatus, vaultPath }
  }

  return null
}

async function executeAutomaticCheckpoint(
  command: AutomaticCheckpointCommand,
): Promise<ExecutedCheckpoint> {
  if (command.action === 'push_only') {
    return {
      action: 'push_only',
      result: await pushCommittedChanges({ vaultPath: command.vaultPath }),
    }
  }

  const result = await executeCommitAction({
    vaultPath: command.vaultPath,
    message: command.message ?? '',
    commitMode: commitModeFromRemoteStatus(command.remoteStatus),
  })
  trackEvent('commit_made')
  return { action: 'commit', result }
}

function uniqueVaultPaths(paths: string[]): string[] {
  const seen = new Set<string>()
  return paths.filter((path) => {
    const trimmed = path.trim()
    if (!trimmed || seen.has(trimmed)) return false
    seen.add(trimmed)
    return true
  })
}

function checkpointVaultPaths({
  automaticVaultPaths,
  vaultPath,
}: Pick<CommitFlowConfig, 'automaticVaultPaths' | 'vaultPath'>): string[] {
  const configuredPaths = automaticVaultPaths && automaticVaultPaths.length > 0
    ? automaticVaultPaths
    : [vaultPath]
  const paths = uniqueVaultPaths(configuredPaths)
  return paths.length > 0 ? paths : [vaultPath]
}

async function checkpointRepository(
  vaultPath: string,
  config: Pick<CommitFlowConfig, 'loadModifiedFilesForVaultPath' | 'resolveRemoteStatusForVaultPath'>,
): Promise<RepositoryCheckpointResult> {
  const remoteStatus = await config.resolveRemoteStatusForVaultPath(vaultPath)
  const modifiedFiles = await config.loadModifiedFilesForVaultPath(vaultPath)
  const message = generateAutomaticCommitMessage(modifiedFiles)
  const command = createAutomaticCheckpointCommand({ remoteStatus, vaultPath, message })

  if (!command) {
    return { remoteStatus, status: 'skipped', vaultPath }
  }

  const { action, result } = await executeAutomaticCheckpoint(command)
  return { action, remoteStatus, result, status: 'executed', vaultPath }
}

function multiRepositoryCheckpointToast(results: RepositoryCheckpointResult[], t: Translator): string {
  const executedCount = results.filter((result) => result.status === 'executed').length
  const failedCount = results.filter((result) => result.status === 'failed').length
  const rejectedCount = results.filter((result) => result.result && isPushRejected(result.result)).length

  if (executedCount === 0) {
    const firstError = results.find((result) => result.status === 'failed')?.error
    return firstError !== undefined
      ? formatAutoGitFailureToast(firstError, t)
      : 'Nothing to commit or push'
  }

  const suffixes = []
  if (rejectedCount > 0) suffixes.push(`${rejectedCount} push rejected`)
  if (failedCount > 0) suffixes.push(`${failedCount} failed`)

  const summary = `AutoGit checkpointed ${executedCount} ${executedCount === 1 ? 'repository' : 'repositories'}`
  return suffixes.length > 0 ? `${summary}; ${suffixes.join(', ')}` : summary
}

async function runCheckpointRefresh({
  loadModifiedFiles,
  resolveRemoteStatusForVaultPath,
  vaultPaths,
}: Pick<CommitFlowConfig, 'loadModifiedFiles' | 'resolveRemoteStatusForVaultPath'> & {
  vaultPaths: string[]
}): Promise<void> {
  await loadModifiedFiles()
  await Promise.all([...new Set(vaultPaths)].map((vaultPath) => resolveRemoteStatusForVaultPath(vaultPath)))
}

async function finalizeCheckpoint(args: FinalizeCheckpointArgs): Promise<void> {
  const {
    result,
    toastMessage,
    loadModifiedFiles,
    resolveRemoteStatusForVaultPath,
    setToastMessage,
    onPushRejected,
    vaultPaths,
  } = args

  setToastMessage(toastMessage)
  if (isPushRejected(result)) {
    onPushRejected?.()
  }

  await runCheckpointRefresh({ loadModifiedFiles, resolveRemoteStatusForVaultPath, vaultPaths })
}

async function runSingleRepositoryCheckpoint(
  targetVaultPath: string,
  config: AutomaticCheckpointRunConfig,
): Promise<boolean> {
  const remoteStatus = await config.resolveRemoteStatusForVaultPath(targetVaultPath)
  const modifiedFiles = await config.loadModifiedFilesForVaultPath(targetVaultPath)
  const message = generateAutomaticCommitMessage(modifiedFiles)
  const command = createAutomaticCheckpointCommand({
    remoteStatus,
    vaultPath: targetVaultPath,
    message,
  })

  if (!command) {
    config.setToastMessage(nothingToCommitToast(remoteStatus))
    return false
  }

  const { action, result } = await executeAutomaticCheckpoint(command)
  await finalizeCheckpoint({
    result,
    toastMessage: checkpointToastMessage(result, action),
    loadModifiedFiles: config.loadModifiedFiles,
    resolveRemoteStatusForVaultPath: config.resolveRemoteStatusForVaultPath,
    setToastMessage: config.setToastMessage,
    onPushRejected: config.onPushRejected,
    vaultPaths: [targetVaultPath],
  })
  return true
}

async function checkpointRepositories(
  vaultPaths: string[],
  config: AutomaticCheckpointRunConfig,
): Promise<RepositoryCheckpointResult[]> {
  const results: RepositoryCheckpointResult[] = []
  for (const targetVaultPath of vaultPaths) {
    try {
      results.push(await checkpointRepository(targetVaultPath, config))
    } catch (error) {
      results.push({
        error,
        remoteStatus: null,
        status: 'failed',
        vaultPath: targetVaultPath,
      })
    }
  }
  return results
}

async function runMultipleRepositoryCheckpoint(
  targetVaultPaths: string[],
  config: AutomaticCheckpointRunConfig,
  t: Translator,
): Promise<boolean> {
  const results = await checkpointRepositories(targetVaultPaths, config)

  if (results.some((result) => result.result && isPushRejected(result.result))) {
    config.onPushRejected?.()
  }

  config.setToastMessage(multiRepositoryCheckpointToast(results, t))
  await runCheckpointRefresh({
    loadModifiedFiles: config.loadModifiedFiles,
    resolveRemoteStatusForVaultPath: config.resolveRemoteStatusForVaultPath,
    vaultPaths: targetVaultPaths,
  })
  return results.some((result) => result.status === 'executed' || result.status === 'failed')
}

function useAutomaticCheckpointAction({
  checkpointInFlightRef,
  savePending,
  loadModifiedFiles,
  loadModifiedFilesForVaultPath,
  resolveRemoteStatusForVaultPath,
  setToastMessage,
  onPushRejected,
  automaticVaultPaths,
  vaultPath,
  t,
}: CommitFlowConfig & {
  checkpointInFlightRef: MutableRefObject<boolean>
  t: Translator
}) {
  return useCallback(async ({
    savePendingBeforeCommit = false,
  }: AutomaticCheckpointOptions = {}): Promise<boolean> => {
    if (checkpointInFlightRef.current) return false
    checkpointInFlightRef.current = true

    try {
      if (savePendingBeforeCommit) {
        await savePending()
      }

      const targetVaultPaths = checkpointVaultPaths({ automaticVaultPaths, vaultPath })
      const runConfig = {
        loadModifiedFiles,
        loadModifiedFilesForVaultPath,
        onPushRejected,
        resolveRemoteStatusForVaultPath,
        setToastMessage,
        vaultPath,
      }
      return await (targetVaultPaths.length === 1
        ? runSingleRepositoryCheckpoint(targetVaultPaths[0], runConfig)
        : runMultipleRepositoryCheckpoint(targetVaultPaths, runConfig, t))
    } catch (err) {
      console.error('Commit failed:', err)
      setToastMessage(formatCommitFailureToast(err, t))
      return true
    } finally {
      checkpointInFlightRef.current = false
    }
  }, [
    automaticVaultPaths,
    checkpointInFlightRef,
    loadModifiedFiles,
    loadModifiedFilesForVaultPath,
    onPushRejected,
    resolveRemoteStatusForVaultPath,
    savePending,
    setToastMessage,
    t,
    vaultPath,
  ])
}

function useManualCommitPushAction({
  checkpointInFlightRef,
  savePending,
  loadModifiedFiles,
  resolveRemoteStatusForVaultPath,
  setToastMessage,
  onPushRejected,
  manualVaultPath,
  vaultPath,
  setShowCommitDialog,
  t,
}: Pick<
  CommitFlowConfig,
  | 'savePending'
  | 'loadModifiedFiles'
  | 'resolveRemoteStatusForVaultPath'
  | 'setToastMessage'
  | 'onPushRejected'
  | 'manualVaultPath'
  | 'vaultPath'
> & {
  checkpointInFlightRef: MutableRefObject<boolean>
  setShowCommitDialog: (open: boolean) => void
  t: Translator
}) {
  return useCallback(async (message: string) => {
    setShowCommitDialog(false)
    if (checkpointInFlightRef.current) return
    checkpointInFlightRef.current = true

    try {
      await savePending()
      const targetVaultPath = manualVaultPath || vaultPath
      const remoteStatus = await resolveRemoteStatusForVaultPath(targetVaultPath)
      const result = await executeCommitAction({
        vaultPath: targetVaultPath,
        message,
        commitMode: commitModeFromRemoteStatus(remoteStatus),
      })

      trackEvent('commit_made')
      await finalizeCheckpoint({
        result,
        toastMessage: commitToastMessage(result),
        loadModifiedFiles,
        resolveRemoteStatusForVaultPath,
        setToastMessage,
        onPushRejected,
        vaultPaths: [targetVaultPath],
      })
    } catch (err) {
      console.error('Commit failed:', err)
      setToastMessage(formatCommitFailureToast(err, t))
    } finally {
      checkpointInFlightRef.current = false
    }
  }, [
    checkpointInFlightRef,
    loadModifiedFiles,
    manualVaultPath,
    onPushRejected,
    resolveRemoteStatusForVaultPath,
    savePending,
    setShowCommitDialog,
    setToastMessage,
    t,
    vaultPath,
  ])
}

function draftToastKey(source: 'ai_model' | 'fallback') {
  return source === 'ai_model'
    ? 'git.commitMessage.generatedAi'
    : 'git.commitMessage.generatedFallback'
}

async function runCommitMessageDraftAction({
  aiFeaturesEnabled,
  commitMessageGenerationRef,
  commitMessageTarget,
  commitMessageTargetReady,
  loadModifiedFilesForVaultPath,
  manualVaultPath,
  savePending,
  setGeneratedCommitMessage,
  setGeneratedCommitMessageKey,
  setGeneratingCommitMessage,
  setToastMessage,
  t,
  vaultPath,
}: CommitMessageDraftActionConfig): Promise<string> {
  if (commitMessageGenerationRef.current) return ''
  commitMessageGenerationRef.current = true
  setGeneratingCommitMessage(true)

  try {
    await savePending()
    const targetVaultPath = manualVaultPath || vaultPath
    const files = await loadModifiedFilesForVaultPath(targetVaultPath, { includeStats: true })
    if (files.length === 0) {
      setToastMessage(t('git.commitMessage.noChanges'))
      return ''
    }

    const result = await generateCommitMessageDraft({
      aiFeaturesEnabled,
      files,
      target: commitMessageTarget,
      targetReady: commitMessageTargetReady,
    })
    if (!result.message) {
      setToastMessage(t('git.commitMessage.noChanges'))
      return ''
    }

    setGeneratedCommitMessage(result.message)
    setGeneratedCommitMessageKey((key) => key + 1)
    trackCommitMessageGenerated({
      aiAttempted: result.aiAttempted,
      fileCount: result.fileCount,
      source: result.source,
    })
    setToastMessage(t(draftToastKey(result.source)))
    return result.message
  } catch (err) {
    console.error('Commit message generation failed:', err)
    setToastMessage(t('git.commitMessage.failed'))
    return ''
  } finally {
    commitMessageGenerationRef.current = false
    setGeneratingCommitMessage(false)
  }
}

function useCommitMessageDraftAction(config: CommitMessageDraftActionConfig) {
  const {
    aiFeaturesEnabled,
    commitMessageGenerationRef,
    commitMessageTarget,
    commitMessageTargetReady,
    loadModifiedFilesForVaultPath,
    manualVaultPath,
    savePending,
    setGeneratedCommitMessage,
    setGeneratedCommitMessageKey,
    setGeneratingCommitMessage,
    setToastMessage,
    t,
    vaultPath,
  } = config

  return useCallback(() => runCommitMessageDraftAction({
    aiFeaturesEnabled,
    commitMessageGenerationRef,
    commitMessageTarget,
    commitMessageTargetReady,
    loadModifiedFilesForVaultPath,
    manualVaultPath,
    savePending,
    setGeneratedCommitMessage,
    setGeneratedCommitMessageKey,
    setGeneratingCommitMessage,
    setToastMessage,
    t,
    vaultPath,
  }), [
    aiFeaturesEnabled,
    commitMessageGenerationRef,
    commitMessageTarget,
    commitMessageTargetReady,
    loadModifiedFilesForVaultPath,
    manualVaultPath,
    savePending,
    setGeneratedCommitMessage,
    setGeneratedCommitMessageKey,
    setGeneratingCommitMessage,
    setToastMessage,
    t,
    vaultPath,
  ])
}

function useCommitModeRefresh({
  commitModeVaultPathRef,
  loadAuthorIdentityForVaultPath,
  manualVaultPath,
  resolveRemoteStatusForVaultPath,
  setCommitMode,
  showCommitDialog,
  vaultPath,
}: Pick<
  CommitFlowConfig,
  'manualVaultPath' | 'resolveRemoteStatusForVaultPath' | 'vaultPath'
> & {
  commitModeVaultPathRef: MutableRefObject<string | null>
  loadAuthorIdentityForVaultPath: (vaultPath: string) => void
  setCommitMode: (mode: CommitMode) => void
  showCommitDialog: boolean
}) {
  useEffect(() => {
    if (!showCommitDialog) return

    let cancelled = false
    const targetVaultPath = manualVaultPath || vaultPath
    loadAuthorIdentityForVaultPath(targetVaultPath)
    if (commitModeVaultPathRef.current === targetVaultPath) return

    void resolveRemoteStatusForVaultPath(targetVaultPath).then((remoteStatus) => {
      if (cancelled) return
      commitModeVaultPathRef.current = targetVaultPath
      setCommitMode(commitModeFromRemoteStatus(remoteStatus))
    })

    return () => {
      cancelled = true
    }
  }, [
    commitModeVaultPathRef,
    loadAuthorIdentityForVaultPath,
    manualVaultPath,
    resolveRemoteStatusForVaultPath,
    setCommitMode,
    showCommitDialog,
    vaultPath,
  ])
}

function useOpenCommitDialog({
  dialogOpeningRef,
  commitModeVaultPathRef,
  loadAuthorIdentityForVaultPath,
  loadModifiedFiles,
  manualVaultPath,
  resolveRemoteStatusForVaultPath,
  savePending,
  setCommitMode,
  setDialogOpening,
  setShowCommitDialog,
  setToastMessage,
  vaultPath,
}: Pick<
  CommitFlowConfig,
  | 'loadModifiedFiles'
  | 'manualVaultPath'
  | 'resolveRemoteStatusForVaultPath'
  | 'savePending'
  | 'setToastMessage'
  | 'vaultPath'
> & {
  dialogOpeningRef: MutableRefObject<boolean>
  commitModeVaultPathRef: MutableRefObject<string | null>
  loadAuthorIdentityForVaultPath: (vaultPath: string) => void
  setCommitMode: (mode: CommitMode) => void
  setDialogOpening: (opening: boolean) => void
  setShowCommitDialog: (open: boolean) => void
}) {
  return useCallback(async () => {
    if (dialogOpeningRef.current) return
    dialogOpeningRef.current = true
    setDialogOpening(true)

    try {
      await savePending()
      await loadModifiedFiles()
      const targetVaultPath = manualVaultPath || vaultPath
      const remoteStatus = await resolveRemoteStatusForVaultPath(targetVaultPath)
      commitModeVaultPathRef.current = targetVaultPath
      setCommitMode(commitModeFromRemoteStatus(remoteStatus))
      setShowCommitDialog(true)
      loadAuthorIdentityForVaultPath(targetVaultPath)
    } catch (err) {
      console.error('Commit dialog failed:', err)
      setToastMessage(`Commit dialog failed: ${errorMessage(err)}`)
    } finally {
      dialogOpeningRef.current = false
      setDialogOpening(false)
    }
  }, [
    commitModeVaultPathRef,
    dialogOpeningRef,
    loadAuthorIdentityForVaultPath,
    loadModifiedFiles,
    manualVaultPath,
    resolveRemoteStatusForVaultPath,
    savePending,
    setCommitMode,
    setDialogOpening,
    setShowCommitDialog,
    setToastMessage,
    vaultPath,
  ])
}

/** Manages the commit dialog state and the save→commit→push/local flow. */
export function useCommitFlow({
  aiFeaturesEnabled,
  commitMessageTarget,
  commitMessageTargetReady,
  savePending,
  loadModifiedFiles,
  loadModifiedFilesForVaultPath,
  resolveRemoteStatusForVaultPath,
  setToastMessage,
  onPushRejected,
  automaticVaultPaths,
  locale,
  manualVaultPath,
  vaultPath,
}: CommitFlowConfig) {
  const [showCommitDialog, setShowCommitDialog] = useState(false)
  const [commitMode, setCommitMode] = useState<CommitMode>('push')
  const [authorIdentity, setAuthorIdentity] = useState<GitAuthorIdentity | null>(null)
  const [isOpeningCommitDialog, setOpeningCommitDialog] = useState(false)
  const [generatedCommitMessage, setGeneratedCommitMessage] = useState('')
  const [generatedCommitMessageKey, setGeneratedCommitMessageKey] = useState(0)
  const [isGeneratingCommitMessage, setGeneratingCommitMessage] = useState(false)
  const checkpointInFlightRef = useRef(false)
  const dialogOpeningRef = useRef(false)
  const commitMessageGenerationRef = useRef(false)
  const commitModeVaultPathRef = useRef<string | null>(null)
  const authorIdentityCacheRef = useRef(new Map<string, GitAuthorIdentity>())
  const authorIdentityInFlightRef = useRef(new Map<string, Promise<GitAuthorIdentity>>())
  const authorIdentityVaultPathRef = useRef<string | null>(null)
  const t = useMemo(() => createTranslator(locale), [locale])
  const loadAuthorIdentityForVaultPath = useCallback((targetVaultPath: string) => {
    authorIdentityVaultPathRef.current = targetVaultPath

    const cachedIdentity = authorIdentityCacheRef.current.get(targetVaultPath)
    setAuthorIdentity(cachedIdentity ?? null)
    if (cachedIdentity) return

    void cachedGitAuthorIdentity({
      authorIdentityCacheRef,
      authorIdentityInFlightRef,
      vaultPath: targetVaultPath,
    }).then((identity) => {
      if (authorIdentityVaultPathRef.current === targetVaultPath) setAuthorIdentity(identity)
    }).catch((err) => {
      console.error('Git author identity failed:', err)
      if (authorIdentityVaultPathRef.current === targetVaultPath) setAuthorIdentity(null)
    })
  }, [])

  const openCommitDialog = useOpenCommitDialog({
    dialogOpeningRef,
    commitModeVaultPathRef,
    loadAuthorIdentityForVaultPath,
    loadModifiedFiles,
    manualVaultPath,
    resolveRemoteStatusForVaultPath,
    savePending,
    setCommitMode,
    setDialogOpening: setOpeningCommitDialog,
    setShowCommitDialog,
    setToastMessage,
    vaultPath,
  })

  const runAutomaticCheckpoint = useAutomaticCheckpointAction({
    checkpointInFlightRef,
    savePending,
    loadModifiedFiles,
    loadModifiedFilesForVaultPath,
    resolveRemoteStatusForVaultPath,
    setToastMessage,
    onPushRejected,
    automaticVaultPaths,
    vaultPath,
    t,
  })

  const handleCommitPush = useManualCommitPushAction({
    checkpointInFlightRef,
    savePending,
    loadModifiedFiles,
    resolveRemoteStatusForVaultPath,
    setToastMessage,
    onPushRejected,
    manualVaultPath,
    vaultPath,
    setShowCommitDialog,
    t,
  })

  const generateCommitMessageForDialog = useCommitMessageDraftAction({
    aiFeaturesEnabled,
    commitMessageGenerationRef,
    commitMessageTarget,
    commitMessageTargetReady,
    loadModifiedFilesForVaultPath,
    manualVaultPath,
    savePending,
    setGeneratedCommitMessage,
    setGeneratedCommitMessageKey,
    setGeneratingCommitMessage,
    setToastMessage,
    t,
    vaultPath,
  })

  const openCommitDialogWithGeneratedMessage = useCallback(async () => {
    await openCommitDialog()
    await generateCommitMessageForDialog()
  }, [generateCommitMessageForDialog, openCommitDialog])

  useCommitModeRefresh({
    commitModeVaultPathRef,
    loadAuthorIdentityForVaultPath,
    manualVaultPath,
    resolveRemoteStatusForVaultPath,
    setCommitMode,
    showCommitDialog,
    vaultPath,
  })

  const closeCommitDialog = useCallback(() => setShowCommitDialog(false), [])

  return {
    showCommitDialog,
    commitMode,
    authorIdentity,
    isOpeningCommitDialog,
    generatedCommitMessage,
    generatedCommitMessageKey,
    isGeneratingCommitMessage,
    openCommitDialog,
    openCommitDialogWithGeneratedMessage,
    generateCommitMessageForDialog,
    handleCommitPush,
    closeCommitDialog,
    runAutomaticCheckpoint,
  }
}
