import { memo, useState, useMemo, useRef, useCallback, useEffect, type FormEvent } from 'react';
import { useTranslation } from 'react-i18next';

import type { FeatureMeta, FeatureFilters } from '../../types';
import { findActiveFilterElement } from '../../lib/active-filter-scroll';
import { buildPercentileScale } from '../../lib/format';
import type { PercentileScale } from '../../lib/format';
import { useCollapsibleGroups } from '../../hooks/useCollapsibleGroups';
import InfoPopup from '../ui/InfoPopup';
import { FeatureInfoPopup } from '../ui/FeatureInfoPopup';
import type { AiFilterErrorType } from '../../hooks/useAiFilters';
import { type TransportMode, type TravelTimeEntry } from '../../hooks/useTravelTime';
import { ActiveFiltersPanel } from './filters/ActiveFiltersPanel';
import { AddFilterPanel } from './filters/AddFilterPanel';
import { ClearFiltersDialog } from './filters/ClearFiltersDialog';
import {
  SPECIFIC_CRIMES_FILTER_NAME,
  getDefaultSpecificCrimeFeatureName,
  getSpecificCrimeFeatureName,
  getSpecificCrimeFilterMeta,
  isSpecificCrimeFeatureName,
  isSpecificCrimeFilterName,
} from '../../lib/crime-filter';
import {
  ELECTION_VOTE_SHARE_FILTER_NAME,
  getDefaultElectionVoteShareFeatureName,
  getElectionVoteShareFeatureName,
  getElectionVoteShareFilterMeta,
  isElectionVoteShareFeatureName,
  isElectionVoteShareFilterName,
} from '../../lib/election-filter';
import {
  ETHNICITIES_FILTER_NAME,
  getDefaultEthnicityFeatureName,
  getEthnicityFeatureName,
  getEthnicityFilterMeta,
  isEthnicityFeatureName,
  isEthnicityFilterName,
} from '../../lib/ethnicity-filter';
import {
  SCHOOL_FILTER_NAME,
  getDefaultSchoolFeatureName,
  getSchoolBackendFeatureName,
  getSchoolFilterMeta,
  isSchoolFilterName,
} from '../../lib/school-filter';
import {
  POI_FILTER_NAMES,
  POI_DISTANCE_FILTER_NAME,
  TRANSPORT_DISTANCE_FILTER_NAME,
  POI_COUNT_2KM_FILTER_NAME,
  POI_COUNT_5KM_FILTER_NAME,
  getDefaultPoiDistanceFeatureName,
  getDefaultPoiFilterFeatureName,
  getPoiDistanceFeatureName,
  getPoiFilterMeta,
  getPoiDistanceFilterMeta,
  getPoiFilterName,
  isPoiDistanceFilterName,
  isPoiFilterFeatureName,
  type PoiFilterName,
} from '../../lib/poi-distance-filter';

