import { useState, useEffect, useMemo, useCallback, useRef } from 'react';
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Textarea } from '@/components/ui/textarea';
import { Label } from '@/components/ui/label';
import { Checkbox } from '@/components/ui/checkbox';
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '@/components/ui/select';
import {
  GitCommit,
  GitMerge,
  Sparkles,
  FilePlus,
  FileX,
  FilePen,
  FileText,
  File,
  ChevronDown,
  ChevronRight,
  Upload,
  RefreshCw,
} from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { getElectronAPI } from '@/lib/electron';
import { getHttpApiClient } from '@/lib/http-api-client';
import { toast } from 'sonner';
import { useAppStore } from '@/store/app-store';
import { resolveModelString } from '@automaker/model-resolver';
import { cn } from '@/lib/utils';
import { TruncatedFilePath } from '@/components/ui/truncated-file-path';
import { ModelOverrideTrigger, useModelOverride } from '@/components/shared';
import type { FileStatus, MergeStateInfo } from '@/types/electron';
import { parseDiff, type ParsedFileDiff } from '@/lib/diff-utils';

interface RemoteInfo {
  name: string;
  url: string;
}

interface WorktreeInfo {
  path: string;
  branch: string;
  isMain: boolean;
  hasChanges?: boolean;
  changedFilesCount?: number;
}

interface CommitWorktreeDialogProps {
  open: boolean;
  onOpenChange: (open: boolean) => void;
  worktree: WorktreeInfo | null;
  onCommitted: () => void;
}

const getFileIcon = (status: string) => {
  switch (status) {
    case 'A':
    case '?':
      return <FilePlus className="w-3.5 h-3.5 text-green-500 flex-shrink-0" />;
    case 'D':
      return <FileX className="w-3.5 h-3.5 text-red-500 flex-shrink-0" />;
    case 'M':
    case 'U':
      return <FilePen className="w-3.5 h-3.5 text-amber-500 flex-shrink-0" />;
    case 'R':
    case 'C':
      return <File className="w-3.5 h-3.5 text-blue-500 flex-shrink-0" />;
    default:
      return <FileText className="w-3.5 h-3.5 text-muted-foreground flex-shrink-0" />;
  }
};

const getStatusLabel = (status: string) => {
  switch (status) {
    case 'A':
      return 'Added';
    case '?':
      return 'Untracked';
    case 'D':
      return 'Deleted';
    case 'M':
      return 'Modified';
    case 'U':
      return 'Updated';
    case 'R':
      return 'Renamed';
    case 'C':
      return 'Copied';
    default:
      return 'Changed';
  }
};

const getStatusBadgeColor = (status: string) => {
  switch (status) {
    case 'A':
    case '?':
      return 'bg-green-500/20 text-green-400 border-green-500/30';
    case 'D':
      return 'bg-red-500/20 text-red-400 border-red-500/30';
    case 'M':
    case 'U':
      return 'bg-amber-500/20 text-amber-400 border-amber-500/30';
    case 'R':
    case 'C':
      return 'bg-blue-500/20 text-blue-400 border-blue-500/30';
    default:
      return 'bg-muted text-muted-foreground border-border';
  }
};

const getMergeTypeLabel = (mergeType?: string) => {
  switch (mergeType) {
    case 'both-modified':
      return 'Both Modified';
    case 'added-by-us':
      return 'Added by Us';
    case 'added-by-them':
      return 'Added by Them';
    case 'deleted-by-us':
      return 'Deleted by Us';
    case 'deleted-by-them':
      return 'Deleted by Them';
    case 'both-added':
      return 'Both Added';
    case 'both-deleted':
      return 'Both Deleted';
    default:
      return 'Merge';
  }
};

