import { useEffect, useMemo, useRef, useState } from "react";
import type { KeyboardEvent as ReactKeyboardEvent } from "react";
import type { GraphDataset } from "../../types/medical";
import { normalizeSearchText } from "../../utils/text.ts";
import type { APITest } from "../../services/medical";
import { parseTestSearchQuery } from "../../services/medical";
import { searchDatasetsByName } from "./searchDatasets";
import {
  formatParsedQueryRange,
  formatParsedResponse,
  resolveDatasetsForTest,
  shouldParseSearchQuery,
  isDateWithinRange,
  type DateRange,
  type ParsedQueryView,
  type ParsedTestQuery,
} from "./graphHelpers.ts";

const PARSE_CACHE = new Map<string, ParsedQueryView>();

export type SearchResultItem =
  | {
      id: string;
      kind: "test";
      title: string;
      subtitle: string;
      test: APITest;
      datasets: GraphDataset[];
    }
  | {
      id: string;
      kind: "parsed";
      title: string;
      subtitle: string;
      queries: ParsedTestQuery[];
    }
  | {
      id: string;
      kind: "dataset";
      dataset: GraphDataset;
    };

type UseGraphSearchParams = {
  userId?: string;
  availableTests: APITest[];
  availableDatasets: GraphDataset[];
  dateRange: DateRange;
};

