import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
import Map from './components/Map';
import Filters from './components/Filters';
import POIPane from './components/POIPane';
import { PropertiesPane } from './components/PropertiesPane';
import DataSources from './components/DataSources';
import DataSourcesPage from './components/DataSourcesPage';
import HomePage from './components/HomePage';
import type {
  FeatureMeta,
  FeatureGroup,
  FeatureFilters,
  Bounds,
  HexagonData,
  ViewChangeParams,
  ApiResponse,
  POI,
  POIResponse,
  POICategoriesResponse,
  POICategoryGroup,
  ViewState,
  Property,
  HexagonPropertiesResponse,
} from './types';

const DEBOUNCE_MS = 150;
const URL_DEBOUNCE_MS = 300;

// Detect if running through VS Code web proxy and construct API base URL
function getApiBaseUrl(): string {
  const { hostname, pathname, href } = window.location;

  // Check pathname for /proxy/PORT pattern
  const pathMatch = pathname.match(/^(\/proxy\/)(\d+)/);
  if (pathMatch) {
    return `${pathMatch[1]}8001`;
  }

  // Check full href in case proxy rewrites pathname
  const hrefMatch = href.match(/(\/proxy\/)\d+/);
  if (hrefMatch) {
    return `${hrefMatch[1]}8001`;
  }

  // If not localhost, assume we're behind a proxy and need explicit backend port
  if (hostname !== 'localhost' && hostname !== '127.0.0.1') {
    return '/proxy/8001';
  }

  // Local development - webpack proxies /api to :8001
  return '';
}

const DEFAULT_VIEW: ViewState = {
  longitude: -1.5,
  latitude: 53.5,
  zoom: 6,
  pitch: 0,
};

// --- URL State helpers ---

function parseUrlState(): {
  viewState?: ViewState;
  filters?: FeatureFilters;
  poiCategories?: Set<string>;
  tab?: 'pois' | 'properties';
} {
  const params = new URLSearchParams(window.location.search);
  const result: ReturnType<typeof parseUrlState> = {};

  // Parse view: v=lat,lng,zoom
  const v = params.get('v');
  if (v) {
    const parts = v.split(',').map(Number);
    if (parts.length === 3 && parts.every((n) => !isNaN(n))) {
      result.viewState = {
        latitude: parts[0],
        longitude: parts[1],
        zoom: parts[2],
        pitch: 0,
      };
    }
  }

  // Parse filters: f=name:min:max,name:val1|val2
  const f = params.get('f');
  if (f) {
    const filters: FeatureFilters = {};
    for (const segment of f.split(',')) {
      const colonIdx = segment.indexOf(':');
      if (colonIdx === -1) continue;
      const name = segment.substring(0, colonIdx);
      const rest = segment.substring(colonIdx + 1);
      if (rest.includes(':')) {
        // Numeric: name:min:max
        const [minStr, maxStr] = rest.split(':');
        const min = Number(minStr);
        const max = Number(maxStr);
        if (!isNaN(min) && !isNaN(max)) {
          filters[name] = [min, max];
        }
      } else if (rest.includes('|')) {
        // Enum: name:val1|val2
        filters[name] = rest.split('|');
      } else {
        // Single enum value
        filters[name] = [rest];
      }
    }
    if (Object.keys(filters).length > 0) {
      result.filters = filters;
    }
  }

  // Parse POI categories: poi=Cafe,Pub,School
  const poi = params.get('poi');
  if (poi) {
    result.poiCategories = new Set(poi.split(',').filter(Boolean));
  }

  // Parse tab: tab=p or tab=o
  const tab = params.get('tab');
  if (tab === 'p') result.tab = 'properties';
  else if (tab === 'o') result.tab = 'pois';

  return result;
}

