import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import {
  api,
  debugImageProxyUrl,
  type DebugImageSearchResult,
  type PaintingCheckupRow,
} from '../api/client';
import './CheckupPage.css';

interface Props {
  onBack: () => void;
  onOpenPainting?: (paintingId: number) => void;
}

type RowSearchState =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'done'; result: DebugImageSearchResult }
  | { status: 'error' };

const SEARCH_CONCURRENCY = 3;

function previewSrc(
  row: PaintingCheckupRow,
  kind: 'gallery' | 'detail',
  version: number
): string | null {
  const bust = version > 0 ? `?v=${version}` : '';
  if (kind === 'gallery') {
    const file = row.gallery_preview ?? row.gallery_file;
    return file ? `/images/${file}${bust}` : null;
  }
  if (row.detail_on_demand) {
    return `/api/paintings/${row.id}/image?size=thumb${version > 0 ? `&v=${version}` : ''}`;
  }
  const file = row.detail_preview ?? row.detail_file;
  return file ? `/images/${file}${bust}` : null;
}

function ImagePreviewCell({
  row,
  kind,
  imageVersion,
}: {
  row: PaintingCheckupRow;
  kind: 'gallery' | 'detail';
  imageVersion: number;
}) {
  const path = kind === 'gallery' ? row.gallery_file : row.detail_file;
  const src = previewSrc(row, kind, imageVersion);
  const exists =
    kind === 'gallery' ? row.gallery_file_exists : row.detail_file_exists;
  const missing = path && exists === false;

  if (!path) {
    return (
      <div className="checkup-thumb checkup-thumb-empty">
        <span>No image</span>
        <small>Hidden in 3D</small>
      </div>
    );
  }

  return (
    <div className={`checkup-thumb${missing ? ' checkup-thumb-missing' : ''}`}>
      {src && (
        <img
          src={src}
          alt=""
          loading="lazy"
          decoding="async"
          title={path}
          onError={(e) => {
            (e.target as HTMLImageElement).src = '/placeholder-art.svg';
          }}
        />
      )}
      {missing && <span className="checkup-thumb-badge">missing file</span>}
      {row.detail_on_demand && kind === 'detail' && (
        <span className="checkup-thumb-badge checkup-thumb-badge-api">on-demand</span>
      )}
    </div>
  );
}

function SearchPreviewCell({
  searchState,
  onRetry,
}: {
  searchState: RowSearchState | undefined;
  onRetry: () => void;
}) {
  if (!searchState) {
    return (
      <div className="checkup-thumb checkup-thumb-search checkup-thumb-empty">
        <span>—</span>
      </div>
    );
  }

  if (searchState.status === 'loading' || searchState.status === 'idle') {
    return (
      <div className="checkup-thumb checkup-thumb-search checkup-thumb-loading">
        <span>{searchState.status === 'loading' ? 'Searching…' : 'Queued…'}</span>
      </div>
    );
  }

  if (searchState.status === 'error') {
    return (
      <div className="checkup-thumb checkup-thumb-search checkup-thumb-empty">
        <span>Search failed</span>
        <button type="button" className="checkup-retry-btn" onClick={onRetry}>
          Retry
        </button>
      </div>
    );
  }

  const { result } = searchState;
  if (!result.imageUrl) {
    return (
      <div className="checkup-thumb checkup-thumb-search checkup-thumb-empty">
        <span>No result</span>
        <small title={result.query}>{result.sourceLabel ?? result.source}</small>
      </div>
    );
  }

  return (
    <div className="checkup-thumb checkup-thumb-search" title={result.query}>
      <img
        src={debugImageProxyUrl(result.imageUrl)}
        alt={`Search: ${result.query}`}
        loading="lazy"
        decoding="async"
        onError={(e) => {
          (e.target as HTMLImageElement).src = '/placeholder-art.svg';
        }}
      />
      <span className="checkup-thumb-badge checkup-thumb-badge-search">
        {result.sourceLabel ?? 'Search'}
      </span>
    </div>
  );
}

function FixCell({
  searchState,
  fixing,
  fixError,
  onFix,
}: {
  searchState: RowSearchState | undefined;
  fixing: boolean;
  fixError: string | null;
  onFix: () => void;
}) {
  const canFix =
    searchState?.status === 'done' && !!searchState.result.imageUrl && !fixing;

  return (
    <div className="checkup-fix-cell">
      <button
        type="button"
        className="checkup-fix-btn"
        onClick={onFix}
        disabled={!canFix}
        title={
          searchState?.status === 'done' && searchState.result.imageUrl
            ? 'Replace gallery and detail images with search result'
            : 'Wait for search result'
        }
      >
        {fixing ? 'Replacing…' : 'Fix it'}
      </button>
      {fixError && <p className="checkup-fix-error">{fixError}</p>}
      {searchState?.status === 'loading' || searchState?.status === 'idle' ? (
        <p className="checkup-fix-hint">Waiting for search…</p>
      ) : null}
      {searchState?.status === 'done' && !searchState.result.imageUrl && (
        <p className="checkup-fix-hint">No image to apply</p>
      )}
    </div>
  );
}