function DiffLine({
  type,
  content,
  lineNumber,
}: {
  type: 'context' | 'addition' | 'deletion' | 'header';
  content: string;
  lineNumber?: { old?: number; new?: number };
}) {
  const bgClass = {
    context: 'bg-transparent',
    addition: 'bg-green-500/10',
    deletion: 'bg-red-500/10',
    header: 'bg-blue-500/10',
  };

  const textClass = {
    context: 'text-foreground-secondary',
    addition: 'text-green-400',
    deletion: 'text-red-400',
    header: 'text-blue-400',
  };

  const prefix = {
    context: ' ',
    addition: '+',
    deletion: '-',
    header: '',
  };

  if (type === 'header') {
    return (
      <div className={cn('px-2 py-1 font-mono text-xs', bgClass[type], textClass[type])}>
        {content}
      </div>
    );
  }

  return (
    <div className={cn('flex font-mono text-xs', bgClass[type])}>
      <span className="w-10 flex-shrink-0 text-right pr-1.5 text-muted-foreground select-none border-r border-border-glass text-[10px]">
        {lineNumber?.old ?? ''}
      </span>
      <span className="w-10 flex-shrink-0 text-right pr-1.5 text-muted-foreground select-none border-r border-border-glass text-[10px]">
        {lineNumber?.new ?? ''}
      </span>
      <span className={cn('w-4 flex-shrink-0 text-center select-none', textClass[type])}>
        {prefix[type]}
      </span>
      <span className={cn('flex-1 px-1.5 whitespace-pre-wrap break-all', textClass[type])}>
        {content || '\u00A0'}
      </span>
    </div>
  );
}

