import { useState, useEffect, useCallback } from 'react';
import { Check, X, RotateCcw, FileText, FilePlus, Trash2, RefreshCw } from 'lucide-react';
import type { PendingChange } from '@/api/types';
import { api } from '@/api/client';

interface Props {
  sessionId: string;
  onPendingChange: (cb: (change: PendingChange) => void) => () => void;
}

export function DiffPane({ sessionId, onPendingChange }: Props) {
  const [changes, setChanges] = useState<PendingChange[]>([]);
  const [loading, setLoading] = useState(true);
  const [expandedId, setExpandedId] = useState<string | null>(null);

  const fetchPending = useCallback(async () => {
    try {
      const result = await api.pending.list(sessionId);
      setChanges(result);
    } catch (err) {
      console.error('fetch pending failed:', err);
    } finally {
      setLoading(false);
    }
  }, [sessionId]);

  // Initial load
  useEffect(() => {
    fetchPending();
  }, [fetchPending]);

  // Listen for WS pending change events
  useEffect(() => {
    const unsub = onPendingChange((change) => {
      setChanges((prev) => {
        const idx = prev.findIndex((c) => c.id === change.id);
        if (idx >= 0) {
          const next = [...prev];
          next[idx] = change;
          return next;
        }
        return [...prev, change];
      });
    });
    return unsub;
  }, [onPendingChange]);

  const pendingChanges = changes.filter((c) => c.status === 'pending');
  const resolvedChanges = changes.filter((c) => c.status !== 'pending');

  const handleApplyOne = async (id: string) => {
    try {
      await api.pending.applyOne(id);
      setChanges((prev) =>
        prev.map((c) => (c.id === id ? { ...c, status: 'applied' as const } : c)),
      );
    } catch (err) {
      console.error('apply failed:', err);
    }
  };

  const handleRejectOne = async (id: string) => {
    try {
      await api.pending.rejectOne(id);
      setChanges((prev) =>
        prev.map((c) => (c.id === id ? { ...c, status: 'rejected' as const } : c)),
      );
    } catch (err) {
      console.error('reject failed:', err);
    }
  };

  const handleRewindOne = async (id: string) => {
    try {
      await api.pending.rewindOne(id);
      setChanges((prev) =>
        prev.map((c) => (c.id === id ? { ...c, status: 'reverted' as const } : c)),
      );
    } catch (err) {
      console.error('rewind failed:', err);
    }
  };

  const handleApplyAll = async () => {
    try {
      const result = await api.pending.applyAll(sessionId);
      const appliedIds = new Set(
        result.results.filter((r) => r.success).map((r) => r.id),
      );
      setChanges((prev) =>
        prev.map((c) =>
          appliedIds.has(c.id) ? { ...c, status: 'applied' as const } : c,
        ),
      );
    } catch (err) {
      console.error('apply all failed:', err);
    }
  };

  const handleRejectAll = async () => {
    // Reject each pending change individually (no batch reject endpoint)
    for (const c of pendingChanges) {
      await handleRejectOne(c.id);
    }
  };

  const OpIcon = ({ op }: { op: PendingChange['operation'] }) => {
    switch (op) {
      case 'create':
        return <FilePlus size={14} className="text-green-400" />;
      case 'edit':
        return <FileText size={14} className="text-blue-400" />;
      case 'delete':
        return <Trash2 size={14} className="text-red-400" />;
    }
  };

  const StatusBadge = ({ status }: { status: PendingChange['status'] }) => {
    const colors: Record<PendingChange['status'], string> = {
      pending: 'bg-yellow-500/20 text-yellow-400',
      applied: 'bg-green-500/20 text-green-400',
      rejected: 'bg-zinc-500/20 text-zinc-400',
      reverted: 'bg-orange-500/20 text-orange-400',
    };
    return (
      <span className={`text-[10px] px-1.5 py-0.5 rounded ${colors[status]}`}>
        {status}
      </span>
    );
  };

  return (
    <div className="flex flex-col h-full">
      {/* Header */}
      <div className="flex items-center justify-between px-4 py-2 border-b border-zinc-800">
        <h2 className="text-sm font-medium text-zinc-300">
          Pending Changes
          {pendingChanges.length > 0 && (
            <span className="ml-1.5 text-xs text-zinc-500">
              ({pendingChanges.length})
            </span>
          )}
        </h2>
        <div className="flex items-center gap-1">
          <button
            onClick={fetchPending}
            className="p-1 rounded hover:bg-zinc-800 text-zinc-400 hover:text-zinc-200"
            title="Refresh"
          >
            <RefreshCw size={14} />
          </button>
          {pendingChanges.length > 0 && (
            <>
              <button
                onClick={handleApplyAll}
                className="text-xs px-2 py-1 rounded bg-green-600/80 hover:bg-green-600 text-white"
              >
                Apply All
              </button>
              <button
                onClick={handleRejectAll}
                className="text-xs px-2 py-1 rounded bg-zinc-700 hover:bg-zinc-600 text-zinc-300"
              >
                Reject All
              </button>
            </>
          )}
        </div>
      </div>

      {/* Changes list */}
      <div className="flex-1 overflow-y-auto">
        {loading && (
          <div className="text-center text-zinc-500 text-sm py-8">Loading...</div>
        )}

        {!loading && changes.length === 0 && (
          <div className="text-center text-zinc-500 text-sm py-8">
            No pending changes yet.
          </div>
        )}

        {/* Pending changes first */}
        {pendingChanges.map((change) => (
          <ChangeItem
            key={change.id}
            change={change}
            expanded={expandedId === change.id}
            onToggle={() =>
              setExpandedId((prev) => (prev === change.id ? null : change.id))
            }
            onApply={() => handleApplyOne(change.id)}
            onReject={() => handleRejectOne(change.id)}
            OpIcon={OpIcon}
            StatusBadge={StatusBadge}
          />
        ))}

        {/* Resolved changes */}
        {resolvedChanges.length > 0 && pendingChanges.length > 0 && (
          <div className="border-t border-zinc-800 my-1" />
        )}
        {resolvedChanges.map((change) => (
          <ChangeItem
            key={change.id}
            change={change}
            expanded={expandedId === change.id}
            onToggle={() =>
              setExpandedId((prev) => (prev === change.id ? null : change.id))
            }
            onRewind={
              change.status === 'applied'
                ? () => handleRewindOne(change.id)
                : undefined
            }
            OpIcon={OpIcon}
            StatusBadge={StatusBadge}
          />
        ))}
      </div>
    </div>
  );
}

