import { useCallback, useEffect, useRef, useState } from 'react';
import { formatChangeType } from '../../data/historyUtils';
import { useServerData } from '../../context/ServerDataContext';
import { computeWindow } from '../../data/treeView';
import { toggleInSelection, useListKeyboardNav } from '../../hooks/useListKeyboardNav';
import { UserAvatar } from '../UserAvatar';
import { ModalPortal } from './ModalPortal';

const FILE_ICON = 'assets/file.svg';
const ROW_H = 24; // must match .revision-file-row height in modals.css
const OVERSCAN = 8;

export function RevisionDetailModal({ entry, onClose, onShowInGraphView, onRollback, initialSelectedPaths }) {
  const [selectedPaths, setSelectedPaths] = useState(() => {
    // An explicit preselection (opened from a graph card) always wins.
    if (initialSelectedPaths?.length) return new Set(initialSelectedPaths);
    // A revision that touched exactly one file leaves nothing to disambiguate,
    // so preselect it: "Show selected in graph view" and "Revert selected"
    // become one click instead of two. Multi-file revisions deliberately start
    // empty — there is no basis for guessing which file the user meant.
    if (entry.changes.length === 1) return new Set([entry.changes[0].path]);
    return new Set();
  });
  const [showRevertConfirm, setShowRevertConfirm] = useState(false);
  const goBackRef = useRef(null);
  const modalRef = useRef(null);
  const { getUser } = useServerData();
  const user = getUser(entry.author);
  const hasSelection = selectedPaths.size > 0;

  const itemIds = entry.changes.map((c) => c.path);

  const handleToggleSelect = useCallback((path) => {
    setSelectedPaths((prev) => toggleInSelection(prev, path));
  }, []);

  const handleSelectSingle = useCallback((path) => {
    setSelectedPaths(new Set([path]));
  }, []);

  const {
    containerRef: fileListRef,
    handleKeyDown: handleListKeyDown,
    registerItemRef,
    focusedId,
    setFocusById,
  } = useListKeyboardNav({
    itemIds,
    selectedIds: selectedPaths,
    onToggleSelect: handleToggleSelect,
    onSelectSingle: handleSelectSingle,
    enabled: !showRevertConfirm,
  });

  // Windowed rendering (same scheme as FileTree): field revisions carry
  // 1,267–1,500 changes; rendering them all froze the modal open.
  const scrollRef = useRef(null);
  const [viewport, setViewport] = useState({ top: 0, height: 220 });
  useEffect(() => {
    const el = scrollRef.current;
    if (!el) return;
    setViewport({ top: el.scrollTop, height: el.clientHeight || 220 });
  }, []);
  const handleScroll = () => {
    const el = scrollRef.current;
    if (!el) return;
    setViewport((v) => (v.top === el.scrollTop ? v : { ...v, top: el.scrollTop }));
  };
  // Keyboard nav can focus a row outside the window; scroll it into range so
  // the next render materializes it (mirror of FileTree's ensure-visible).
  useEffect(() => {
    if (!focusedId) return;
    const el = scrollRef.current;
    if (!el) return;
    const index = entry.changes.findIndex((c) => c.path === focusedId);
    if (index < 0) return;
    const top = index * ROW_H;
    if (top < el.scrollTop) el.scrollTop = top;
    else if (top + ROW_H > el.scrollTop + el.clientHeight)
      el.scrollTop = top - el.clientHeight + ROW_H;
    handleScroll();
  }, [focusedId, entry.changes]);

  const clampedTop = Math.min(
    viewport.top,
    Math.max(0, entry.changes.length * ROW_H - viewport.height)
  );
  const { start, end } = computeWindow(
    clampedTop,
    viewport.height,
    entry.changes.length,
    ROW_H,
    OVERSCAN
  );
  const windowChanges = entry.changes.slice(start, end);

  useEffect(() => {
    modalRef.current?.focus();
  }, []);

  useEffect(() => {
    const onKey = (e) => {
      if (e.key === 'Escape') {
        if (showRevertConfirm) setShowRevertConfirm(false);
        else onClose();
      }
    };
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, [onClose, showRevertConfirm]);

  useEffect(() => {
    if (showRevertConfirm) goBackRef.current?.focus();
  }, [showRevertConfirm]);

  const handleModalKeyDown = (e) => {
    if (showRevertConfirm) return;
    handleListKeyDown(e);
  };

  const handleFileClick = (e, path) => {
    e.preventDefault();
    const multi = e.metaKey || e.ctrlKey;
    setFocusById(path);

    if (multi) {
      handleToggleSelect(path);
      return;
    }

    setSelectedPaths(new Set([path]));
  };

  // Graph View charts exactly one file's history, so the button needs a
  // single unambiguous target. Deleted files are fine — the graph is keyed
  // by path, not by what still exists in the tree.
  const singleFileSelected = selectedPaths.size === 1;

  const handleShowInGraph = () => {
    const [path] = [...selectedPaths];
    onShowInGraphView?.(path);
    onClose();
  };

  const handleRevertClick = () => {
    setShowRevertConfirm(true);
  };

  const handleRevertConfirm = () => {
    // Roll the chosen paths back to their state at this revision. The
    // Repository panel owns the actual work (server rollback revisions +
    // workspace sync) and reports per-file results in its own popup.
    const paths = hasSelection ? [...selectedPaths] : entry.changes.map((c) => c.path);
    onRollback?.(paths, entry.revision);
    setShowRevertConfirm(false);
    onClose();
  };

  return (
    <ModalPortal>
      <div className="app-modal-overlay" onMouseDown={onClose}>
        <div
          ref={modalRef}
          className="app-modal app-modal--revision"
          role="dialog"
          aria-modal="true"
          aria-label={`Revision ${entry.revision}`}
          tabIndex={-1}
          onMouseDown={(e) => e.stopPropagation()}
          onKeyDown={handleModalKeyDown}
        >
          <button type="button" className="app-modal-close" onClick={onClose} aria-label="Close">
            <img src="assets/close.svg" alt="" />
          </button>

          <div className="revision-modal-header">
            <UserAvatar user={user} size={44} />
            <div className="revision-modal-identity">
              <div className="revision-modal-name">{user.displayName}</div>
              <div className="revision-modal-email">{user.username}</div>
            </div>
          </div>

          <div className="revision-modal-meta">
            <div className="revision-modal-meta-item">
              <span className="revision-modal-meta-label">Revision</span>
              <span className="revision-modal-meta-value">{entry.revision}</span>
            </div>
            <div className="revision-modal-meta-item">
              <span className="revision-modal-meta-label">Date</span>
              <span className="revision-modal-meta-value">{entry.date}</span>
            </div>
            <div className="revision-modal-meta-item">
              <span className="revision-modal-meta-label">Files changed</span>
              <span className="revision-modal-meta-value">{entry.changes.length}</span>
            </div>
          </div>

          <div className="revision-modal-description">{entry.description}</div>

          <div className="revision-modal-files" ref={fileListRef}>
            <ul
              className="revision-file-list"
              ref={scrollRef}
              onScroll={handleScroll}
              style={{
                paddingTop: start * ROW_H,
                paddingBottom: (entry.changes.length - end) * ROW_H,
              }}
            >
              {windowChanges.map((change, i) => {
                const index = start + i;
                const selected = selectedPaths.has(change.path);
                const focused = focusedId === change.path;
                return (
                  <li key={change.path}>
                    <button
                      type="button"
                      ref={(el) => registerItemRef(change.path, el)}
                      className={`revision-file-row${index % 2 === 1 ? ' revision-file-row--alt' : ''}${selected ? ' revision-file-row--selected' : ''}${focused ? ' revision-file-row--focused' : ''}`}
                      onClick={(e) => handleFileClick(e, change.path)}
                    >
                      <img src={FILE_ICON} alt="" className="revision-file-icon" />
                      <span className="revision-file-path">{change.path}</span>
                      <span className="revision-file-type">({formatChangeType(change.change)})</span>
                    </button>
                  </li>
                );
              })}
            </ul>
          </div>

          <div className="revision-modal-actions">
            <button
              type="button"
              className="revision-pill-btn"
              disabled={!singleFileSelected}
              onClick={handleShowInGraph}
            >
              Show selected in graph view
            </button>
            <button
              type="button"
              className="revision-pill-btn revision-pill-btn--danger"
              onClick={handleRevertClick}
            >
              {hasSelection
                ? 'Revert selected files back to this revision'
                : 'Revert all files back to this revision'}
            </button>
          </div>

          {showRevertConfirm && (
            <div className="revert-confirm-overlay" onMouseDown={() => setShowRevertConfirm(false)}>
              <div className="revert-confirm-dialog" onMouseDown={(e) => e.stopPropagation()}>
                <img src="assets/circle_exclaim.svg" alt="" className="revert-confirm-icon" />
                <h3 className="revert-confirm-title">Are you sure?</h3>
                <p className="revert-confirm-text">
                  Reverting will submit a new revision restoring{' '}
                  {hasSelection && selectedPaths.size < entry.changes.length
                    ? 'the selected files'
                    : 'these files'}{' '}
                  to how they were at revision {entry.revision}.
                </p>
                <div className="revert-confirm-actions">
                  <button
                    ref={goBackRef}
                    type="button"
                    className="revision-pill-btn"
                    onClick={() => setShowRevertConfirm(false)}
                  >
                    Go back
                  </button>
                  <button
                    type="button"
                    className="revision-pill-btn revision-pill-btn--danger"
                    onClick={handleRevertConfirm}
                  >
                    I am sure
                  </button>
                </div>
              </div>
            </div>
          )}
        </div>
      </div>
    </ModalPortal>
  );
}