export function useGraphSearch({ userId, availableTests, availableDatasets, dateRange }: UseGraphSearchParams) {
  const [searchQuery, setSearchQuery] = useState("");
  const [showResults, setShowResults] = useState(false);
  const [activeSearchResultIndex, setActiveSearchResultIndex] = useState<number | null>(null);
  const [parsedQueryResult, setParsedQueryResult] = useState<ParsedQueryView | null>(null);
  const [isParsingQuery, setIsParsingQuery] = useState(false);
  const [parseQueryError, setParseQueryError] = useState<string | null>(null);
  const searchInputRef = useRef<HTMLInputElement | null>(null);
  const parseRequestVersionRef = useRef(0);

  const testSearchResults = useMemo<SearchResultItem[]>(() => {
    const query = normalizeSearchText(searchQuery);
    
    // First filter tests by date range
    const testsInRange = availableTests.filter((test) => isDateWithinRange(test.date, dateRange));

    // Group by name to show unique test types and their combined subtests in the range
    const groupMap = new Map<string, { test: APITest; datasets: GraphDataset[]; subtests: Map<string, any> }>();

    testsInRange.forEach((test) => {
      const normalizedName = normalizeSearchText(test.name);
      const testId = normalizeSearchText(test.testId);
      const sutCode = normalizeSearchText(test.sutCode);

      const matchesQuery = !query || 
        normalizedName.includes(query) || 
        testId.includes(query) || 
        sutCode.includes(query);

      if (!matchesQuery) return;

      if (!groupMap.has(test.name)) {
        groupMap.set(test.name, {
          test: { ...test, subtests: [] },
          datasets: resolveDatasetsForTest(availableDatasets, test._id),
          subtests: new Map(),
        });
      }

      const group = groupMap.get(test.name)!;
      
      // Merge subtests
      (test.subtests || []).forEach(st => {
        if (!group.subtests.has(st.name)) {
          group.subtests.set(st.name, st);
          group.test.subtests.push(st);
        }
      });

      // Merge datasets
      const newDatasets = resolveDatasetsForTest(availableDatasets, test._id);
      newDatasets.forEach(ds => {
        if (!group.datasets.find(existing => existing.id === ds.id)) {
          group.datasets.push(ds);
        }
      });
    });

    return Array.from(groupMap.entries())
      .map(([name, group]) => ({
        id: `test-group-${name}`,
        kind: "test" as const,
        title: name,
        subtitle: `${group.test.subtests.length} subtest${group.test.subtests.length === 1 ? "" : "s"}`,
        test: group.test,
        datasets: group.datasets,
      }))
      .sort((a, b) => a.title.localeCompare(b.title));
  }, [availableTests, searchQuery, availableDatasets, dateRange]);

  const searchResults = useMemo(() => {
    const baseResults = !searchQuery.trim()
      ? [...availableDatasets].sort((a, b) => a.name.localeCompare(b.name))
      : searchDatasetsByName(availableDatasets, searchQuery);

    // Filter datasets to only those that have at least one point in the range
    return baseResults.filter(ds => ds.data.some(point => isDateWithinRange(point.date, dateRange)));
  }, [availableDatasets, searchQuery, dateRange]);

  const parsedSearchResult = useMemo<SearchResultItem | null>(() => {
    if (!parsedQueryResult || parsedQueryResult.testQueries.length === 0) {
      return null;
    }

    const validQueries = parsedQueryResult.testQueries.filter((query) => {
      const normalizedParam = normalizeSearchText(query.test_parameter);
      if (!normalizedParam) return false;


      const test_id = query.test_id;
      // Check by ID first
      if (test_id && availableDatasets.some(ds => ds.sourceTestIds?.includes(test_id))) {
        return true;
      }

      // Check for exact match with dataset names
      return availableDatasets.some(ds => normalizeSearchText(ds.name) === normalizedParam);
    });

    if (validQueries.length === 0) {
      return null;
    }

    const summaryParts = validQueries
      .map((item) => item.test_parameter)
      .filter((value) => value.trim().length > 0);

    return {
      id: `parsed-${parsedQueryResult.rawQuery || searchQuery}`,
      kind: "parsed",
      title: summaryParts.length > 0 ? summaryParts.join(", ") : "Parsed query",
      subtitle: parsedQueryResult.rawQuery || searchQuery,
      queries: validQueries,
    };
  }, [availableDatasets, parsedQueryResult, searchQuery]);

  const searchResultItems = useMemo<SearchResultItem[]>(() => {
    const datasetItems = searchResults.map((dataset) => ({
      id: dataset.id,
      kind: "dataset" as const,
      dataset,
    }));

    if (!parsedSearchResult) {
      return [...testSearchResults, ...datasetItems];
    }

    return [parsedSearchResult, ...testSearchResults, ...datasetItems];
  }, [parsedSearchResult, searchResults, testSearchResults]);

  useEffect(() => {
    setActiveSearchResultIndex(null);
  }, [searchQuery, showResults]);

  useEffect(() => {
    const query = searchQuery.trim();
    const requestVersion = ++parseRequestVersionRef.current;

    if (!shouldParseSearchQuery(query) || !userId) {
      setParsedQueryResult(null);
      setParseQueryError(null);
      setIsParsingQuery(false);
      return;
    }

    const controller = new AbortController();
    const timeoutId = window.setTimeout(async () => {
      try {
        if (requestVersion !== parseRequestVersionRef.current) return;
        if (PARSE_CACHE.has(query)) {
          setParsedQueryResult(PARSE_CACHE.get(query)!);
          return;
        }

        setIsParsingQuery(true);
        setParseQueryError(null);

        const payload = await parseTestSearchQuery(userId, query, controller.signal);
        if (requestVersion !== parseRequestVersionRef.current) return;
        const formatted = formatParsedResponse(payload);
        PARSE_CACHE.set(query, formatted);
        setParsedQueryResult(formatted);
      } catch (error) {
        if ((error as Error).name === "AbortError") return;
        if (requestVersion !== parseRequestVersionRef.current) return;
        setParsedQueryResult(null);
        setParseQueryError("Unable to parse query right now");
      } finally {
        if (requestVersion === parseRequestVersionRef.current) {
          setIsParsingQuery(false);
        }
      }
    }, 300);

    return () => {
      controller.abort();
      window.clearTimeout(timeoutId);
    };
  }, [searchQuery, userId]);

  const focusSearchResult = (nextIndex: number) => {
    if (searchResultItems.length === 0) return;

    const clampedIndex = Math.max(0, Math.min(nextIndex, searchResultItems.length - 1));
    setShowResults(true);
    setActiveSearchResultIndex(clampedIndex);
  };

  const handleSearchInputKeyDown = (event: ReactKeyboardEvent<HTMLInputElement>) => {
    if (event.key !== "ArrowDown" || searchResultItems.length === 0) return;

    event.preventDefault();
    focusSearchResult(0);
  };

  const handleSearchResultKeyDown = (event: ReactKeyboardEvent<HTMLButtonElement>, index: number) => {
    if (event.key === "ArrowDown") {
      event.preventDefault();
      focusSearchResult(index + 1);
      return;
    }

    if (event.key === "ArrowUp") {
      event.preventDefault();
      if (index === 0) {
        setActiveSearchResultIndex(null);
        searchInputRef.current?.focus();
        return;
      }

      focusSearchResult(index - 1);
    }
  };

  return {
    searchQuery,
    setSearchQuery,
    showResults,
    setShowResults,
    activeSearchResultIndex,
    searchInputRef,
    parsedQueryResult,
    isParsingQuery,
    parseQueryError,
    searchResultItems,
    handleSearchInputKeyDown,
    handleSearchResultKeyDown,
    formatParsedQueryRange,
  };
}