import { EntryId, MaybeString, SupportedEntry, isOntimeEvent, isOntimeGroup, isOntimeMilestone } from 'ontime-types';
import { ChangeEvent, useCallback, useEffect, useRef, useState } from 'react';

import { useFlatRundown } from '../../../common/hooks-query/useRundown';
import { useSelectAndRevealEntry } from '../../../features/rundown/useSelectAndRevealEntry';

const maxResults = 12;

type FilterableGroup = {
  type: SupportedEntry.Group;
  id: EntryId;
  index: number;
  title: string;
  colour: string;
};

type FilterableEvent = {
  type: SupportedEntry.Event;
  id: EntryId;
  index: number;
  eventIndex: number;
  title: string;
  cue: string;
  colour: string;
  parent: MaybeString;
};

type FilterableMilestone = {
  type: SupportedEntry.Milestone;
  id: EntryId;
  index: number;
  title: string;
  cue: string;
  colour: string;
  parent: MaybeString;
};

type FilterableEntry = FilterableGroup | FilterableEvent | FilterableMilestone;

export default function useFinder() {
  const { data, rundownId } = useFlatRundown();
  const [results, setResults] = useState<FilterableEntry[]>([]);
  const [error, setError] = useState<MaybeString>(null);
  const lastSearchString = useRef('');

  const selectAndRevealEntry = useSelectAndRevealEntry(rundownId);

  /** Filters the rundown to a given evaluation */
  const find = useCallback(
    (event: ChangeEvent<HTMLInputElement>) => {
      if (!data || data.length === 0) {
        setError('No data');
        return;
      }
      setError(null);

      if (event.target.value === '') {
        setResults([]);
        return;
      }

      const searchValue = event.target.value.toLowerCase();
      lastSearchString.current = searchValue;

      if (searchValue.startsWith('index ')) {
        const searchString = searchValue.slice('index '.length).trim();
        const { results, error } = searchByIndex(searchString);
        setResults(results);
        setError(error);
        return;
      }

      if (searchValue.startsWith('cue ')) {
        const searchString = searchValue.slice('cue '.length).trim();
        const { results, error } = searchByCue(searchString);
        setResults(results);
        setError(error);
        return;
      }

      const searchString = searchValue.startsWith('title ') ? searchValue.slice('title '.length).trim() : searchValue;
      const { results, error } = searchByTitle(searchString);
      setResults(results);
      setError(error);

      /** Returns a single item with a matching index */
      function searchByIndex(searchString: string) {
        const searchIndex = Number(searchString);
        if (isNaN(searchIndex) || searchIndex < 1) {
          return { results: [], error: 'Invalid index' };
        }

        if (searchIndex > data.length) {
          return { results: [], error: null };
        }

        // indexes exposed to the UI are 1-based
        let eventIndex = 1;
        const results: FilterableEvent[] = [];
        for (let i = 0; i < data.length; i++) {
          const event = data[i];
          if (isOntimeEvent(event)) {
            if (eventIndex === searchIndex) {
              results.push({
                type: SupportedEntry.Event,
                id: event.id,
                index: i,
                eventIndex,
                title: event.title,
                cue: event.cue,
                colour: event.colour,
                parent: event.parent,
              } satisfies FilterableEvent);
              break;
            }
            eventIndex++;
          }
        }

        return { results, error: null };
      }

      /** Returns maxResults of OntimeEvents that match the cue field */
      function searchByCue(searchString: string) {
        // indexes exposed to the UI are 1-based
        let eventIndex = 1;
        // limit amount of results we show
        let remaining = maxResults;
        const results: FilterableEvent[] = [];

        for (let i = 0; i < data.length; i++) {
          if (remaining <= 0) {
            break;
          }
          const event = data[i];
          if (isOntimeEvent(event)) {
            if (event.cue.toLowerCase().includes(searchString)) {
              remaining--;
              results.push({
                type: SupportedEntry.Event,
                id: event.id,
                index: i,
                eventIndex,
                title: event.title,
                cue: event.cue,
                colour: event.colour,
                parent: event.parent,
              } satisfies FilterableEvent);
            }
            eventIndex++;
          }
        }
        return { results, error: null };
      }

      /** Returns maxResults of OntimeEvents that match the title field*/
      function searchByTitle(searchString: string) {
        // indexes exposed to the UI are 1-based
        let eventIndex = 1;
        // limit amount of results we show
        let remaining = maxResults;
        const results: FilterableEntry[] = [];

        for (let i = 0; i < data.length; i++) {
          if (remaining <= 0) {
            break;
          }

          const entry = data[i];
          if (isOntimeEvent(entry)) {
            if (entry.title.toLowerCase().includes(searchString)) {
              remaining--;
              results.push({
                type: SupportedEntry.Event,
                id: entry.id,
                index: i,
                eventIndex,
                title: entry.title,
                cue: entry.cue,
                colour: entry.colour,
                parent: entry.parent,
              } satisfies FilterableEvent);
            }
            eventIndex++;
          } else if (isOntimeGroup(entry)) {
            if (entry.title.toLowerCase().includes(searchString)) {
              remaining--;
              results.push({
                type: SupportedEntry.Group,
                id: entry.id,
                index: i,
                title: entry.title,
                colour: entry.colour,
              } satisfies FilterableGroup);
            }
          } else if (isOntimeMilestone(entry)) {
            if (entry.title.toLowerCase().includes(searchString)) {
              remaining--;
              results.push({
                type: SupportedEntry.Milestone,
                id: entry.id,
                index: i,
                title: entry.title,
                cue: entry.cue,
                colour: entry.colour,
                parent: entry.parent,
              } satisfies FilterableMilestone);
            }
          }
        }
        return { results, error: null };
      }
    },
    [data],
  );

  const select = useCallback(
    (selectedEvent: FilterableEntry) => {
      selectAndRevealEntry({
        id: selectedEvent.id,
        index: selectedEvent.index,
        parent: 'parent' in selectedEvent ? selectedEvent.parent : null,
      });
    },
    [selectAndRevealEntry],
  );

  /** clear results when source data changes */
  useEffect(() => {
    setResults([]);
    setError(null);
    // fake a submit event to re-run the search
    if (lastSearchString.current) {
      find({ target: { value: lastSearchString.current } } as ChangeEvent<HTMLInputElement>);
    }
  }, [data, find]);

  return { find, select, results, error };
}
