import React, { useEffect, useRef, useState } from 'react';
import { Select } from 'antd';
import useDebounce from '@/hooks/useDebounce';
import { getLaunchesByCity } from '../service';

export interface LaunchOption {
  id: number;
  name: string;
}

interface LaunchMultiSelectProps {
  cityId?: number;
  value: number[];
  /* Names for ids that may not be in the currently loaded page(s) — e.g.
   * launches restored from a previously saved mapping. Merged into the
   * dropdown's options so their tags still render a real name. */
  valueLabels?: Record<number, string>;
  disabled?: boolean;
  onChange: (ids: number[]) => void;
  /* Fires with every page of launches as it loads, so the parent can build
   * up a full id→name map for launches the user has actually seen/picked —
   * needed since not every launch is loaded up front anymore. */
  onLaunchesLoaded?: (launches: LaunchOption[]) => void;
}

const PAGE_SIZE = 10;

/*
  A city can have hundreds of launches — this loads them 10 at a time via
  scroll instead of fetching the whole city up front, the same pagination
  pattern as CampaignSelect.
 */
const LaunchMultiSelect: React.FC<LaunchMultiSelectProps> = ({
  cityId,
  value,
  valueLabels,
  disabled,
  onChange,
  onLaunchesLoaded,
}) => {
  const [launches, setLaunches] = useState<LaunchOption[]>([]);
  const [loading, setLoading] = useState(false);
  const [searchQuery, setSearchQuery] = useState('');
  const [page, setPage] = useState(1);
  const [hasMore, setHasMore] = useState(false);
  const loadMoreLock = useRef(false);

  const debouncedSearch = useDebounce(searchQuery, 400);

  const fetchLaunches = (search: string, nextPage: number): void => {
    if (!cityId) return;
    setLoading(true);
    getLaunchesByCity(cityId, { search, page: nextPage, pageSize: PAGE_SIZE })
      .then((res: any) => {
        const body = res?.data || {};
        const list: LaunchOption[] = body.data || [];
        setPage(body.page || nextPage);
        setHasMore(!!body.hasMore);
        setLaunches((prev) => (nextPage === 1 ? list : [...prev, ...list]));
        onLaunchesLoaded?.(list);
      })
      .finally(() => {
        setLoading(false);
        loadMoreLock.current = false;
      });
  };

  // Single effect for both `cityId` and `debouncedSearch` — two separate effects
  // both fire on mount for the same page-1/empty-search request, double-calling
  // the API (the exact bug already fixed once in CampaignSelect).
  useEffect(() => {
    if (!cityId) {
      setLaunches([]);
      return;
    }
    fetchLaunches(debouncedSearch, 1);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [cityId, debouncedSearch]);

  const handlePopupScroll = (e: React.UIEvent<HTMLDivElement>): void => {
    const { currentTarget } = e;
    const reachedBottom = currentTarget.scrollTop + currentTarget.clientHeight >= currentTarget.scrollHeight - 5;
    if (reachedBottom && hasMore && !loading && !loadMoreLock.current) {
      loadMoreLock.current = true;
      fetchLaunches(searchQuery, page + 1);
    }
  };

  const loadedIds = new Set(launches.map((l) => l.id));
  const options = [
    ...launches,
    ...value.filter((id) => !loadedIds.has(id)).map((id) => ({ id, name: valueLabels?.[id] || `#${id}` })),
  ].map((l) => ({ value: l.id, label: l.name }));

  return (
    <Select
      className="block mt-1"
      style={{ width: '100%' }}
      mode="multiple"
      showSearch
      allowClear
      loading={loading}
      disabled={disabled || !cityId}
      placeholder={cityId ? 'Select launch(es)' : 'Select a city first'}
      value={value}
      filterOption={false}
      onSearch={setSearchQuery}
      onPopupScroll={handlePopupScroll}
      onChange={(ids: number[]) => onChange(ids)}
      options={options}
    />
  );
};

export default LaunchMultiSelect;
