// Trip listing page — extended for CS 499 Enhancement Two.
//
// The page now drives the algorithm endpoints added to the API:
//   - a search box (server-side relevance scoring)
//   - a sort selector (server-side stable merge sort)
//   - a "Load more" button (cursor-based pagination)
//   - a "Similar trips" panel (hash map + bounded min-heap top-k)
//
// All heavy work happens on the server; this component only manages
// query state and appends pages as they arrive.
import { FormEvent, useCallback, useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import TripCard from '../components/TripCard';
import { Trip } from '../models/trip';
import {
  searchTrips,
  getRecommendations,
  RecommendedTrip,
  SortField
} from '../api/trip-api';
import { useAuth } from '../context/AuthContext';

const PAGE_SIZE = 6;

export default function TripListing() {
  const [trips, setTrips] = useState<Trip[]>([]);
  const [totalMatches, setTotalMatches] = useState(0);
  const [nextCursor, setNextCursor] = useState<string | null>(null);
  const [message, setMessage] = useState('Loading trips...');
  const [isLoading, setIsLoading] = useState(false);

  // Query controls
  const [searchInput, setSearchInput] = useState('');
  const [activeSearch, setActiveSearch] = useState('');
  const [sortBy, setSortBy] = useState<SortField>('name');
  const [order, setOrder] = useState<'asc' | 'desc'>('asc');

  // Recommendations
  const [seedCode, setSeedCode] = useState('');
  const [recommendations, setRecommendations] = useState<RecommendedTrip[]>([]);
  const [recError, setRecError] = useState('');

  const { isLoggedIn } = useAuth();
  const navigate = useNavigate();

  // Loads the first page for the current query (replaces the list).
  const loadFirstPage = useCallback(async (search: string, sort: SortField, ord: 'asc' | 'desc') => {
    setIsLoading(true);
    try {
      const page = await searchTrips({ search, sortBy: sort, order: ord, limit: PAGE_SIZE });
      setTrips(page.results);
      setTotalMatches(page.totalMatches);
      setNextCursor(page.nextCursor);
      setMessage(
        page.totalMatches === 0
          ? (search
            ? `No trips matched "${search}".`
            : 'There were no trips retrieved from the database')
          : ''
      );
    } catch {
      setMessage('Unable to load trips. Is the API running?');
    } finally {
      setIsLoading(false);
    }
  }, []);

  useEffect(() => {
    // A new search/sort context invalidates the recommendation panel:
    // leaving it rendered above fresh results made the page look like
    // the search had done nothing (a stale-state bug found in manual
    // testing and now covered by a regression test).
    setSeedCode('');
    setRecommendations([]);
    setRecError('');
    loadFirstPage(activeSearch, sortBy, order);
  }, [activeSearch, sortBy, order, loadFirstPage]);

  // If the search is cleared while sorted by relevance, relevance no
  // longer means anything (and its option unmounts) — fall back to name.
  useEffect(() => {
    if (!activeSearch && sortBy === 'relevance') {
      setSortBy('name');
      setOrder('asc');
    }
  }, [activeSearch, sortBy]);

  const handleSearchSubmit = (event: FormEvent) => {
    event.preventDefault();
    setActiveSearch(searchInput.trim());
  };

  // Appends the next page using the cursor from the previous response.
  const handleLoadMore = async () => {
    if (!nextCursor) {
      return;
    }
    setIsLoading(true);
    try {
      const page = await searchTrips({
        search: activeSearch,
        sortBy,
        order,
        limit: PAGE_SIZE,
        cursor: nextCursor
      });
      setTrips((previous) => [...previous, ...page.results]);
      setNextCursor(page.nextCursor);
    } catch {
      setMessage('Unable to load more trips.');
    } finally {
      setIsLoading(false);
    }
  };

  const handleRecommend = async (code: string) => {
    setSeedCode(code);
    setRecError('');
    setRecommendations([]);
    if (!code) {
      return;
    }
    try {
      setRecommendations(await getRecommendations(code, 3));
    } catch {
      setRecError('Unable to load recommendations.');
    }
  };

  return (
    <>
      <div className="row align-items-end mb-3">
        {isLoggedIn && (
          <div className="col-auto">
            <button
              type="button"
              className="btn btn-primary"
              onClick={() => navigate('/add-trip')}
            >
              Add Trip
            </button>
          </div>
        )}
        <div className="col-md-4">
          <form onSubmit={handleSearchSubmit}>
            <label htmlFor="trip-search">Search trips</label>
            <div className="input-group">
              <input
                id="trip-search"
                type="search"
                className="form-control"
                placeholder="e.g. reef, family, luxury"
                value={searchInput}
                onChange={(event) => setSearchInput(event.target.value)}
              />
              <button type="submit" className="btn btn-info">
                Search
              </button>
            </div>
          </form>
        </div>
        <div className="col-auto">
          <label htmlFor="trip-sort">Sort by</label>
          <select
            id="trip-sort"
            className="form-select"
            value={`${sortBy}:${order}`}
            onChange={(event) => {
              const [field, direction] = event.target.value.split(':');
              setSortBy(field as SortField);
              setOrder(direction as 'asc' | 'desc');
            }}
          >
            {activeSearch && <option value="relevance:desc">Best match</option>}
            <option value="name:asc">Name (A to Z)</option>
            <option value="name:desc">Name (Z to A)</option>
            <option value="price:asc">Price (low to high)</option>
            <option value="price:desc">Price (high to low)</option>
            <option value="start:asc">Start date (earliest)</option>
            <option value="start:desc">Start date (latest)</option>
          </select>
        </div>
        <div className="col-auto">
          <label htmlFor="trip-similar">Similar to</label>
          <select
            id="trip-similar"
            className="form-select"
            value={seedCode}
            onChange={(event) => handleRecommend(event.target.value)}
          >
            <option value="">Choose a trip...</option>
            {trips.map((trip) => (
              <option key={trip.code} value={trip.code}>
                {trip.name}
              </option>
            ))}
          </select>
        </div>
      </div>

      {seedCode && (
        <div className="mb-4">
          <h5>
            Trips similar to {trips.find((t) => t.code === seedCode)?.name || seedCode}{' '}
            <button
              type="button"
              className="btn btn-sm btn-outline-secondary ms-2"
              onClick={() => handleRecommend('')}
            >
              Clear
            </button>
          </h5>
          {recError && <p>{recError}</p>}
          <div className="row">
            {recommendations.map((trip) => (
              <div className="col-md-4 col-sm-6 col-12 mb-3" key={`rec-${trip.code}`}>
                {/* Surfacing the similarity score makes the ranking
                    inspectable instead of a black box. */}
                <p className="text-muted mb-1">
                  Similarity score: {trip.similarity.toFixed(1)}
                </p>
                <TripCard trip={trip} />
              </div>
            ))}
          </div>
          <hr />
        </div>
      )}

      {message && <p>{message}</p>}
      {totalMatches > 0 && (
        <p className="text-muted">
          Showing {trips.length} of {totalMatches} trip{totalMatches === 1 ? '' : 's'}
          {activeSearch ? ` matching "${activeSearch}"` : ''}
        </p>
      )}
      <div className="row">
        {trips.map((trip) => (
          <div className="col-md-4 col-sm-6 col-12 mb-3" key={trip.code}>
            <TripCard trip={trip} />
          </div>
        ))}
      </div>
      {nextCursor && (
        <div className="row mb-4">
          <div className="col-12 text-center">
            <button
              type="button"
              className="btn btn-outline-info"
              onClick={handleLoadMore}
              disabled={isLoading}
            >
              {isLoading ? 'Loading...' : 'Load more trips'}
            </button>
          </div>
        </div>
      )}
    </>
  );
}
