/**
 * ScriptDiffReview — side-by-side diff view for a pending .next.js patch.
 *
 * Design choice: .next.js files appear as sibling rows in the FileTree just like
 * any other file. Clicking a .next.js file opens this component (instead of
 * MonacoFileEditor). The diff view shows the current .js on the left (original)
 * and the candidate .next.js on the right (modified), read-only.
 *
 * Accept: archives scripts/{name}.js to trash, renames .next.js into place.
 * Reject: moves .next.js to trash; original is untouched.
 * Both actions invalidate the scripts listing and navigate back to scripts/{name}.js.
 */
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { DiffEditor } from '@monaco-editor/react';

type ShowToast = (message: string, variant?: 'success' | 'error') => void;

interface ScriptDiffReviewProps {
  /** The bare script name without extension, e.g. "myscript" */
  scriptName: string;
  onClose: (acceptedScript?: string) => void;
  showToast?: ShowToast;
}

interface DiffResponse {
  current: string | null;
  candidate: string;
  candidateMtime: string;
}

async function fetchDiff(name: string): Promise<DiffResponse> {
  const res = await fetch(`/api/users/me/browser-macros/${encodeURIComponent(name)}/diff`, {
    credentials: 'include',
  });
  if (res.status === 404) throw new Error('No pending patch found.');
  if (!res.ok) throw new Error(`Diff fetch failed: ${res.status}`);
  return res.json() as Promise<DiffResponse>;
}

async function postAccept(name: string): Promise<void> {
  const res = await fetch(`/api/users/me/browser-macros/${encodeURIComponent(name)}/accept`, {
    method: 'POST',
    credentials: 'include',
  });
  if (!res.ok) throw new Error(`Accept failed: ${res.status}`);
}

async function postReject(name: string): Promise<void> {
  const res = await fetch(`/api/users/me/browser-macros/${encodeURIComponent(name)}/reject`, {
    method: 'POST',
    credentials: 'include',
  });
  if (!res.ok) throw new Error(`Reject failed: ${res.status}`);
}

export function ScriptDiffReview({ scriptName, onClose, showToast }: ScriptDiffReviewProps) {
  const qc = useQueryClient();
  const notifyError = (label: string, err: unknown) => {
    const msg = `${label}: ${err instanceof Error ? err.message : 'Unknown error'}`;
    if (showToast) showToast(msg, 'error');
    else console.error(msg);
  };

  const diffQuery = useQuery<DiffResponse, Error>({
    queryKey: ['userfolder', 'diff', scriptName],
    queryFn: () => fetchDiff(scriptName),
    staleTime: 10_000,
    refetchOnWindowFocus: false,
  });

  const candidateMtimeLabel = diffQuery.data?.candidateMtime
    ? new Date(diffQuery.data.candidateMtime).toLocaleString()
    : '';

  async function handleAccept() {
    try {
      await postAccept(scriptName);
      // Invalidate browser-macros listing so the .next.js row disappears
      qc.invalidateQueries({ queryKey: ['userfolder', 'list', 'browser-macros'] });
      qc.removeQueries({ queryKey: ['userfolder', 'diff', scriptName] });
      // Navigate back to the now-accepted script
      onClose(`${scriptName}.js`);
    } catch (err) {
      notifyError('Accept failed', err);
    }
  }

  async function handleReject() {
    try {
      await postReject(scriptName);
      qc.invalidateQueries({ queryKey: ['userfolder', 'list', 'browser-macros'] });
      qc.removeQueries({ queryKey: ['userfolder', 'diff', scriptName] });
      // Navigate back to the original (unchanged) script if it exists; otherwise close
      const hasOriginal = diffQuery.data?.current !== null;
      onClose(hasOriginal ? `${scriptName}.js` : undefined);
    } catch (err) {
      notifyError('Reject failed', err);
    }
  }

  return (
    <div className="flex flex-col h-full">
      {/* Header */}
      <div className="flex-shrink-0 flex items-center gap-3 px-4 py-2.5 border-b border-hairline bg-surface-2/50">
        <div className="flex-1 min-w-0">
          <span className="text-xs font-semibold text-slate-700">Patch review: </span>
          <span className="font-mono text-xs text-slate-600">{scriptName}.next.js</span>
          {candidateMtimeLabel && (
            <span className="ml-2 text-2xs text-slate-400">{candidateMtimeLabel}</span>
          )}
        </div>
        <div className="flex items-center gap-2">
          <span className="text-2xs text-slate-400 font-mono">original → patch</span>
          <button
            type="button"
            onClick={handleReject}
            disabled={diffQuery.isLoading}
            className="px-3 py-1 rounded-md text-2xs font-semibold bg-red-500 text-white hover:bg-red-600 disabled:opacity-50 transition-colors"
          >
            Reject
          </button>
          <button
            type="button"
            onClick={handleAccept}
            disabled={diffQuery.isLoading}
            className="px-3 py-1 rounded-md text-2xs font-semibold bg-green-600 text-white hover:bg-green-700 disabled:opacity-50 transition-colors"
          >
            Accept
          </button>
        </div>
      </div>

      {/* Body */}
      <div className="flex-1 min-h-0 overflow-hidden">
        {diffQuery.isLoading && (
          <div className="h-full flex items-center justify-center text-[13px] text-slate-400">
            Loading diff…
          </div>
        )}
        {diffQuery.isError && (
          <div className="h-full flex items-center justify-center text-[13px] text-red-500">
            {diffQuery.error?.message ?? 'Failed to load diff.'}
          </div>
        )}
        {diffQuery.data && (
          <DiffEditor
            height="100%"
            language="javascript"
            original={diffQuery.data.current ?? ''}
            modified={diffQuery.data.candidate}
            options={{
              readOnly: true,
              renderSideBySide: true,
              minimap: { enabled: false },
              scrollBeyondLastLine: false,
              fontSize: 12,
            }}
          />
        )}
      </div>
    </div>
  );
}