function stateToParams(
  viewState: { latitude: number; longitude: number; zoom: number } | null,
  filters: FeatureFilters,
  features: FeatureMeta[],
  selectedPOICategories: Set<string>,
  rightPaneTab: 'pois' | 'properties'
): URLSearchParams {
  const params = new URLSearchParams();

  // View
  if (viewState) {
    params.set(
      'v',
      `${viewState.latitude.toFixed(4)},${viewState.longitude.toFixed(4)},${viewState.zoom.toFixed(1)}`
    );
  }

  // Filters
  const filterEntries = Object.entries(filters);
  if (filterEntries.length > 0) {
    const filtersStr = filterEntries
      .map(([name, value]) => {
        const meta = features.find((f) => f.name === name);
        if (meta?.type === 'enum') {
          return `${name}:${(value as string[]).join('|')}`;
        }
        const [min, max] = value as [number, number];
        return `${name}:${min}:${max}`;
      })
      .join(',');
    params.set('f', filtersStr);
  }

  // POI categories
  if (selectedPOICategories.size > 0) {
    params.set('poi', Array.from(selectedPOICategories).join(','));
  }

  // Tab (only if non-default)
  if (rightPaneTab === 'properties') {
    params.set('tab', 'p');
  }

  return params;
}

// --- Header ---

type Page = 'home' | 'dashboard' | 'data-sources';

function Header({
  activePage,
  onPageChange,
}: {
  activePage: Page;
  onPageChange: (page: Page) => void;
}) {
  const [copied, setCopied] = useState(false);

  const handleShare = useCallback(() => {
    navigator.clipboard.writeText(window.location.href).then(() => {
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    });
  }, []);

  const tabClass = (page: Page) =>
    `px-3 py-1.5 rounded text-sm transition-colors ${
      activePage === page
        ? 'bg-navy-700 font-semibold'
        : 'text-warm-300 hover:bg-navy-800 hover:text-white'
    }`;

  return (
    <header className="h-12 bg-navy-900 text-white flex items-center justify-between px-4 shrink-0">
      <div className="flex items-center gap-4">
        <button
          className="flex items-center gap-2 hover:opacity-80 transition-opacity"
          onClick={() => onPageChange('home')}
        >
          <svg
            className="w-5 h-5 text-teal-400"
            fill="none"
            stroke="currentColor"
            viewBox="0 0 24 24"
          >
            <path
              strokeLinecap="round"
              strokeLinejoin="round"
              strokeWidth={2}
              d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"
            />
            <path
              strokeLinecap="round"
              strokeLinejoin="round"
              strokeWidth={2}
              d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"
            />
          </svg>
          <span className="font-semibold text-lg">Narrowit</span>
        </button>
        <nav className="flex items-center gap-1">
          <button className={tabClass('home')} onClick={() => onPageChange('home')}>
            Home
          </button>
          <button className={tabClass('dashboard')} onClick={() => onPageChange('dashboard')}>
            Dashboard
          </button>
          <button className={tabClass('data-sources')} onClick={() => onPageChange('data-sources')}>
            Data Sources
          </button>
        </nav>
      </div>
      {activePage === 'dashboard' && (
        <button
          onClick={handleShare}
          className="flex items-center gap-1.5 px-3 py-1.5 rounded bg-navy-800 hover:bg-navy-700 transition-colors text-sm"
        >
          {copied ? (
            <>
              <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path
                  strokeLinecap="round"
                  strokeLinejoin="round"
                  strokeWidth={2}
                  d="M5 13l4 4L19 7"
                />
              </svg>
              Copied!
            </>
          ) : (
            <>
              <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path
                  strokeLinecap="round"
                  strokeLinejoin="round"
                  strokeWidth={2}
                  d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"
                />
              </svg>
              Share
            </>
          )}
        </button>
      )}
    </header>
  );
}

// --- App ---

