import { useState, useEffect, useMemo, useCallback } 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 {
  GitCommit,
  Sparkles,
  FilePlus,
  FileX,
  FilePen,
  FileText,
  File,
  ChevronDown,
  ChevronRight,
} from 'lucide-react';
import { Spinner } from '@/components/ui/spinner';
import { getElectronAPI } from '@/lib/electron';
import { toast } from 'sonner';
import { useAppStore } from '@/store/app-store';
import { cn } from '@/lib/utils';
import type { FileStatus } from '@/types/electron';

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;
}

interface ParsedDiffHunk {
  header: string;
  lines: {
    type: 'context' | 'addition' | 'deletion' | 'header';
    content: string;
    lineNumber?: { old?: number; new?: number };
  }[];
}

interface ParsedFileDiff {
  filePath: string;
  hunks: ParsedDiffHunk[];
  isNew?: boolean;
  isDeleted?: boolean;
  isRenamed?: boolean;
}

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';
  }
};

/**
 * Parse unified diff format into structured data
 */
function parseDiff(diffText: string): ParsedFileDiff[] {
  if (!diffText) return [];

  const files: ParsedFileDiff[] = [];
  const lines = diffText.split('\n');
  let currentFile: ParsedFileDiff | null = null;
  let currentHunk: ParsedDiffHunk | null = null;
  let oldLineNum = 0;
  let newLineNum = 0;

  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];

    if (line.startsWith('diff --git')) {
      if (currentFile) {
        if (currentHunk) currentFile.hunks.push(currentHunk);
        files.push(currentFile);
      }
      const match = line.match(/diff --git a\/(.*?) b\/(.*)/);
      currentFile = {
        filePath: match ? match[2] : 'unknown',
        hunks: [],
      };
      currentHunk = null;
      continue;
    }

    if (line.startsWith('new file mode')) {
      if (currentFile) currentFile.isNew = true;
      continue;
    }
    if (line.startsWith('deleted file mode')) {
      if (currentFile) currentFile.isDeleted = true;
      continue;
    }
    if (line.startsWith('rename from') || line.startsWith('rename to')) {
      if (currentFile) currentFile.isRenamed = true;
      continue;
    }
    if (line.startsWith('index ') || line.startsWith('--- ') || line.startsWith('+++ ')) {
      continue;
    }

    if (line.startsWith('@@')) {
      if (currentHunk && currentFile) currentFile.hunks.push(currentHunk);
      const hunkMatch = line.match(/@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
      oldLineNum = hunkMatch ? parseInt(hunkMatch[1], 10) : 1;
      newLineNum = hunkMatch ? parseInt(hunkMatch[2], 10) : 1;
      currentHunk = {
        header: line,
        lines: [{ type: 'header', content: line }],
      };
      continue;
    }

    if (currentHunk) {
      if (line.startsWith('+')) {
        currentHunk.lines.push({
          type: 'addition',
          content: line.substring(1),
          lineNumber: { new: newLineNum },
        });
        newLineNum++;
      } else if (line.startsWith('-')) {
        currentHunk.lines.push({
          type: 'deletion',
          content: line.substring(1),
          lineNumber: { old: oldLineNum },
        });
        oldLineNum++;
      } else if (line.startsWith(' ') || line === '') {
        currentHunk.lines.push({
          type: 'context',
          content: line.substring(1) || '',
          lineNumber: { old: oldLineNum, new: newLineNum },
        });
        oldLineNum++;
        newLineNum++;
      }
    }
  }

  if (currentFile) {
    if (currentHunk) currentFile.hunks.push(currentHunk);
    files.push(currentFile);
  }

  return files;
}

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);

  // 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);

  // 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]);

  // Load diffs when dialog opens
  useEffect(() => {
    if (open && worktree) {
      setIsLoadingDiffs(true);
      setFiles([]);
      setDiffContent('');
      setSelectedFiles(new Set());
      setExpandedFile(null);

      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 ?? [];
              setFiles(fileList);
              setDiffContent(result.diff ?? '');
              // Select all files by default
              setSelectedFiles(new Set(fileList.map((f) => f.path)));
            }
          }
        } catch (err) {
          console.warn('Failed to load diffs for commit dialog:', err);
        } finally {
          setIsLoadingDiffs(false);
        }
      };

      loadDiffs();
    }
  }, [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));
  }, []);

  const handleCommit = async () => {
    if (!worktree || !message.trim() || selectedFiles.size === 0) return;

    setIsLoading(true);
    setError(null);

    try {
      const api = getElectronAPI();
      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) {
          toast.success('Changes committed', {
            description: `Commit ${result.result.commitHash} on ${result.result.branch}`,
          });
          onCommitted();
          onOpenChange(false);
          setMessage('');
        } 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);
    }
  };

  const handleKeyDown = (e: React.KeyboardEvent) => {
    if (
      e.key === 'Enter' &&
      e.metaKey &&
      !isLoading &&
      !isGenerating &&
      message.trim() &&
      selectedFiles.size > 0
    ) {
      handleCommit();
    }
  };

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

      if (!enableAiCommitMessages) {
        return;
      }

      setIsGenerating(true);
      let cancelled = false;

      const generateMessage = async () => {
        try {
          const api = getElectronAPI();
          if (!api?.worktree?.generateCommitMessage) {
            if (!cancelled) {
              setIsGenerating(false);
            }
            return;
          }

          const result = await api.worktree.generateCommitMessage(worktree.path);

          if (cancelled) return;

          if (result.success && result.message) {
            setMessage(result.message);
          } else {
            console.warn('Failed to generate commit message:', result.error);
            setMessage('');
          }
        } catch (err) {
          if (cancelled) return;
          console.warn('Error generating commit message:', err);
          setMessage('');
        } finally {
          if (!cancelled) {
            setIsGenerating(false);
          }
        }
      };

      generateMessage();

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

  if (!worktree) return null;

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

  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <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">
          {/* 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
                    ? fileDiff.hunks.reduce(
                        (acc, hunk) => acc + hunk.lines.filter((l) => l.type === 'addition').length,
                        0
                      )
                    : 0;
                  const deletions = fileDiff
                    ? fileDiff.hunks.reduce(
                        (acc, hunk) => acc + hunk.lines.filter((l) => l.type === 'deletion').length,
                        0
                      )
                    : 0;

                  return (
                    <div key={file.path} className="border-b border-border last:border-b-0">
                      <div
                        className={cn(
                          'flex items-center gap-2 px-3 py-1.5 hover:bg-accent/50 transition-colors group',
                          isExpanded && '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" />
                          )}
                          {getFileIcon(file.status)}
                          <span className="text-xs font-mono truncate flex-1 text-foreground">
                            {file.path}
                          </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>
                          {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">
            <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>
            <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>

          <p className="text-xs text-muted-foreground">
            Press <kbd className="px-1 py-0.5 bg-muted rounded text-xs">Cmd+Enter</kbd> to commit
          </p>
        </div>

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