export function CommitWorktreeDialog({
  open,
  onOpenChange,
  worktree,
  onCommitted,
}: CommitWorktreeDialogProps) {
  const [message, setMessage] = useState('');
  const [isLoading, setIsLoading] = useState(false);
  const [isGenerating, setIsGenerating] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const enableAiCommitMessages = useAppStore((state) => state.enableAiCommitMessages);

  // Commit message model override
  const commitModelOverride = useModelOverride({ phase: 'commitMessageModel' });
  const { effectiveModel: commitEffectiveModel, effectiveModelEntry: commitEffectiveModelEntry } =
    commitModelOverride;

  // File selection state
  const [files, setFiles] = useState<FileStatus[]>([]);
  const [diffContent, setDiffContent] = useState('');
  const [selectedFiles, setSelectedFiles] = useState<Set<string>>(new Set());
  const [expandedFile, setExpandedFile] = useState<string | null>(null);
  const [isLoadingDiffs, setIsLoadingDiffs] = useState(false);
  const [mergeState, setMergeState] = useState<MergeStateInfo | undefined>(undefined);

  // Push after commit state
  const [pushAfterCommit, setPushAfterCommit] = useState(false);
  const [remotes, setRemotes] = useState<RemoteInfo[]>([]);
  const [selectedRemote, setSelectedRemote] = useState<string>('');
  const [isLoadingRemotes, setIsLoadingRemotes] = useState(false);
  const [isPushing, setIsPushing] = useState(false);
  const [remotesFetched, setRemotesFetched] = useState(false);
  const [remotesFetchError, setRemotesFetchError] = useState<string | null>(null);
  // Track whether the commit already succeeded so retries can skip straight to push
  const [commitSucceeded, setCommitSucceeded] = useState(false);

  // Parse diffs
  const parsedDiffs = useMemo(() => parseDiff(diffContent), [diffContent]);

  // Create a map of file path to parsed diff for quick lookup
  const diffsByFile = useMemo(() => {
    const map = new Map<string, ParsedFileDiff>();
    for (const diff of parsedDiffs) {
      map.set(diff.filePath, diff);
    }
    return map;
  }, [parsedDiffs]);

  // Fetch remotes when push option is enabled
  const fetchRemotesForWorktree = useCallback(
    async (worktreePath: string, signal?: { cancelled: boolean }) => {
      setIsLoadingRemotes(true);
      setRemotesFetchError(null);
      try {
        const api = getElectronAPI();
        if (api?.worktree?.listRemotes) {
          const result = await api.worktree.listRemotes(worktreePath);
          if (signal?.cancelled) return;
          setRemotesFetched(true);
          if (result.success && result.result) {
            const remoteInfos = result.result.remotes.map((r) => ({
              name: r.name,
              url: r.url,
            }));
            setRemotes(remoteInfos);
            // Auto-select 'origin' if available, otherwise first remote
            if (remoteInfos.length > 0) {
              const defaultRemote = remoteInfos.find((r) => r.name === 'origin') || remoteInfos[0];
              setSelectedRemote(defaultRemote.name);
            }
          }
        } else {
          // API not available — mark fetch as complete with an error so the UI
          // shows feedback instead of remaining in an empty/loading state.
          setRemotesFetchError('Remote listing not available');
          setRemotesFetched(true);
          return;
        }
      } catch (err) {
        if (signal?.cancelled) return;
        // Don't mark as successfully fetched — show an error with retry instead
        setRemotesFetchError(err instanceof Error ? err.message : 'Failed to fetch remotes');
        console.warn('Failed to fetch remotes:', err);
      } finally {
        if (!signal?.cancelled) setIsLoadingRemotes(false);
      }
    },
    []
  );

  useEffect(() => {
    if (pushAfterCommit && worktree && !remotesFetched && !remotesFetchError) {
      const signal = { cancelled: false };
      fetchRemotesForWorktree(worktree.path, signal);
      return () => {
        signal.cancelled = true;
      };
    }
  }, [pushAfterCommit, worktree, remotesFetched, remotesFetchError, fetchRemotesForWorktree]);

  // Load diffs when dialog opens
  useEffect(() => {
    if (open && worktree) {
      setIsLoadingDiffs(true);
      setFiles([]);
      setDiffContent('');
      setSelectedFiles(new Set());
      setExpandedFile(null);
      setMergeState(undefined);
      // Reset push state
      setPushAfterCommit(false);
      setRemotes([]);
      setSelectedRemote('');
      setIsPushing(false);
      setRemotesFetched(false);
      setRemotesFetchError(null);
      setCommitSucceeded(false);

      let cancelled = false;

      const loadDiffs = async () => {
        try {
          const api = getElectronAPI();
          if (api?.git?.getDiffs) {
            const result = await api.git.getDiffs(worktree.path);
            if (result.success) {
              const fileList = result.files ?? [];
              // Sort merge-affected files first when a merge is in progress
              if (result.mergeState?.isMerging) {
                const mergeSet = new Set(result.mergeState.mergeAffectedFiles);
                fileList.sort((a, b) => {
                  const aIsMerge = mergeSet.has(a.path) || (a.isMergeAffected ?? false);
                  const bIsMerge = mergeSet.has(b.path) || (b.isMergeAffected ?? false);
                  if (aIsMerge && !bIsMerge) return -1;
                  if (!aIsMerge && bIsMerge) return 1;
                  return 0;
                });
              }
              if (!cancelled) setFiles(fileList);
              if (!cancelled) setDiffContent(result.diff ?? '');
              if (!cancelled) setMergeState(result.mergeState);
              // If any files are already staged, pre-select only staged files
              // Otherwise select all files by default
              const stagedFiles = fileList.filter((f) => {
                const idx = f.indexStatus ?? ' ';
                return idx !== ' ' && idx !== '?';
              });
              if (!cancelled) {
                if (stagedFiles.length > 0) {
                  // Also include untracked files that are staged (A status)
                  setSelectedFiles(new Set(stagedFiles.map((f) => f.path)));
                } else {
                  setSelectedFiles(new Set(fileList.map((f) => f.path)));
                }
              }
            } else {
              const errorMsg = result.error ?? 'Failed to load diffs';
              console.warn('Failed to load diffs for commit dialog:', errorMsg);
              if (!cancelled) {
                setError(errorMsg);
                toast.error(errorMsg);
              }
            }
          }
        } catch (err) {
          console.error('Failed to load diffs for commit dialog:', err);
          if (!cancelled) {
            const errorMsg = err instanceof Error ? err.message : 'Failed to load diffs';
            setError(errorMsg);
            toast.error(errorMsg);
          }
        } finally {
          if (!cancelled) setIsLoadingDiffs(false);
        }
      };

      loadDiffs();

      return () => {
        cancelled = true;
      };
    }
  }, [open, worktree]);

  const handleToggleFile = useCallback((filePath: string) => {
    setSelectedFiles((prev) => {
      const next = new Set(prev);
      if (next.has(filePath)) {
        next.delete(filePath);
      } else {
        next.add(filePath);
      }
      return next;
    });
  }, []);

  const handleToggleAll = useCallback(() => {
    setSelectedFiles((prev) => {
      if (prev.size === files.length) {
        return new Set();
      }
      return new Set(files.map((f) => f.path));
    });
  }, [files]);

  const handleFileClick = useCallback((filePath: string) => {
    setExpandedFile((prev) => (prev === filePath ? null : filePath));
  }, []);

  /** Shared push helper — returns true if the push succeeded */
  const performPush = async (
    api: ReturnType<typeof getElectronAPI>,
    worktreePath: string,
    remoteName: string
  ): Promise<boolean> => {
    if (!api?.worktree?.push) {
      toast.error('Push API not available');
      return false;
    }
    setIsPushing(true);
    try {
      const pushResult = await api.worktree.push(worktreePath, false, remoteName);
      if (pushResult.success && pushResult.result) {
        toast.success('Pushed to remote', {
          description: pushResult.result.message,
        });
        return true;
      } else {
        toast.error(pushResult.error || 'Failed to push to remote');
        return false;
      }
    } catch (pushErr) {
      toast.error(pushErr instanceof Error ? pushErr.message : 'Failed to push to remote');
      return false;
    } finally {
      setIsPushing(false);
    }
  };

  const handleCommit = async () => {
    if (!worktree) return;

    const api = getElectronAPI();

    // If commit already succeeded on a previous attempt, skip straight to push (or close if no push needed)
    if (commitSucceeded) {
      if (pushAfterCommit && selectedRemote) {
        const ok = await performPush(api, worktree.path, selectedRemote);
        if (ok) {
          onCommitted();
          onOpenChange(false);
          setMessage('');
        }
      } else {
        onCommitted();
        onOpenChange(false);
        setMessage('');
      }
      return;
    }

    if (!message.trim() || selectedFiles.size === 0) return;

    setIsLoading(true);
    setError(null);

    try {
      if (!api?.worktree?.commit) {
        setError('Worktree API not available');
        return;
      }

      // Pass selected files if not all files are selected
      const filesToCommit =
        selectedFiles.size === files.length ? undefined : Array.from(selectedFiles);

      const result = await api.worktree.commit(worktree.path, message, filesToCommit);

      if (result.success && result.result) {
        if (result.result.committed) {
          setCommitSucceeded(true);
          toast.success('Changes committed', {
            description: `Commit ${result.result.commitHash} on ${result.result.branch}`,
          });

          // Push after commit if enabled
          let pushSucceeded = false;
          if (pushAfterCommit && selectedRemote) {
            pushSucceeded = await performPush(api, worktree.path, selectedRemote);
          }

          // Only close the dialog when no push was requested or the push completed successfully.
          // If push failed, keep the dialog open so the user can retry.
          if (!pushAfterCommit || pushSucceeded) {
            onCommitted();
            onOpenChange(false);
            setMessage('');
          } else {
            // Commit succeeded but push failed — notify parent of commit but keep dialog open for retry
            onCommitted();
          }
        } else {
          toast.info('No changes to commit', {
            description: result.result.message,
          });
        }
      } else {
        setError(result.error || 'Failed to commit changes');
      }
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Failed to commit');
    } finally {
      setIsLoading(false);
    }
  };

  // When the commit succeeded but push failed, allow retrying the push without
  // requiring a commit message or file selection.
  const isPushRetry = commitSucceeded && pushAfterCommit && !isPushing;

  const handleKeyDown = (e: React.KeyboardEvent) => {
    if (
      e.key === 'Enter' &&
      (e.metaKey || e.ctrlKey) &&
      !isLoading &&
      !isPushing &&
      !isGenerating
    ) {
      if (isPushRetry) {
        // Push retry only needs a selected remote
        if (selectedRemote) {
          handleCommit();
        }
      } else if (
        message.trim() &&
        selectedFiles.size > 0 &&
        !(pushAfterCommit && !selectedRemote)
      ) {
        handleCommit();
      }
    }
  };

  // Generate AI commit message
  const generateCommitMessage = useCallback(async () => {
    if (!worktree) return;

    setIsGenerating(true);
    try {
      const resolvedCommitModel = resolveModelString(commitEffectiveModel);
      const api = getHttpApiClient();
      const result = await api.worktree.generateCommitMessage(
        worktree.path,
        resolvedCommitModel,
        commitEffectiveModelEntry?.thinkingLevel,
        commitEffectiveModelEntry?.providerId
      );

      if (result.success && result.message) {
        setMessage(result.message);
      } else {
        console.warn('Failed to generate commit message:', result.error);
        toast.error('Failed to generate commit message', {
          description: result.error || 'Unknown error',
        });
      }
    } catch (err) {
      console.warn('Error generating commit message:', err);
      toast.error('Failed to generate commit message', {
        description: err instanceof Error ? err.message : 'Unknown error',
      });
    } finally {
      setIsGenerating(false);
    }
  }, [worktree, commitEffectiveModel, commitEffectiveModelEntry]);

  // Keep a stable ref to generateCommitMessage so the open-dialog effect
  // doesn't re-fire (and erase user edits) when the model override changes.
  const generateCommitMessageRef = useRef(generateCommitMessage);
  useEffect(() => {
    generateCommitMessageRef.current = generateCommitMessage;
  });

  // Generate AI commit message when dialog opens (if enabled)
  useEffect(() => {
    if (open && worktree) {
      // Reset state
      setMessage('');
      setError(null);

      if (!enableAiCommitMessages) {
        return;
      }

      generateCommitMessageRef.current();
    }
  }, [open, worktree, enableAiCommitMessages]);

  if (!worktree) return null;

  const allSelected = selectedFiles.size === files.length && files.length > 0;

  // Prevent the dialog from being dismissed while a push or generation is in progress.
  // Overlay clicks and Escape key both route through onOpenChange(false); we
  // intercept those here so the UI stays open until the operation completes.
  const handleOpenChange = (nextOpen: boolean) => {
    if (!nextOpen && (isLoading || isPushing || isGenerating)) {
      // Ignore close requests during an active commit, push, or generation.
      return;
    }
    onOpenChange(nextOpen);
  };

  return (
    <Dialog open={open} onOpenChange={handleOpenChange}>
      <DialogContent className="sm:max-w-[700px] max-h-[85vh] flex flex-col">
        <DialogHeader>
          <DialogTitle className="flex items-center gap-2">
            <GitCommit className="w-5 h-5" />
            Commit Changes
          </DialogTitle>
          <DialogDescription>
            Commit changes in the{' '}
            <code className="font-mono bg-muted px-1 rounded">{worktree.branch}</code> worktree.
          </DialogDescription>
        </DialogHeader>

        <div className="flex flex-col gap-3 py-2 min-h-0 flex-1 overflow-hidden">
          {/* Merge state banner */}
          {mergeState?.isMerging && (
            <div className="flex items-start gap-2 p-3 rounded-md bg-purple-500/10 border border-purple-500/20">
              <GitMerge className="w-4 h-4 text-purple-500 mt-0.5 flex-shrink-0" />
              <div className="text-sm">
                <span className="font-medium text-purple-400">
                  {mergeState.mergeOperationType === 'cherry-pick'
                    ? 'Cherry-pick'
                    : mergeState.mergeOperationType === 'rebase'
                      ? 'Rebase'
                      : 'Merge'}{' '}
                  in progress
                </span>
                {mergeState.conflictFiles.length > 0 ? (
                  <span className="text-purple-400/80 ml-1">
                    &mdash; {mergeState.conflictFiles.length} file
                    {mergeState.conflictFiles.length !== 1 ? 's' : ''} with conflicts
                  </span>
                ) : mergeState.isCleanMerge ? (
                  <span className="text-purple-400/80 ml-1">
                    &mdash; Clean merge, {mergeState.mergeAffectedFiles.length} file
                    {mergeState.mergeAffectedFiles.length !== 1 ? 's' : ''} affected
                  </span>
                ) : null}
              </div>
            </div>
          )}

          {/* File Selection */}
          <div className="flex flex-col min-h-0">
            <div className="flex items-center justify-between mb-1.5">
              <Label className="text-sm font-medium flex items-center gap-2">
                Files to commit
                {isLoadingDiffs ? (
                  <Spinner size="sm" />
                ) : (
                  <span className="text-xs text-muted-foreground font-normal">
                    ({selectedFiles.size}/{files.length} selected)
                  </span>
                )}
              </Label>
              {files.length > 0 && (
                <button
                  onClick={handleToggleAll}
                  className="text-xs text-muted-foreground hover:text-foreground transition-colors"
                >
                  {allSelected ? 'Deselect all' : 'Select all'}
                </button>
              )}
            </div>

            {isLoadingDiffs ? (
              <div className="flex items-center justify-center py-6 text-muted-foreground border border-border rounded-lg">
                <Spinner size="sm" className="mr-2" />
                <span className="text-sm">Loading changes...</span>
              </div>
            ) : files.length === 0 ? (
              <div className="flex items-center justify-center py-6 text-muted-foreground border border-border rounded-lg">
                <span className="text-sm">No changes detected</span>
              </div>
            ) : (
              <div className="border border-border rounded-lg overflow-hidden max-h-[300px] overflow-y-auto scrollbar-visible">
                {files.map((file) => {
                  const isChecked = selectedFiles.has(file.path);
                  const isExpanded = expandedFile === file.path;
                  const fileDiff = diffsByFile.get(file.path);
                  const additions = fileDiff?.additions ?? 0;
                  const deletions = fileDiff?.deletions ?? 0;
                  // Determine staging state from index/worktree status
                  const idx = file.indexStatus ?? ' ';
                  const wt = file.workTreeStatus ?? ' ';
                  const isStaged = idx !== ' ' && idx !== '?';
                  const isUnstaged = wt !== ' ' && wt !== '?';
                  const isUntracked = idx === '?' && wt === '?';
                  const isMergeFile =
                    file.isMergeAffected ||
                    (mergeState?.mergeAffectedFiles?.includes(file.path) ?? false);

                  return (
                    <div
                      key={file.path}
                      className={cn(
                        'border-b last:border-b-0',
                        isMergeFile ? 'border-purple-500/30' : 'border-border'
                      )}
                    >
                      <div
                        className={cn(
                          'flex items-center gap-2 px-3 py-1.5 transition-colors group',
                          isMergeFile
                            ? 'bg-purple-500/5 hover:bg-purple-500/10'
                            : 'hover:bg-accent/50',
                          isExpanded && (isMergeFile ? 'bg-purple-500/10' : 'bg-accent/30')
                        )}
                      >
                        {/* Checkbox */}
                        <Checkbox
                          checked={isChecked}
                          onCheckedChange={() => handleToggleFile(file.path)}
                          className="flex-shrink-0"
                        />

                        {/* Clickable file row to show diff */}
                        <button
                          onClick={() => handleFileClick(file.path)}
                          className="flex items-center gap-2 flex-1 min-w-0 text-left"
                        >
                          {isExpanded ? (
                            <ChevronDown className="w-3 h-3 text-muted-foreground flex-shrink-0" />
                          ) : (
                            <ChevronRight className="w-3 h-3 text-muted-foreground flex-shrink-0" />
                          )}
                          {isMergeFile ? (
                            <GitMerge className="w-3.5 h-3.5 text-purple-500 flex-shrink-0" />
                          ) : (
                            getFileIcon(file.status)
                          )}
                          <TruncatedFilePath
                            path={file.path}
                            className="text-xs font-mono flex-1 text-foreground"
                          />
                          {isMergeFile && (
                            <span className="text-[10px] px-1.5 py-0.5 rounded border font-medium flex-shrink-0 bg-purple-500/15 text-purple-400 border-purple-500/30 inline-flex items-center gap-0.5">
                              <GitMerge className="w-2.5 h-2.5" />
                              {getMergeTypeLabel(file.mergeType)}
                            </span>
                          )}
                          <span
                            className={cn(
                              'text-[10px] px-1.5 py-0.5 rounded border font-medium flex-shrink-0',
                              getStatusBadgeColor(file.status)
                            )}
                          >
                            {getStatusLabel(file.status)}
                          </span>
                          {isStaged && !isUnstaged && !isUntracked && (
                            <span className="text-[10px] px-1 py-0.5 rounded border font-medium flex-shrink-0 bg-green-500/15 text-green-400 border-green-500/30">
                              Staged
                            </span>
                          )}
                          {isStaged && isUnstaged && (
                            <span className="text-[10px] px-1 py-0.5 rounded border font-medium flex-shrink-0 bg-amber-500/15 text-amber-400 border-amber-500/30">
                              Partial
                            </span>
                          )}
                          {additions > 0 && (
                            <span className="text-[10px] text-green-400 flex-shrink-0">
                              +{additions}
                            </span>
                          )}
                          {deletions > 0 && (
                            <span className="text-[10px] text-red-400 flex-shrink-0">
                              -{deletions}
                            </span>
                          )}
                        </button>
                      </div>

                      {/* Expanded diff view */}
                      {isExpanded && fileDiff && (
                        <div className="bg-background border-t border-border max-h-[200px] overflow-y-auto scrollbar-visible">
                          {fileDiff.hunks.map((hunk, hunkIndex) => (
                            <div
                              key={hunkIndex}
                              className="border-b border-border-glass last:border-b-0"
                            >
                              {hunk.lines.map((line, lineIndex) => (
                                <DiffLine
                                  key={lineIndex}
                                  type={line.type}
                                  content={line.content}
                                  lineNumber={line.lineNumber}
                                />
                              ))}
                            </div>
                          ))}
                        </div>
                      )}
                      {isExpanded && !fileDiff && (
                        <div className="px-4 py-3 text-xs text-muted-foreground bg-background border-t border-border">
                          {file.status === '?' ? (
                            <span>New file - diff preview not available</span>
                          ) : file.status === 'D' ? (
                            <span>File deleted</span>
                          ) : (
                            <span>Diff content not available</span>
                          )}
                        </div>
                      )}
                    </div>
                  );
                })}
              </div>
            )}
          </div>

          {/* Commit Message */}
          <div className="grid gap-1.5">
            <div className="flex items-center justify-between">
              <Label htmlFor="commit-message" className="flex items-center gap-2">
                Commit Message
                {isGenerating && (
                  <span className="flex items-center gap-1 text-xs text-muted-foreground">
                    <Sparkles className="w-3 h-3 animate-pulse" />
                    Generating...
                  </span>
                )}
              </Label>
              <div className="flex items-center gap-1">
                {enableAiCommitMessages && (
                  <>
                    <Button
                      variant="ghost"
                      size="sm"
                      onClick={generateCommitMessage}
                      disabled={isGenerating || isLoading}
                      className="h-6 px-2 text-xs"
                      title="Regenerate commit message"
                    >
                      {isGenerating ? (
                        <Spinner size="xs" className="mr-1" />
                      ) : (
                        <RefreshCw className="w-3 h-3 mr-1" />
                      )}
                      Regenerate
                    </Button>
                    <ModelOverrideTrigger
                      currentModelEntry={commitModelOverride.effectiveModelEntry}
                      onModelChange={commitModelOverride.setOverride}
                      phase="commitMessageModel"
                      isOverridden={commitModelOverride.isOverridden}
                      size="sm"
                      variant="icon"
                    />
                  </>
                )}
              </div>
            </div>
            <Textarea
              id="commit-message"
              placeholder={
                isGenerating ? 'Generating commit message...' : 'Describe your changes...'
              }
              value={message}
              onChange={(e) => {
                setMessage(e.target.value);
                setError(null);
              }}
              onKeyDown={handleKeyDown}
              className="min-h-[80px] font-mono text-sm"
              autoFocus
              disabled={isGenerating}
            />
            {error && <p className="text-sm text-destructive">{error}</p>}
          </div>

          {/* Push after commit option */}
          <div className="flex flex-col gap-2">
            <div className="flex items-center gap-2">
              <Checkbox
                id="push-after-commit"
                checked={pushAfterCommit}
                onCheckedChange={(checked) => setPushAfterCommit(checked === true)}
              />
              <Label
                htmlFor="push-after-commit"
                className="text-sm font-medium cursor-pointer flex items-center gap-1.5"
              >
                <Upload className="w-3.5 h-3.5" />
                Push to remote after commit
              </Label>
            </div>

            {pushAfterCommit && (
              <div className="ml-6 flex flex-col gap-1.5">
                {isLoadingRemotes || (!remotesFetched && !remotesFetchError) ? (
                  <div className="flex items-center gap-2 text-sm text-muted-foreground">
                    <Spinner size="sm" />
                    <span>Loading remotes...</span>
                  </div>
                ) : remotesFetchError ? (
                  <div className="flex items-center gap-2 text-sm text-destructive">
                    <span>Failed to load remotes.</span>
                    <button
                      className="text-xs underline hover:text-foreground transition-colors"
                      onClick={() => {
                        if (worktree) {
                          setRemotesFetchError(null);
                        }
                      }}
                    >
                      Retry
                    </button>
                  </div>
                ) : remotes.length === 0 && remotesFetched ? (
                  <p className="text-sm text-muted-foreground">
                    No remotes configured for this repository.
                  </p>
                ) : remotes.length > 0 ? (
                  <div className="flex items-center gap-2">
                    <Label
                      htmlFor="remote-select"
                      className="text-xs text-muted-foreground whitespace-nowrap"
                    >
                      Remote:
                    </Label>
                    <Select value={selectedRemote} onValueChange={setSelectedRemote}>
                      <SelectTrigger id="remote-select" className="h-8 text-xs flex-1">
                        <SelectValue placeholder="Select remote" />
                      </SelectTrigger>
                      <SelectContent>
                        {remotes.map((remote) => (
                          <SelectItem
                            key={remote.name}
                            value={remote.name}
                            description={
                              <span className="text-xs text-muted-foreground truncate w-full block">
                                {remote.url}
                              </span>
                            }
                          >
                            <span className="font-medium">{remote.name}</span>
                          </SelectItem>
                        ))}
                      </SelectContent>
                    </Select>
                  </div>
                ) : null}
              </div>
            )}
          </div>

          <p className="text-xs text-muted-foreground">
            Press <kbd className="px-1 py-0.5 bg-muted rounded text-xs">Cmd/Ctrl+Enter</kbd> to
            commit{pushAfterCommit ? ' & push' : ''}
          </p>
        </div>

        <DialogFooter>
          <Button
            variant="ghost"
            onClick={() => onOpenChange(false)}
            disabled={isLoading || isPushing || isGenerating}
          >
            Cancel
          </Button>
          <Button
            onClick={handleCommit}
            disabled={
              isLoading ||
              isPushing ||
              isGenerating ||
              (isPushRetry
                ? !selectedRemote
                : !message.trim() ||
                  selectedFiles.size === 0 ||
                  (pushAfterCommit && !selectedRemote))
            }
          >
            {isLoading || isPushing ? (
              <>
                <Spinner size="sm" className="mr-2" />
                {isPushing ? 'Pushing...' : 'Committing...'}
              </>
            ) : isPushRetry ? (
              <>
                <Upload className="w-4 h-4 mr-2" />
                Retry Push
              </>
            ) : (
              <>
                {pushAfterCommit ? (
                  <Upload className="w-4 h-4 mr-2" />
                ) : (
                  <GitCommit className="w-4 h-4 mr-2" />
                )}
                {pushAfterCommit ? 'Commit & Push' : 'Commit'}
                {selectedFiles.size > 0 && selectedFiles.size < files.length
                  ? ` (${selectedFiles.size} file${selectedFiles.size > 1 ? 's' : ''})`
                  : ''}
              </>
            )}
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}