interface FiltersProps {
  features: FeatureMeta[];
  filters: FeatureFilters;
  activeFeature: string | null;
  dragValue: [number, number] | null;
  enabledFeatures: Set<string>;
  onAddFilter: (name: string) => void;
  onRemoveFilter: (name: string) => void;
  onFilterChange: (name: string, value: [number, number] | string[]) => void;
  onDragStart: (name: string, initialValue?: [number, number]) => void;
  onDragChange: (value: [number, number]) => void;
  onDragEnd: () => void;
  pinnedFeature: string | null;
  onTogglePin: (name: string) => void;
  onNavigateToSource?: (slug: string, featureName: string) => void;
  openInfoFeature?: string | null;
  onClearOpenInfoFeature?: () => void;
  travelTimeEntries: TravelTimeEntry[];
  onTravelTimeAddEntry: (mode: TransportMode) => void;
  onTravelTimeRemoveEntry: (index: number) => void;
  onTravelTimeSetDestination: (
    index: number,
    slug: string,
    label: string,
    lat: number,
    lon: number
  ) => void;
  onTravelTimeRangeChange: (index: number, range: [number, number]) => void;
  onTravelTimeDragEnd: (index: number) => void;
  onTravelTimeToggleBest: (index: number) => void;
  onTravelTimeToggleNoChange: (index: number) => void;
  onTravelTimeToggleOneChange: (index: number) => void;
  onTravelTimeToggleNoBuses: (index: number) => void;
  aiFilterLoading: boolean;
  aiFilterError: string | null;
  aiFilterErrorType: AiFilterErrorType | null;
  aiFilterNotes: string | null;
  aiFilterSummary: string | null;
  onAiFilterSubmit: (query: string) => void;
  isLoggedIn: boolean;
  onLoginRequired: () => void;
  isLicensed: boolean;
  onUpgradeClick?: () => void;
  onResetTutorial?: () => void;
  filterImpacts?: Record<string, number>;
  onClearAll: () => void;
  onSaveSearch?: (name: string) => Promise<void>;
  savingSearch?: boolean;
  editingSearchName?: string | null;
  onUpdateSearch?: () => Promise<void>;
  onExitEditing?: () => void;
  destinationDropdownPortal?: boolean;
}