function needsSearch(state: RowSearchState | undefined): boolean {
  return !state || state.status === 'idle' || state.status === 'error';
}

type FlagFilter = 'all' | 'yes' | 'no';

function FlagCheckbox({
  label,
  checked,
  disabled,
  onChange,
}: {
  label: string;
  checked: boolean;
  disabled?: boolean;
  onChange: (next: boolean) => void;
}) {
  return (
    <label className="checkup-flag-label">
      <input
        type="checkbox"
        checked={checked}
        disabled={disabled}
        onChange={(e) => onChange(e.target.checked)}
      />
      <span>{label}</span>
    </label>
  );
}

export default function CheckupPage({ onBack, onOpenPainting }: Props) {
  const [rows, setRows] = useState<PaintingCheckupRow[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [filter, setFilter] = useState('');
  const [filterChecked, setFilterChecked] = useState<FlagFilter>('all');
  const [filterFixed, setFilterFixed] = useState<FlagFilter>('all');
  const [searchById, setSearchById] = useState<Record<number, RowSearchState>>({});
  const [imageVersionById, setImageVersionById] = useState<Record<number, number>>({});
  const [fixingId, setFixingId] = useState<number | null>(null);
  const [fixErrorById, setFixErrorById] = useState<Record<number, string>>({});
  const [flagSavingId, setFlagSavingId] = useState<number | null>(null);
  const [queuePending, setQueuePending] = useState(0);

  const searchByIdRef = useRef(searchById);
  const queueRef = useRef<number[]>([]);
  const activeRef = useRef(0);
  const queuedSetRef = useRef(new Set<number>());

  useEffect(() => {
    searchByIdRef.current = searchById;
  }, [searchById]);

  useEffect(() => {
    let cancelled = false;
    setLoading(true);
    api.getPaintingCheckup()
      .then((data) => {
        if (!cancelled) {
          setRows(data.paintings);
          setError(null);
        }
      })
      .catch(() => {
        if (!cancelled) setError('Could not load checkup data. Is the server running?');
      })
      .finally(() => {
        if (!cancelled) setLoading(false);
      });
    return () => {
      cancelled = true;
    };
  }, []);

  const updateQueuePending = useCallback(() => {
    setQueuePending(queueRef.current.length + activeRef.current);
  }, []);

  const runSearch = useCallback(
    (rowId: number) => {
      activeRef.current += 1;
      updateQueuePending();
      setSearchById((prev) => ({ ...prev, [rowId]: { status: 'loading' } }));

      api.getPaintingDebugImageSearch(rowId)
        .then((result) => {
          setSearchById((prev) => ({ ...prev, [rowId]: { status: 'done', result } }));
        })
        .catch(() => {
          queuedSetRef.current.delete(rowId);
          setSearchById((prev) => ({ ...prev, [rowId]: { status: 'error' } }));
        })
        .finally(() => {
          activeRef.current = Math.max(0, activeRef.current - 1);
          updateQueuePending();
          pumpQueueRef.current();
        });
    },
    [updateQueuePending]
  );

  const pumpQueue = useCallback(() => {
    while (activeRef.current < SEARCH_CONCURRENCY && queueRef.current.length > 0) {
      const rowId = queueRef.current.shift()!;
      runSearch(rowId);
    }
    updateQueuePending();
  }, [runSearch, updateQueuePending]);

  const pumpQueueRef = useRef(pumpQueue);
  useEffect(() => {
    pumpQueueRef.current = pumpQueue;
  }, [pumpQueue]);

  const enqueueSearch = useCallback(
    (rowId: number) => {
      const state = searchByIdRef.current[rowId];
      if (!needsSearch(state)) return;
      if (queuedSetRef.current.has(rowId)) return;

      queuedSetRef.current.add(rowId);
      setSearchById((prev) => ({ ...prev, [rowId]: { status: 'idle' } }));
      queueRef.current.push(rowId);
      updateQueuePending();
      pumpQueueRef.current();
    },
    [updateQueuePending]
  );

  const retrySearch = useCallback(
    (rowId: number) => {
      queuedSetRef.current.delete(rowId);
      queueRef.current = queueRef.current.filter((id) => id !== rowId);
      setSearchById((prev) => ({ ...prev, [rowId]: { status: 'idle' } }));
      enqueueSearch(rowId);
    },
    [enqueueSearch]
  );

  const handleFix = useCallback(
    async (row: PaintingCheckupRow) => {
      const searchState = searchById[row.id];
      if (searchState?.status !== 'done' || !searchState.result.imageUrl || fixingId) return;

      setFixingId(row.id);
      setFixErrorById((prev) => {
        const next = { ...prev };
        delete next[row.id];
        return next;
      });

      try {
        const updated = await api.fixPaintingImage(row.id, searchState.result.imageUrl);
        setImageVersionById((prev) => ({ ...prev, [row.id]: (prev[row.id] ?? 0) + 1 }));
        setRows((list) =>
          list.map((r) =>
            r.id === row.id
              ? {
                  ...r,
                  gallery_file: updated.imagePath,
                  detail_file: updated.imagePath,
                  gallery_preview: updated.thumbnailPath,
                  detail_preview: updated.thumbnailPath,
                  gallery_file_exists: true,
                  detail_file_exists: true,
                  detail_on_demand: false,
                  fixed: true,
                  checked: true,
                }
              : r
          )
        );
      } catch (err) {
        setFixErrorById((prev) => ({
          ...prev,
          [row.id]: err instanceof Error ? err.message : 'Could not replace image.',
        }));
      } finally {
        setFixingId(null);
      }
    },
    [fixingId, searchById]
  );

  const updateRowFlags = useCallback(
    async (rowId: number, flags: { checked?: boolean; fixed?: boolean }) => {
      setFlagSavingId(rowId);
      try {
        const updated = await api.updatePaintingCheckupFlags(rowId, flags);
        setRows((list) =>
          list.map((r) =>
            r.id === rowId ? { ...r, checked: updated.checked, fixed: updated.fixed } : r
          )
        );
      } catch (err) {
        setError(err instanceof Error ? err.message : 'Could not save checkup flags.');
      } finally {
        setFlagSavingId(null);
      }
    },
    []
  );

  const filtered = useMemo(() => {
    const q = filter.trim().toLowerCase();
    return rows.filter((row) => {
      if (q) {
        const matchesText =
          row.title.toLowerCase().includes(q) ||
          row.artist_name.toLowerCase().includes(q) ||
          String(row.year ?? '').includes(q) ||
          (row.gallery_file ?? '').toLowerCase().includes(q) ||
          row.detail_file.toLowerCase().includes(q);
        if (!matchesText) return false;
      }
      if (filterChecked === 'yes' && !row.checked) return false;
      if (filterChecked === 'no' && row.checked) return false;
      if (filterFixed === 'yes' && !row.fixed) return false;
      if (filterFixed === 'no' && row.fixed) return false;
      return true;
    });
  }, [rows, filter, filterChecked, filterFixed]);

  const handleSearchVisible = useCallback(() => {
    for (const row of filtered) {
      enqueueSearch(row.id);
    }
  }, [filtered, enqueueSearch]);

  const visibleNeedSearch = useMemo(
    () => filtered.filter((row) => needsSearch(searchById[row.id])).length,
    [filtered, searchById]
  );

  const searchStats = useMemo(() => {
    let done = 0;
    let failed = 0;
    for (const row of filtered) {
      const state = searchById[row.id];
      if (state?.status === 'done') done += 1;
      if (state?.status === 'error') failed += 1;
    }
    return { done, failed };
  }, [filtered, searchById]);

  const stats = useMemo(() => {
    const noGallery = rows.filter((r) => !r.gallery_file).length;
    const onDemand = rows.filter((r) => r.detail_on_demand).length;
    const missingGallery = rows.filter((r) => r.gallery_file && !r.gallery_file_exists).length;
    const missingDetail = rows.filter((r) => r.detail_file_exists === false).length;
    const checked = rows.filter((r) => r.checked).length;
    const fixed = rows.filter((r) => r.fixed).length;
    return { noGallery, onDemand, missingGallery, missingDetail, checked, fixed };
  }, [rows]);

  return (
    <div className="checkup-page">
      <header className="checkup-header">
        <button type="button" className="checkup-back-btn" onClick={onBack}>
          ← Back to timeline
        </button>
        <div className="checkup-title-block">
          <h1>Painting checkup</h1>
          <p>
            Compare gallery vs detail images, then use Search visible to fetch reference images.
          </p>
        </div>
      </header>

      <div className="checkup-toolbar">
        <input
          type="search"
          className="checkup-filter"
          placeholder="Filter by title, artist, year…"
          value={filter}
          onChange={(e) => setFilter(e.target.value)}
        />
        <button
          type="button"
          className="checkup-search-visible-btn"
          onClick={handleSearchVisible}
          disabled={loading || filtered.length === 0 || visibleNeedSearch === 0}
          title="Run Google-family image search for all rows currently shown in the table"
        >
          Search visible{visibleNeedSearch > 0 ? ` (${visibleNeedSearch})` : ''}
        </button>
        <label className="checkup-filter-select-wrap">
          <span>Checked</span>
          <select
            className="checkup-filter-select"
            value={filterChecked}
            onChange={(e) => setFilterChecked(e.target.value as FlagFilter)}
          >
            <option value="all">All</option>
            <option value="yes">Checked</option>
            <option value="no">Not checked</option>
          </select>
        </label>
        <label className="checkup-filter-select-wrap">
          <span>Fixed</span>
          <select
            className="checkup-filter-select"
            value={filterFixed}
            onChange={(e) => setFilterFixed(e.target.value as FlagFilter)}
          >
            <option value="all">All</option>
            <option value="yes">Fixed</option>
            <option value="no">Not fixed</option>
          </select>
        </label>
        <div className="checkup-stats">
          <span>{filtered.length} shown</span>
          <span>{stats.checked} checked</span>
          <span>{stats.fixed} fixed</span>
          <span>
            search {searchStats.done}/{filtered.length} done
            {queuePending > 0 ? ` · ${queuePending} active` : ''}
          </span>
          {searchStats.failed > 0 && <span>{searchStats.failed} search failed</span>}
          <span>{stats.noGallery} no gallery file</span>
          <span>{stats.onDemand} detail on-demand</span>
          <span>{stats.missingGallery} gallery missing</span>
          <span>{stats.missingDetail} detail missing</span>
        </div>
      </div>

      {error && <div className="checkup-error">{error}</div>}

      {loading ? (
        <div className="checkup-loading">Loading paintings…</div>
      ) : (
        <div className="checkup-table-wrap">
          <table className="checkup-table">
            <thead>
              <tr>
                <th>Painting</th>
                <th>Artist</th>
                <th>Year</th>
                <th>Gallery</th>
                <th>Detail</th>
                <th>Search</th>
                <th>Fix</th>
                <th>Checked</th>
                <th>Fixed</th>
              </tr>
            </thead>
            <tbody>
              {filtered.map((row) => {
                const searchState = searchById[row.id];
                const flagsBusy = flagSavingId === row.id;
                return (
                  <tr
                    key={row.id}
                    className={
                      row.checked && row.fixed
                        ? 'checkup-row-done'
                        : row.checked
                          ? 'checkup-row-checked'
                          : undefined
                    }
                  >
                    <td>
                      {onOpenPainting ? (
                        <button
                          type="button"
                          className="checkup-title-link"
                          onClick={() => onOpenPainting(row.id)}
                        >
                          {row.title}
                        </button>
                      ) : (
                        row.title
                      )}
                    </td>
                    <td>{row.artist_name}</td>
                    <td className="checkup-year">{row.year ?? '—'}</td>
                    <td className="checkup-thumb-cell">
                      <ImagePreviewCell
                        row={row}
                        kind="gallery"
                        imageVersion={imageVersionById[row.id] ?? 0}
                      />
                    </td>
                    <td className="checkup-thumb-cell">
                      <ImagePreviewCell
                        row={row}
                        kind="detail"
                        imageVersion={imageVersionById[row.id] ?? 0}
                      />
                    </td>
                    <td className="checkup-thumb-cell">
                      <SearchPreviewCell
                        searchState={searchState}
                        onRetry={() => retrySearch(row.id)}
                      />
                    </td>
                    <td className="checkup-fix-cell-wrap">
                      <FixCell
                        searchState={searchState}
                        fixing={fixingId === row.id}
                        fixError={fixErrorById[row.id] ?? null}
                        onFix={() => handleFix(row)}
                      />
                    </td>
                    <td className="checkup-flag-cell">
                      <FlagCheckbox
                        label="Reviewed"
                        checked={row.checked}
                        disabled={flagsBusy || row.fixed}
                        onChange={(checked) => updateRowFlags(row.id, { checked })}
                      />
                    </td>
                    <td className="checkup-flag-cell">
                      <FlagCheckbox
                        label="Fixed"
                        checked={row.fixed}
                        disabled={flagsBusy}
                        onChange={(fixed) => updateRowFlags(row.id, { fixed })}
                      />
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      )}
    </div>
  );
}