interface ChangeItemProps {
  change: PendingChange;
  expanded: boolean;
  onToggle: () => void;
  onApply?: () => void;
  onReject?: () => void;
  onRewind?: () => void;
  OpIcon: React.ComponentType<{ op: PendingChange['operation'] }>;
  StatusBadge: React.ComponentType<{ status: PendingChange['status'] }>;
}

function ChangeItem({
  change,
  expanded,
  onToggle,
  onApply,
  onReject,
  onRewind,
  OpIcon,
  StatusBadge,
}: ChangeItemProps) {
  const fileName = change.file_path.split('/').pop() || change.file_path;
  const dirPath = change.file_path.split('/').slice(0, -1).join('/');

  return (
    <div className="border-b border-zinc-800/50">
      <div
        className="flex items-center gap-2 px-4 py-2 hover:bg-zinc-800/50 cursor-pointer"
        onClick={onToggle}
      >
        <OpIcon op={change.operation} />
        <div className="flex-1 min-w-0">
          <span className="text-sm font-mono text-zinc-200 truncate block">
            {fileName}
          </span>
          {dirPath && (
            <span className="text-[11px] text-zinc-500 truncate block">
              {dirPath}
            </span>
          )}
        </div>
        <StatusBadge status={change.status} />
        {change.status === 'pending' && (
          <div className="flex items-center gap-1 ml-1">
            <button
              onClick={(e) => {
                e.stopPropagation();
                onApply?.();
              }}
              className="p-1 rounded hover:bg-green-600/30 text-green-400"
              title="Apply"
            >
              <Check size={14} />
            </button>
            <button
              onClick={(e) => {
                e.stopPropagation();
                onReject?.();
              }}
              className="p-1 rounded hover:bg-red-600/30 text-red-400"
              title="Reject"
            >
              <X size={14} />
            </button>
          </div>
        )}
        {change.status === 'applied' && onRewind && (
          <button
            onClick={(e) => {
              e.stopPropagation();
              onRewind();
            }}
            className="p-1 rounded hover:bg-orange-600/30 text-orange-400"
            title="Rewind"
          >
            <RotateCcw size={14} />
          </button>
        )}
      </div>

      {expanded && (
        <div className="px-4 pb-3">
          {change.operation === 'edit' && (
            <div className="space-y-2">
              {change.old_string && (
                <div className="rounded bg-red-950/30 border border-red-900/30 p-2">
                  <div className="text-[10px] text-red-400 mb-1 font-medium">
                    Remove
                  </div>
                  <pre className="text-xs text-red-200 whitespace-pre-wrap break-all font-mono">
                    {change.old_string}
                  </pre>
                </div>
              )}
              {change.new_string && (
                <div className="rounded bg-green-950/30 border border-green-900/30 p-2">
                  <div className="text-[10px] text-green-400 mb-1 font-medium">
                    Add
                  </div>
                  <pre className="text-xs text-green-200 whitespace-pre-wrap break-all font-mono">
                    {change.new_string}
                  </pre>
                </div>
              )}
            </div>
          )}
          {change.operation === 'create' && change.content && (
            <div className="rounded bg-green-950/30 border border-green-900/30 p-2">
              <div className="text-[10px] text-green-400 mb-1 font-medium">
                New file
              </div>
              <pre className="text-xs text-green-200 whitespace-pre-wrap break-all font-mono max-h-60 overflow-y-auto">
                {change.content.length > 2000
                  ? change.content.slice(0, 2000) + '\n... (truncated)'
                  : change.content}
              </pre>
            </div>
          )}
          {change.operation === 'delete' && (
            <div className="rounded bg-red-950/30 border border-red-900/30 p-2 text-xs text-red-300">
              This file will be deleted.
            </div>
          )}
        </div>
      )}
    </div>
  );
}