export default memo(function Filters({
  features,
  filters,
  activeFeature,
  dragValue,
  enabledFeatures,
  onAddFilter,
  onRemoveFilter,
  onFilterChange,
  onDragStart,
  onDragChange,
  onDragEnd,
  pinnedFeature,
  onTogglePin,
  onNavigateToSource,
  openInfoFeature,
  onClearOpenInfoFeature,
  travelTimeEntries,
  onTravelTimeAddEntry,
  onTravelTimeRemoveEntry,
  onTravelTimeSetDestination,
  onTravelTimeRangeChange,
  onTravelTimeDragEnd,
  onTravelTimeToggleBest,
  onTravelTimeToggleNoChange,
  onTravelTimeToggleOneChange,
  onTravelTimeToggleNoBuses,
  aiFilterLoading,
  aiFilterError,
  aiFilterErrorType,
  aiFilterNotes,
  aiFilterSummary,
  onAiFilterSubmit,
  isLoggedIn,
  onLoginRequired,
  isLicensed,
  onUpgradeClick,
  onResetTutorial,
  filterImpacts,
  onClearAll,
  onSaveSearch,
  savingSearch,
  editingSearchName,
  onUpdateSearch,
  onExitEditing,
  destinationDropdownPortal = true,
}: FiltersProps) {
  const { t } = useTranslation();

  const defaultSchoolFeatureName = useMemo(() => getDefaultSchoolFeatureName(features), [features]);
  const schoolMeta = useMemo(() => getSchoolFilterMeta(features), [features]);
  const defaultSpecificCrimeFeatureName = useMemo(
    () => getDefaultSpecificCrimeFeatureName(features),
    [features]
  );
  const specificCrimeMeta = useMemo(() => getSpecificCrimeFilterMeta(features), [features]);
  const defaultElectionVoteShareFeatureName = useMemo(
    () => getDefaultElectionVoteShareFeatureName(features),
    [features]
  );
  const electionVoteShareMeta = useMemo(() => getElectionVoteShareFilterMeta(features), [features]);
  const defaultEthnicityFeatureName = useMemo(
    () => getDefaultEthnicityFeatureName(features),
    [features]
  );
  const ethnicityMeta = useMemo(() => getEthnicityFilterMeta(features), [features]);
  const defaultPoiDistanceFeatureName = useMemo(
    () => getDefaultPoiDistanceFeatureName(features),
    [features]
  );
  const defaultTransportDistanceFeatureName = useMemo(
    () => getDefaultPoiFilterFeatureName(features, TRANSPORT_DISTANCE_FILTER_NAME),
    [features]
  );
  const defaultPoiCount2KmFeatureName = useMemo(
    () => getDefaultPoiFilterFeatureName(features, POI_COUNT_2KM_FILTER_NAME),
    [features]
  );
  const defaultPoiCount5KmFeatureName = useMemo(
    () => getDefaultPoiFilterFeatureName(features, POI_COUNT_5KM_FILTER_NAME),
    [features]
  );
  const poiDistanceMeta = useMemo(() => getPoiDistanceFilterMeta(features), [features]);
  const transportDistanceMeta = useMemo(
    () => getPoiFilterMeta(features, TRANSPORT_DISTANCE_FILTER_NAME),
    [features]
  );
  const poiCount2KmMeta = useMemo(
    () => getPoiFilterMeta(features, POI_COUNT_2KM_FILTER_NAME),
    [features]
  );
  const poiCount5KmMeta = useMemo(
    () => getPoiFilterMeta(features, POI_COUNT_5KM_FILTER_NAME),
    [features]
  );
  const poiFilterMetas = useMemo(
    () => ({
      [POI_DISTANCE_FILTER_NAME]: poiDistanceMeta,
      [TRANSPORT_DISTANCE_FILTER_NAME]: transportDistanceMeta,
      [POI_COUNT_2KM_FILTER_NAME]: poiCount2KmMeta,
      [POI_COUNT_5KM_FILTER_NAME]: poiCount5KmMeta,
    }),
    [poiDistanceMeta, transportDistanceMeta, poiCount2KmMeta, poiCount5KmMeta]
  );
  const defaultPoiFilterFeatureNames = useMemo(
    () => ({
      [POI_DISTANCE_FILTER_NAME]: defaultPoiDistanceFeatureName,
      [TRANSPORT_DISTANCE_FILTER_NAME]: defaultTransportDistanceFeatureName,
      [POI_COUNT_2KM_FILTER_NAME]: defaultPoiCount2KmFeatureName,
      [POI_COUNT_5KM_FILTER_NAME]: defaultPoiCount5KmFeatureName,
    }),
    [
      defaultPoiDistanceFeatureName,
      defaultTransportDistanceFeatureName,
      defaultPoiCount2KmFeatureName,
      defaultPoiCount5KmFeatureName,
    ]
  );
  const schoolFilterItems = useMemo(() => {
    return Object.keys(filters)
      .filter(isSchoolFilterName)
      .map((name) => {
        const backendName = getSchoolBackendFeatureName(name);
        const backendFeature = backendName
          ? features.find((feature) => feature.name === backendName)
          : undefined;
        return { ...(backendFeature ?? schoolMeta), name, group: 'Schools' };
      });
  }, [filters, features, schoolMeta]);
  const specificCrimeFilterItems = useMemo(() => {
    return Object.keys(filters)
      .filter(isSpecificCrimeFilterName)
      .map((name) => {
        const backendName = getSpecificCrimeFeatureName(name);
        const backendFeature = backendName
          ? features.find((feature) => feature.name === backendName)
          : undefined;
        return { ...(backendFeature ?? specificCrimeMeta), name, group: 'Crime' };
      });
  }, [filters, features, specificCrimeMeta]);
  const electionVoteShareFilterItems = useMemo(() => {
    return Object.keys(filters)
      .filter(isElectionVoteShareFilterName)
      .map((name) => {
        const backendName = getElectionVoteShareFeatureName(name);
        const backendFeature = backendName
          ? features.find((feature) => feature.name === backendName)
          : undefined;
        return { ...(backendFeature ?? electionVoteShareMeta), name, group: 'Neighbours' };
      });
  }, [filters, features, electionVoteShareMeta]);
  const ethnicityFilterItems = useMemo(() => {
    return Object.keys(filters)
      .filter(isEthnicityFilterName)
      .map((name) => {
        const backendName = getEthnicityFeatureName(name);
        const backendFeature = backendName
          ? features.find((feature) => feature.name === backendName)
          : undefined;
        return { ...(backendFeature ?? ethnicityMeta), name, group: 'Neighbours' };
      });
  }, [filters, features, ethnicityMeta]);
  const poiDistanceFilterItems = useMemo(() => {
    return Object.keys(filters)
      .filter(isPoiDistanceFilterName)
      .map((name) => {
        const backendName = getPoiDistanceFeatureName(name);
        const filterName = getPoiFilterName(name) ?? POI_DISTANCE_FILTER_NAME;
        const backendFeature = backendName
          ? features.find((feature) => feature.name === backendName)
          : undefined;
        return {
          ...(backendFeature ?? poiFilterMetas[filterName]),
          name,
          group: poiFilterMetas[filterName].group,
        };
      });
  }, [filters, features, poiFilterMetas]);
  const availableFeatures = useMemo(() => {
    const result: FeatureMeta[] = [];
    let insertedSchoolFilter = false;
    let insertedSpecificCrimeFilter = false;
    let insertedElectionVoteShareFilter = false;
    let insertedEthnicityFilter = false;
    const insertedPoiFilters = new Set<PoiFilterName>();
    const maybeInsertPoiFilter = (filterName: PoiFilterName | null) => {
      if (
        filterName &&
        defaultPoiFilterFeatureNames[filterName] &&
        !insertedPoiFilters.has(filterName)
      ) {
        result.push(poiFilterMetas[filterName]);
        insertedPoiFilters.add(filterName);
      }
    };

    for (const feature of features) {
      if (feature.group === 'Transport') {
        maybeInsertPoiFilter(TRANSPORT_DISTANCE_FILTER_NAME);
      }
      if (isSchoolFilterName(feature.name)) {
        if (defaultSchoolFeatureName && !insertedSchoolFilter) {
          result.push(schoolMeta);
          insertedSchoolFilter = true;
        }
        continue;
      }
      if (isSpecificCrimeFeatureName(feature.name)) {
        if (defaultSpecificCrimeFeatureName && !insertedSpecificCrimeFilter) {
          result.push(specificCrimeMeta);
          insertedSpecificCrimeFilter = true;
        }
        continue;
      }
      if (isElectionVoteShareFeatureName(feature.name)) {
        if (defaultElectionVoteShareFeatureName && !insertedElectionVoteShareFilter) {
          result.push(electionVoteShareMeta);
          insertedElectionVoteShareFilter = true;
        }
        continue;
      }
      if (isEthnicityFeatureName(feature.name)) {
        if (defaultEthnicityFeatureName && !insertedEthnicityFilter) {
          result.push(ethnicityMeta);
          insertedEthnicityFilter = true;
        }
        continue;
      }
      if (isPoiFilterFeatureName(feature.name)) {
        maybeInsertPoiFilter(getPoiFilterName(feature.name));
        continue;
      }
      if (!enabledFeatures.has(feature.name)) result.push(feature);
    }

    return result;
  }, [
    features,
    enabledFeatures,
    defaultSchoolFeatureName,
    schoolMeta,
    defaultSpecificCrimeFeatureName,
    specificCrimeMeta,
    defaultElectionVoteShareFeatureName,
    electionVoteShareMeta,
    defaultEthnicityFeatureName,
    ethnicityMeta,
    defaultPoiFilterFeatureNames,
    poiFilterMetas,
  ]);
  const enabledFeatureList = useMemo(() => {
    const result: FeatureMeta[] = [];
    let insertedSchoolFilter = false;
    let insertedSpecificCrimeFilters = false;
    let insertedElectionVoteShareFilters = false;
    let insertedEthnicityFilters = false;
    const insertedPoiFilters = new Set<PoiFilterName>();
    const insertPoiFilterItems = (filterName: PoiFilterName | null) => {
      if (!filterName || insertedPoiFilters.has(filterName)) return;
      result.push(
        ...poiDistanceFilterItems.filter((item) => getPoiFilterName(item.name) === filterName)
      );
      insertedPoiFilters.add(filterName);
    };

    for (const feature of features) {
      if (feature.group === 'Transport') {
        insertPoiFilterItems(TRANSPORT_DISTANCE_FILTER_NAME);
      }
      if (isSchoolFilterName(feature.name)) {
        if (!insertedSchoolFilter) {
          result.push(...schoolFilterItems);
          insertedSchoolFilter = true;
        }
        continue;
      }
      if (isSpecificCrimeFeatureName(feature.name)) {
        if (!insertedSpecificCrimeFilters) {
          result.push(...specificCrimeFilterItems);
          insertedSpecificCrimeFilters = true;
        }
        continue;
      }
      if (isElectionVoteShareFeatureName(feature.name)) {
        if (!insertedElectionVoteShareFilters) {
          result.push(...electionVoteShareFilterItems);
          insertedElectionVoteShareFilters = true;
        }
        continue;
      }
      if (isEthnicityFeatureName(feature.name)) {
        if (!insertedEthnicityFilters) {
          result.push(...ethnicityFilterItems);
          insertedEthnicityFilters = true;
        }
        continue;
      }
      if (isPoiFilterFeatureName(feature.name)) {
        insertPoiFilterItems(getPoiFilterName(feature.name));
        continue;
      }
      if (enabledFeatures.has(feature.name)) result.push(feature);
    }

    return result;
  }, [
    features,
    enabledFeatures,
    schoolFilterItems,
    specificCrimeFilterItems,
    electionVoteShareFilterItems,
    ethnicityFilterItems,
    poiDistanceFilterItems,
  ]);

  const containerRef = useRef<HTMLDivElement>(null);
  const scrollRef = useRef<HTMLDivElement>(null);
  const [showPhilosophy, setShowPhilosophy] = useState(false);
  const [activeInfoFeature, setActiveInfoFeature] = useState<FeatureMeta | null>(null);
  const [activeFilterCollapsed, setActiveFilterCollapsed] = useState(false);
  const [addFilterCollapsed, setAddFilterCollapsed] = useState(false);
  const [isActiveFilterGroupExpanded, toggleActiveFilterGroup, expandActiveFilterGroup] =
    useCollapsibleGroups();
  const activeEntryCount = travelTimeEntries.length;

  const pendingScrollRef = useRef<string | null>(null);
  const highlightTimeoutRef = useRef<number | null>(null);

  const queueActiveFilterScroll = useCallback(
    (filterName: string, groupName: string | null | undefined) => {
      if (groupName) expandActiveFilterGroup(groupName);
      pendingScrollRef.current = filterName;
    },
    [expandActiveFilterGroup]
  );

  const getAddFilterGroupName = useCallback(
    (name: string): string | null => {
      if (name === SCHOOL_FILTER_NAME) return schoolMeta.group ?? 'Schools';
      if (name === SPECIFIC_CRIMES_FILTER_NAME) return specificCrimeMeta.group ?? 'Crime';
      if (name === ELECTION_VOTE_SHARE_FILTER_NAME) {
        return electionVoteShareMeta.group ?? 'Neighbours';
      }
      if (name === ETHNICITIES_FILTER_NAME) return ethnicityMeta.group ?? 'Neighbours';
      if (POI_FILTER_NAMES.includes(name as PoiFilterName)) {
        return poiFilterMetas[name as PoiFilterName].group ?? null;
      }
      return features.find((feature) => feature.name === name)?.group ?? null;
    },
    [
      electionVoteShareMeta.group,
      ethnicityMeta.group,
      features,
      poiFilterMetas,
      schoolMeta.group,
      specificCrimeMeta.group,
    ]
  );

  const handleAddAndScroll = useCallback(
    (name: string) => {
      if (name === SCHOOL_FILTER_NAME) {
        if (!defaultSchoolFeatureName) return;
        queueActiveFilterScroll(SCHOOL_FILTER_NAME, getAddFilterGroupName(SCHOOL_FILTER_NAME));
        onAddFilter(SCHOOL_FILTER_NAME);
        return;
      }
      if (name === SPECIFIC_CRIMES_FILTER_NAME) {
        if (!defaultSpecificCrimeFeatureName) return;
        queueActiveFilterScroll(
          SPECIFIC_CRIMES_FILTER_NAME,
          getAddFilterGroupName(SPECIFIC_CRIMES_FILTER_NAME)
        );
        onAddFilter(SPECIFIC_CRIMES_FILTER_NAME);
        return;
      }
      if (name === ELECTION_VOTE_SHARE_FILTER_NAME) {
        if (!defaultElectionVoteShareFeatureName) return;
        queueActiveFilterScroll(
          ELECTION_VOTE_SHARE_FILTER_NAME,
          getAddFilterGroupName(ELECTION_VOTE_SHARE_FILTER_NAME)
        );
        onAddFilter(ELECTION_VOTE_SHARE_FILTER_NAME);
        return;
      }
      if (name === ETHNICITIES_FILTER_NAME) {
        if (!defaultEthnicityFeatureName) return;
        queueActiveFilterScroll(
          ETHNICITIES_FILTER_NAME,
          getAddFilterGroupName(ETHNICITIES_FILTER_NAME)
        );
        onAddFilter(ETHNICITIES_FILTER_NAME);
        return;
      }
      if (POI_FILTER_NAMES.includes(name as PoiFilterName)) {
        const filterName = name as PoiFilterName;
        if (!defaultPoiFilterFeatureNames[filterName]) return;
        queueActiveFilterScroll(filterName, getAddFilterGroupName(filterName));
        onAddFilter(filterName);
        return;
      }

      queueActiveFilterScroll(name, getAddFilterGroupName(name));
      onAddFilter(name);
    },
    [
      defaultSchoolFeatureName,
      defaultSpecificCrimeFeatureName,
      defaultElectionVoteShareFeatureName,
      defaultEthnicityFeatureName,
      defaultPoiFilterFeatureNames,
      getAddFilterGroupName,
      onAddFilter,
      queueActiveFilterScroll,
    ]
  );

  const handleAddTravelTimeAndScroll = useCallback(
    (mode: TransportMode) => {
      queueActiveFilterScroll(`tt_${travelTimeEntries.length}`, 'Transport');
      onTravelTimeAddEntry(mode);
    },
    [onTravelTimeAddEntry, queueActiveFilterScroll, travelTimeEntries.length]
  );

  useEffect(() => {
    const name = pendingScrollRef.current;
    if (!name) return;
    pendingScrollRef.current = null;
    const el = findActiveFilterElement(scrollRef.current, name);
    if (!el) return;
    el.scrollIntoView({ behavior: 'smooth', block: 'start' });

    if (highlightTimeoutRef.current !== null) {
      window.clearTimeout(highlightTimeoutRef.current);
      document.querySelectorAll('.filter-highlight-flash').forEach((node) => {
        node.classList.remove('filter-highlight-flash');
      });
    }
    // Restart the animation if the same element is re-highlighted.
    el.classList.remove('filter-highlight-flash');
    void el.offsetWidth;
    el.classList.add('filter-highlight-flash');
    highlightTimeoutRef.current = window.setTimeout(() => {
      el.classList.remove('filter-highlight-flash');
      highlightTimeoutRef.current = null;
    }, 2000);
  }, [enabledFeatureList, travelTimeEntries]);
  const percentileScales = useMemo(() => {
    const scales = new Map<string, PercentileScale>();
    for (const f of features) {
      if (f.type === 'numeric' && f.histogram && !f.absolute) {
        scales.set(f.name, buildPercentileScale(f.histogram));
      }
    }
    return scales;
  }, [features]);

  const badgeCount = enabledFeatureList.length + activeEntryCount;

  const [showClearPopup, setShowClearPopup] = useState(false);
  const [clearSaveName, setClearSaveName] = useState('');
  const [clearSaveError, setClearSaveError] = useState<string | null>(null);

  const handleClearAllClick = useCallback(() => {
    if (badgeCount === 0) return;
    if (onUpdateSearch || onSaveSearch) {
      setShowClearPopup(true);
      setClearSaveName('');
      setClearSaveError(null);
    } else {
      onClearAll();
    }
  }, [badgeCount, onUpdateSearch, onSaveSearch, onClearAll]);

  const handleSaveAndClear = useCallback(
    async (e: FormEvent) => {
      e.preventDefault();
      if (!clearSaveName.trim() || savingSearch) return;
      try {
        await onSaveSearch!(clearSaveName.trim());
        setShowClearPopup(false);
        onClearAll();
      } catch {
        setClearSaveError(t('saveSearch.saving'));
      }
    },
    [clearSaveName, savingSearch, onSaveSearch, onClearAll, t]
  );

  const handleUpdateAndClear = useCallback(async () => {
    if (savingSearch || !onUpdateSearch) return;
    try {
      await onUpdateSearch();
      setShowClearPopup(false);
      onClearAll();
    } catch {
      setClearSaveError(t('saveSearch.saving'));
    }
  }, [savingSearch, onUpdateSearch, onClearAll, t]);

  const handleClearWithoutSaving = useCallback(() => {
    setShowClearPopup(false);
    onClearAll();
    if (editingSearchName) onExitEditing?.();
  }, [onClearAll, editingSearchName, onExitEditing]);

  return (
    <div
      ref={containerRef}
      className="relative flex flex-col bg-white dark:bg-navy-950 overflow-y-auto md:overflow-hidden h-full touch-pan-y"
    >
      <ActiveFiltersPanel
        scrollRef={scrollRef}
        collapsed={activeFilterCollapsed}
        addFiltersExpanded={!addFilterCollapsed}
        badgeCount={badgeCount}
        activeEntryCount={activeEntryCount}
        features={features}
        filters={filters}
        enabledFeatureList={enabledFeatureList}
        activeFeature={activeFeature}
        dragValue={dragValue}
        pinnedFeature={pinnedFeature}
        travelTimeEntries={travelTimeEntries}
        filterImpacts={filterImpacts}
        percentileScales={percentileScales}
        destinationDropdownPortal={destinationDropdownPortal}
        isGroupExpanded={isActiveFilterGroupExpanded}
        onToggleGroup={toggleActiveFilterGroup}
        aiFilterLoading={aiFilterLoading}
        aiFilterError={aiFilterError}
        aiFilterErrorType={aiFilterErrorType}
        aiFilterNotes={aiFilterNotes}
        aiFilterSummary={aiFilterSummary}
        isLoggedIn={isLoggedIn}
        onToggleCollapsed={() => setActiveFilterCollapsed((v) => !v)}
        onClearAllClick={handleClearAllClick}
        onShowPhilosophy={() => setShowPhilosophy(true)}
        onAiFilterSubmit={onAiFilterSubmit}
        onLoginRequired={onLoginRequired}
        onFilterChange={onFilterChange}
        onRemoveFilter={onRemoveFilter}
        onDragStart={onDragStart}
        onDragChange={onDragChange}
        onDragEnd={onDragEnd}
        onTogglePin={onTogglePin}
        onShowInfo={setActiveInfoFeature}
        onTravelTimeRemoveEntry={onTravelTimeRemoveEntry}
        onTravelTimeSetDestination={onTravelTimeSetDestination}
        onTravelTimeRangeChange={onTravelTimeRangeChange}
        onTravelTimeDragEnd={onTravelTimeDragEnd}
        onTravelTimeToggleBest={onTravelTimeToggleBest}
        onTravelTimeToggleNoChange={onTravelTimeToggleNoChange}
        onTravelTimeToggleOneChange={onTravelTimeToggleOneChange}
        onTravelTimeToggleNoBuses={onTravelTimeToggleNoBuses}
      />

      <AddFilterPanel
        collapsed={addFilterCollapsed}
        isLicensed={isLicensed}
        availableFeatures={availableFeatures}
        allFeatures={[
          ...features,
          schoolMeta,
          specificCrimeMeta,
          electionVoteShareMeta,
          ethnicityMeta,
          poiDistanceMeta,
          transportDistanceMeta,
          poiCount2KmMeta,
          poiCount5KmMeta,
        ]}
        pinnedFeature={pinnedFeature}
        defaultSchoolFeatureName={defaultSchoolFeatureName}
        defaultSpecificCrimeFeatureName={defaultSpecificCrimeFeatureName}
        defaultElectionVoteShareFeatureName={defaultElectionVoteShareFeatureName}
        defaultEthnicityFeatureName={defaultEthnicityFeatureName}
        defaultPoiFilterFeatureNames={defaultPoiFilterFeatureNames}
        openInfoFeature={openInfoFeature}
        travelTimeEntries={travelTimeEntries}
        onToggleCollapsed={() => setAddFilterCollapsed((v) => !v)}
        onAddFilter={handleAddAndScroll}
        onTogglePin={onTogglePin}
        onNavigateToSource={onNavigateToSource}
        onClearOpenInfoFeature={onClearOpenInfoFeature}
        onAddTravelTimeEntry={handleAddTravelTimeAndScroll}
        onUpgradeClick={onUpgradeClick}
      />

      {showPhilosophy && (
        <InfoPopup
          title={t('filters.findingPerfectPostcode')}
          onClose={() => setShowPhilosophy(false)}
        >
          <div className="space-y-4 text-sm">
            <p className="text-warm-600 dark:text-warm-300">{t('philosophy.intro')}</p>

            <div className="space-y-2">
              {([1, 2, 3, 4, 5, 6] as const).map((n) => (
                <div key={n} className="flex gap-2">
                  <span className="shrink-0 w-5 h-5 rounded-full bg-teal-600 text-white flex items-center justify-center text-xs font-bold">
                    {n}
                  </span>
                  <p className="text-warm-600 dark:text-warm-300">
                    <span className="font-semibold text-navy-950 dark:text-warm-100">
                      {t(`philosophy.step${n}Title`)}
                    </span>{' '}
                    {t(`philosophy.step${n}Desc`)}
                  </p>
                </div>
              ))}
            </div>

            <p className="text-warm-500 dark:text-warm-400 italic text-xs">{t('philosophy.tip')}</p>

            {onResetTutorial && (
              <button
                onClick={() => {
                  setShowPhilosophy(false);
                  onResetTutorial();
                }}
                className="w-full px-3 py-2 rounded-lg border border-warm-200 dark:border-warm-700 bg-white dark:bg-warm-800 hover:bg-warm-50 dark:hover:bg-warm-700 text-teal-600 dark:text-teal-400 font-medium text-sm"
              >
                {t('filters.replayTutorial')}
              </button>
            )}
          </div>
        </InfoPopup>
      )}

      {activeInfoFeature && (
        <FeatureInfoPopup
          feature={activeInfoFeature}
          onClose={() => setActiveInfoFeature(null)}
          onNavigateToSource={onNavigateToSource}
        />
      )}

      <ClearFiltersDialog
        open={showClearPopup}
        saveName={clearSaveName}
        saveError={clearSaveError}
        savingSearch={savingSearch}
        editingSearchName={editingSearchName ?? null}
        onClose={() => setShowClearPopup(false)}
        onSaveNameChange={setClearSaveName}
        onSaveAndClear={handleSaveAndClear}
        onUpdateAndClear={onUpdateSearch ? handleUpdateAndClear : undefined}
        onClearWithoutSaving={handleClearWithoutSaving}
      />
    </div>
  );
});