export default function App() {
  // Parse URL state once on mount
  const urlState = useMemo(() => parseUrlState(), []);

  const [features, setFeatures] = useState<FeatureMeta[]>([]);
  const [filters, setFilters] = useState<FeatureFilters>(urlState.filters || {});
  const [activeFeature, setActiveFeature] = useState<string | null>(null);
  const [dragValue, setDragValue] = useState<[number, number] | null>(null);
  const [pinnedFeature, setPinnedFeature] = useState<string | null>(null);
  const [rawData, setRawData] = useState<HexagonData[]>([]);
  const [dragData, setDragData] = useState<HexagonData[] | null>(null);
  const [resolution, setResolution] = useState<number>(8);
  const [bounds, setBounds] = useState<Bounds | null>(null);
  const [loading, setLoading] = useState<boolean>(false);
  const [zoom, setZoom] = useState<number>(urlState.viewState?.zoom || DEFAULT_VIEW.zoom);
  const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const abortControllerRef = useRef<AbortController | null>(null);
  const dragAbortRef = useRef<AbortController | null>(null);

  // View state for URL serialization
  const [currentView, setCurrentView] = useState<{
    latitude: number;
    longitude: number;
    zoom: number;
  } | null>(
    urlState.viewState
      ? {
          latitude: urlState.viewState.latitude,
          longitude: urlState.viewState.longitude,
          zoom: urlState.viewState.zoom,
        }
      : null
  );

  // Initial view state for Map
  const initialViewState = useMemo(() => urlState.viewState || DEFAULT_VIEW, []);

  // POI state
  const [pois, setPois] = useState<POI[]>([]);
  const [poiCategoryGroups, setPOICategoryGroups] = useState<POICategoryGroup[]>([]);
  const [selectedPOICategories, setSelectedPOICategories] = useState<Set<string>>(
    urlState.poiCategories || new Set()
  );
  const poiDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const poiAbortControllerRef = useRef<AbortController | null>(null);

  // Hexagon properties state
  const [selectedHexagon, setSelectedHexagon] = useState<{ h3: string; resolution: number } | null>(
    null
  );
  const [properties, setProperties] = useState<Property[]>([]);
  const [propertiesTotal, setPropertiesTotal] = useState(0);
  const [propertiesOffset, setPropertiesOffset] = useState(0);
  const [loadingProperties, setLoadingProperties] = useState(false);
  const [rightPaneTab, setRightPaneTab] = useState<'pois' | 'properties'>(urlState.tab || 'pois');
  const [activePage, setActivePage] = useState<Page>('home');

  // Derive enabled features from filter keys
  const enabledFeatures = useMemo(() => new Set(Object.keys(filters)), [filters]);

  // Derive view feature: active drag takes priority over pinned
  const viewFeature = activeFeature || pinnedFeature;
  const viewSource: 'drag' | 'eye' | null = activeFeature ? 'drag' : pinnedFeature ? 'eye' : null;
  // Color range: always the feature's full slider range from metadata
  const colorRange = useMemo((): [number, number] | null => {
    if (!viewFeature) return null;
    const meta = features.find((f) => f.name === viewFeature);
    if (meta?.min != null && meta?.max != null) return [meta.min, meta.max];
    return null;
  }, [viewFeature, features]);

  // Filter range: current drag or committed filter values, used for gray-out
  const filterRange = useMemo((): [number, number] | null => {
    if (!viewFeature) return null;
    if (activeFeature && dragValue) return dragValue;
    const filterVal = filters[viewFeature];
    if (filterVal && typeof filterVal[0] === 'number') return filterVal as [number, number];
    return null;
  }, [viewFeature, activeFeature, dragValue, filters]);

  // --- URL sync ---
  const urlDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);

  useEffect(() => {
    if (urlDebounceRef.current) {
      clearTimeout(urlDebounceRef.current);
    }
    urlDebounceRef.current = setTimeout(() => {
      const params = stateToParams(
        currentView,
        filters,
        features,
        selectedPOICategories,
        rightPaneTab
      );
      const search = params.toString();
      const newUrl = search ? `${window.location.pathname}?${search}` : window.location.pathname;
      window.history.replaceState(null, '', newUrl);
    }, URL_DEBOUNCE_MS);

    return () => {
      if (urlDebounceRef.current) clearTimeout(urlDebounceRef.current);
    };
  }, [currentView, filters, features, selectedPOICategories, rightPaneTab]);

  // Fetch feature metadata + POI categories on mount
  useEffect(() => {
    fetch(`${getApiBaseUrl()}/api/features`)
      .then((res) => res.json())
      .then((json: { groups: FeatureGroup[] }) => {
        // Flatten grouped response into a flat feature list with group annotation
        const flat: FeatureMeta[] = json.groups.flatMap((g) =>
          g.features.map((f) => ({ ...f, group: g.name }))
        );
        setFeatures(flat);
      })
      .catch((err) => console.error('Failed to fetch features:', err));

    fetch(`${getApiBaseUrl()}/api/poi-categories`)
      .then((res) => res.json())
      .then((json: POICategoriesResponse) => {
        setPOICategoryGroups(json.groups);
      })
      .catch((err) => console.error('Failed to fetch POI categories:', err));
  }, []);

  // Build filter query string helper
  const buildFilterParam = useCallback((): string => {
    const filterEntries = Object.entries(filters);
    if (filterEntries.length === 0) return '';
    return filterEntries
      .map(([name, value]) => {
        const meta = features.find((f) => f.name === name);
        if (meta?.type === 'enum') {
          return `${name}:${(value as string[]).join('|')}`;
        }
        const [min, max] = value as [number, number];
        return `${name}:${min}:${max}`;
      })
      .join(',');
  }, [filters, features]);

  // Debounced fetch when resolution/bounds/filters change — always fetch hexagons
  useEffect(() => {
    if (!bounds) return;

    if (debounceRef.current) {
      clearTimeout(debounceRef.current);
    }

    debounceRef.current = setTimeout(async () => {
      if (abortControllerRef.current) {
        abortControllerRef.current.abort();
      }
      abortControllerRef.current = new AbortController();

      setLoading(true);
      try {
        const boundsStr = `${bounds.south},${bounds.west},${bounds.north},${bounds.east}`;
        const filtersStr = buildFilterParam();

        const params = new URLSearchParams({
          resolution: resolution.toString(),
          bounds: boundsStr,
        });
        if (filtersStr) params.set('filters', filtersStr);
        const res = await fetch(`${getApiBaseUrl()}/api/hexagons?${params}`, {
          signal: abortControllerRef.current.signal,
        });
        const json: ApiResponse = await res.json();
        setRawData(json.features || []);
      } catch (err) {
        if (err instanceof Error && err.name !== 'AbortError') {
          console.error('Failed to fetch data:', err);
        }
      } finally {
        setLoading(false);
      }
    }, DEBOUNCE_MS);

    return () => {
      if (debounceRef.current) {
        clearTimeout(debounceRef.current);
      }
    };
  }, [resolution, bounds, filters, buildFilterParam]);

  // During slider drag, use the expanded dataset (without active feature filter)
  // so both narrowing and expanding are visible. Otherwise use server-filtered data.
  const data = dragData ?? rawData;

  // Fetch POIs when bounds or selected categories change
  useEffect(() => {
    if (!bounds || selectedPOICategories.size === 0) {
      setPois([]);
      return;
    }

    if (poiDebounceRef.current) {
      clearTimeout(poiDebounceRef.current);
    }

    poiDebounceRef.current = setTimeout(async () => {
      if (poiAbortControllerRef.current) {
        poiAbortControllerRef.current.abort();
      }
      poiAbortControllerRef.current = new AbortController();

      try {
        const boundsStr = `${bounds.south},${bounds.west},${bounds.north},${bounds.east}`;
        const categoriesStr = Array.from(selectedPOICategories).join(',');
        const params = new URLSearchParams({
          categories: categoriesStr,
          bounds: boundsStr,
        });
        const res = await fetch(`${getApiBaseUrl()}/api/pois?${params}`, {
          signal: poiAbortControllerRef.current.signal,
        });
        const json: POIResponse = await res.json();
        setPois(json.pois || []);
      } catch (err) {
        if (err instanceof Error && err.name !== 'AbortError') {
          console.error('Failed to fetch POIs:', err);
        }
      }
    }, DEBOUNCE_MS);

    return () => {
      if (poiDebounceRef.current) {
        clearTimeout(poiDebounceRef.current);
      }
    };
  }, [bounds, selectedPOICategories]);

  const prevBoundsRef = useRef<string>('');
  const handleViewChange = useCallback(
    ({
      resolution: newRes,
      bounds: newBounds,
      zoom: newZoom,
      latitude,
      longitude,
    }: ViewChangeParams) => {
      // Only update bounds/resolution when quantized values actually change
      const boundsKey = `${newBounds.south},${newBounds.west},${newBounds.north},${newBounds.east},${newRes}`;
      if (boundsKey !== prevBoundsRef.current) {
        prevBoundsRef.current = boundsKey;
        setResolution(newRes);
        setBounds(newBounds);
      }
      setZoom(newZoom);
      setCurrentView({ latitude, longitude, zoom: newZoom });
    },
    []
  );

  const handleAddFilter = useCallback(
    (name: string) => {
      const meta = features.find((f) => f.name === name);
      if (!meta) return;
      if (meta.type === 'enum' && meta.values) {
        setFilters((prev) => ({ ...prev, [name]: [...meta.values!] }));
      } else if (meta.min != null && meta.max != null) {
        setFilters((prev) => ({ ...prev, [name]: [meta.min!, meta.max!] }));
      }
    },
    [features]
  );

  const handleFilterChange = useCallback((name: string, value: [number, number] | string[]) => {
    setFilters((prev) => ({ ...prev, [name]: value }));
  }, []);

  const handleRemoveFilter = useCallback((name: string) => {
    setFilters((prev) => {
      const next = { ...prev };
      delete next[name];
      return next;
    });
    setPinnedFeature((prev) => (prev === name ? null : prev));
  }, []);

  const handleDragStart = useCallback(
    (name: string) => {
      const meta = features.find((f) => f.name === name);
      if (meta?.type === 'enum') return; // No drag interaction for enum features
      setActiveFeature(name);
      const fval = filters[name];
      setDragValue(fval && !Array.isArray(fval[0]) ? (fval as [number, number]) : null);

      // Fetch hexagons without this feature's filter so we can expand the range
      if (!bounds) return;
      if (dragAbortRef.current) dragAbortRef.current.abort();
      dragAbortRef.current = new AbortController();

      const otherFilters = Object.entries(filters).filter(([k]) => k !== name);
      let filtersStr = '';
      if (otherFilters.length > 0) {
        filtersStr = otherFilters
          .map(([n, value]) => {
            const m = features.find((f) => f.name === n);
            if (m?.type === 'enum') return `${n}:${(value as string[]).join('|')}`;
            const [min, max] = value as [number, number];
            return `${n}:${min}:${max}`;
          })
          .join(',');
      }

      const boundsStr = `${bounds.south},${bounds.west},${bounds.north},${bounds.east}`;
      const params = new URLSearchParams({ resolution: resolution.toString(), bounds: boundsStr });
      if (filtersStr) params.set('filters', filtersStr);

      fetch(`${getApiBaseUrl()}/api/hexagons?${params}`, {
        signal: dragAbortRef.current.signal,
      })
        .then((res) => res.json())
        .then((json: ApiResponse) => setDragData(json.features || []))
        .catch((err) => {
          if (err instanceof Error && err.name !== 'AbortError') {
            console.error('Failed to fetch drag data:', err);
          }
        });
    },
    [filters, features, bounds, resolution]
  );

  const handleDragChange = useCallback((value: [number, number]) => {
    setDragValue(value);
  }, []);

  const handleDragEnd = useCallback(() => {
    if (activeFeature && dragValue) {
      setFilters((prev) => ({ ...prev, [activeFeature]: dragValue }));
    }
    setActiveFeature(null);
    setDragValue(null);
    setDragData(null);
    if (dragAbortRef.current) {
      dragAbortRef.current.abort();
      dragAbortRef.current = null;
    }
  }, [activeFeature, dragValue]);

  const handleTogglePin = useCallback((name: string) => {
    setPinnedFeature((prev) => (prev === name ? null : name));
  }, []);

  const handleCancelPin = useCallback(() => {
    setPinnedFeature(null);
  }, []);

  const fetchHexagonProperties = useCallback(
    async (h3: string, res: number, offset = 0) => {
      setLoadingProperties(true);
      try {
        const params = new URLSearchParams({
          h3,
          resolution: res.toString(),
          limit: '100',
          offset: offset.toString(),
        });

        // Add current filters
        const filterEntries = Object.entries(filters);
        if (filterEntries.length > 0) {
          const filterStr = filterEntries
            .map(([name, value]) => {
              const meta = features.find((f) => f.name === name);
              if (meta?.type === 'enum') {
                return `${name}:${(value as string[]).join('|')}`;
              }
              const [min, max] = value as [number, number];
              return `${name}:${min}:${max}`;
            })
            .join(',');
          params.append('filters', filterStr);
        }

        const response = await fetch(`${getApiBaseUrl()}/api/hexagon-properties?${params}`);
        const data: HexagonPropertiesResponse = await response.json();

        if (offset === 0) {
          setProperties(data.properties);
        } else {
          setProperties((prev) => [...prev, ...data.properties]);
        }
        setPropertiesTotal(data.total);
        setPropertiesOffset(offset + data.properties.length);
      } catch (err) {
        console.error('Failed to fetch properties:', err);
      } finally {
        setLoadingProperties(false);
      }
    },
    [filters, features]
  );

  const handleHexagonClick = useCallback(
    (h3: string) => {
      if (selectedHexagon?.h3 === h3) {
        // Deselect if clicking same hexagon
        setSelectedHexagon(null);
        setProperties([]);
      } else {
        setSelectedHexagon({ h3, resolution });
        setPropertiesOffset(0);
        setRightPaneTab('properties'); // Auto-switch to properties tab
        fetchHexagonProperties(h3, resolution, 0);
      }
    },
    [selectedHexagon, resolution, fetchHexagonProperties]
  );

  const handleLoadMoreProperties = useCallback(() => {
    if (selectedHexagon) {
      fetchHexagonProperties(selectedHexagon.h3, selectedHexagon.resolution, propertiesOffset);
    }
  }, [selectedHexagon, propertiesOffset, fetchHexagonProperties]);

  const handleCloseProperties = useCallback(() => {
    setSelectedHexagon(null);
    setProperties([]);
  }, []);

  return (
    <div className="h-screen flex flex-col">
      <Header activePage={activePage} onPageChange={setActivePage} />
      {activePage === 'home' ? (
        <HomePage onOpenDashboard={() => setActivePage('dashboard')} />
      ) : activePage === 'data-sources' ? (
        <DataSourcesPage />
      ) : (
        <div className="flex-1 flex overflow-hidden">
          <Filters
            features={features}
            filters={filters}
            activeFeature={activeFeature}
            dragValue={dragValue}
            enabledFeatures={enabledFeatures}
            onAddFilter={handleAddFilter}
            onRemoveFilter={handleRemoveFilter}
            onFilterChange={handleFilterChange}
            onDragStart={handleDragStart}
            onDragChange={handleDragChange}
            onDragEnd={handleDragEnd}
            zoom={zoom}
            pinnedFeature={pinnedFeature}
            onTogglePin={handleTogglePin}
            onCancelPin={handleCancelPin}
          />
          <div className="flex-1 relative">
            <Map
              data={data}
              pois={pois}
              onViewChange={handleViewChange}
              viewFeature={viewFeature}
              colorRange={colorRange}
              filterRange={filterRange}
              viewSource={viewSource}
              onCancelPin={handleCancelPin}
              features={features}
              selectedHexagonId={selectedHexagon?.h3 || null}
              onHexagonClick={handleHexagonClick}
              initialViewState={initialViewState}
            />
            {loading && (
              <div className="absolute top-4 right-4 bg-white px-3 py-1 rounded shadow">
                Loading...
              </div>
            )}
            <DataSources onNavigate={() => setActivePage('data-sources')} />
          </div>
          <div className="w-72 bg-white shadow-lg z-10 flex flex-col">
            {/* Tab headers */}
            <div className="flex border-b border-warm-200">
              <button
                className={`flex-1 p-3 ${
                  rightPaneTab === 'pois'
                    ? 'border-b-2 border-teal-500 font-semibold'
                    : 'text-warm-600'
                }`}
                onClick={() => setRightPaneTab('pois')}
              >
                POIs {pois.length > 0 && `(${pois.length})`}
              </button>
              <button
                className={`flex-1 p-3 ${
                  rightPaneTab === 'properties'
                    ? 'border-b-2 border-teal-500 font-semibold'
                    : 'text-warm-600'
                }`}
                onClick={() => setRightPaneTab('properties')}
              >
                Properties {propertiesTotal > 0 && `(${propertiesTotal})`}
              </button>
            </div>

            {/* Tab content */}
            <div className="flex-1 overflow-hidden">
              {rightPaneTab === 'pois' ? (
                <POIPane
                  groups={poiCategoryGroups}
                  selectedCategories={selectedPOICategories}
                  onCategoriesChange={setSelectedPOICategories}
                  poiCount={pois.length}
                />
              ) : (
                <PropertiesPane
                  properties={properties}
                  total={propertiesTotal}
                  loading={loadingProperties}
                  hexagonId={selectedHexagon?.h3 || null}
                  onLoadMore={handleLoadMoreProperties}
                  onClose={handleCloseProperties}
                />
              )}
            </div>
          </div>
        </div>
      )}
    </div>
  );
}
