import { Suspense, type MutableRefObject, type ReactNode } from 'react';

import type {
  ActualListing,
  FeatureFilters,
  FeatureMeta,
  POI,
  PostcodeGeometry,
  ViewState,
} from '../../../types';
import type { useMapData } from '../../../hooks/useMapData';
import type { TravelTimeEntry } from '../../../hooks/useTravelTime';
import type { OverlayId } from '../../../lib/overlays';
import type { BasemapId } from '../../../lib/basemaps';
import type { SearchedLocation } from '../LocationSearch';
import MobileBottomSheet from '../MobileBottomSheet';
import { MapPinIcon } from '../../ui/icons/MapPinIcon';
import { EyeIcon } from '../../ui/icons/EyeIcon';
import { HouseIcon } from '../../ui/icons/HouseIcon';
import { SpinnerIcon } from '../../ui/icons/SpinnerIcon';
import { IndeterminateProgressBar } from '../../ui/IndeterminateProgressBar';
import type { MapFlyTo } from './types';
import { MapFallback, PaneFallback } from './Fallbacks';
import { MapErrorBoundary } from '../MapErrorBoundary';
import { LoadingOverlay } from './LoadingOverlay';
import { Map, MobileDrawer } from './lazyComponents';

type MapData = ReturnType<typeof useMapData>;
type RightPaneTab = 'properties' | 'area';

interface MobileMapPageProps {
  initialLoading: boolean;
  mapData: MapData;
  pois: POI[];
  activeOverlays: Set<OverlayId>;
  activeCrimeTypes: Set<string>;
  basemap: BasemapId;
  colorOpacity: number;
  mapViewFeature: string | null;
  filterRange: [number, number] | null;
  viewSource: 'drag' | 'eye' | null;
  onCancelPin: () => void;
  features: FeatureMeta[];
  selectedHexagonId: string | null;
  hoveredHexagonId: string | null;
  onHexagonClick: (id: string, isPostcode?: boolean, geometry?: PostcodeGeometry) => void;
  onHexagonHover: (h3: string | null, x?: number, y?: number) => void;
  initialViewState: ViewState;
  flyToRef: MutableRefObject<MapFlyTo | null>;
  theme: 'light' | 'dark';
  filters: FeatureFilters;
  selectedPostcodeGeometry: PostcodeGeometry | null;
  onLocationSearched: (location: SearchedLocation | null) => void;
  onCurrentLocationFound: (lat: number, lng: number) => void;
  currentLocation: { lat: number; lng: number } | null;
  actualListings: ActualListing[];
  actualListingsEnabled: boolean;
  actualListingsLoading: boolean;
  onToggleActualListings?: () => void;
  travelTimeEntries: TravelTimeEntry[];
  bottomScreenInset: number;
  onBottomSheetCoveredHeightChange: (height: number) => void;
  mobileDrawerOpen: boolean;
  onMobileDrawerClose: () => void;
  onMobileDrawerPanelRectChange: (rect: DOMRectReadOnly) => void;
  rightPaneTab: RightPaneTab;
  onMobileDrawerTabChange: (tab: RightPaneTab) => void;
  poiPaneOpen: boolean;
  onTogglePoiPane: () => void;
  poiButtonLabel: string;
  poiPane: ReactNode;
  overlayPaneOpen: boolean;
  onToggleOverlayPane: () => void;
  overlayPane: ReactNode;
  filtersPane: ReactNode;
  mobileLegend: ReactNode;
  renderAreaPane: () => ReactNode;
  renderPropertiesPane: () => ReactNode;
  toasts: ReactNode;
  upgradeModal: ReactNode;
  editingBar?: ReactNode;
}

export function MobileMapPage({
  initialLoading,
  mapData,
  pois,
  activeOverlays,
  activeCrimeTypes,
  basemap,
  colorOpacity,
  mapViewFeature,
  filterRange,
  viewSource,
  onCancelPin,
  features,
  selectedHexagonId,
  hoveredHexagonId,
  onHexagonClick,
  onHexagonHover,
  initialViewState,
  flyToRef,
  theme,
  filters,
  selectedPostcodeGeometry,
  onLocationSearched,
  onCurrentLocationFound,
  currentLocation,
  actualListings,
  actualListingsEnabled,
  actualListingsLoading,
  onToggleActualListings,
  travelTimeEntries,
  bottomScreenInset,
  onBottomSheetCoveredHeightChange,
  mobileDrawerOpen,
  onMobileDrawerClose,
  onMobileDrawerPanelRectChange,
  rightPaneTab,
  onMobileDrawerTabChange,
  poiPaneOpen,
  onTogglePoiPane,
  poiButtonLabel,
  poiPane,
  overlayPaneOpen,
  onToggleOverlayPane,
  overlayPane,
  filtersPane,
  mobileLegend,
  renderAreaPane,
  renderPropertiesPane,
  toasts,
  upgradeModal,
  editingBar,
}: MobileMapPageProps) {
  const floatingPaneAvailableHeight = `max(12rem, calc(100dvh - ${Math.ceil(
    bottomScreenInset
  )}px - 7rem))`;

  // The action buttons (listing/overlays/POI) are pinned to the top-right over
  // the map. Reserve their width on the right of the top-card overlay so the
  // expanded search bar shrinks to fit instead of sliding underneath them.
  // Each button is p-2 + a 1.25rem icon (36px); they sit in a `gap-2` (8px) row.
  const ACTION_BUTTON_WIDTH = 36;
  const ACTION_BUTTON_GAP = 8;
  const actionButtonCount = (onToggleActualListings ? 1 : 0) + 2;
  const topCardsRightInset =
    actionButtonCount * ACTION_BUTTON_WIDTH + actionButtonCount * ACTION_BUTTON_GAP; // inter-button gaps + breathing room before the search

  return (
    <div className="flex-1 overflow-hidden relative">
      <LoadingOverlay show={initialLoading} />

      <div className="absolute inset-0">
        <IndeterminateProgressBar show={mapData.loading && !initialLoading} />
        <MapErrorBoundary>
          <Suspense fallback={<MapFallback />}>
            <Map
              data={mapData.data}
              postcodeData={mapData.postcodeData}
              usePostcodeView={mapData.usePostcodeView}
              pois={pois}
              activeOverlays={activeOverlays}
              activeCrimeTypes={activeCrimeTypes}
              basemap={basemap}
              colorOpacity={colorOpacity}
              onViewChange={mapData.handleViewChange}
              viewFeature={mapViewFeature}
              colorRange={mapData.colorRange}
              filterRange={filterRange}
              viewSource={viewSource}
              onCancelPin={onCancelPin}
              onResetPreviewScale={mapData.handleResetPreviewScale}
              canResetPreviewScale={mapData.canResetPreviewScale}
              features={features}
              selectedHexagonId={selectedHexagonId}
              hoveredHexagonId={hoveredHexagonId}
              onHexagonClick={onHexagonClick}
              onHexagonHover={onHexagonHover}
              initialViewState={initialViewState}
              flyToRef={flyToRef}
              theme={theme}
              filters={filters}
              selectedPostcodeGeometry={selectedPostcodeGeometry}
              onLocationSearched={onLocationSearched}
              onCurrentLocationFound={onCurrentLocationFound}
              currentLocation={currentLocation}
              actualListings={actualListings}
              bounds={mapData.bounds}
              hideLegend
              topCardsRightInset={topCardsRightInset}
              travelTimeEntries={travelTimeEntries}
              bottomScreenInset={bottomScreenInset}
            />
          </Suspense>
        </MapErrorBoundary>
      </div>

      <div className="absolute right-3 top-3 z-20 flex max-w-[calc(100%_-_1.5rem)] flex-row flex-wrap justify-end gap-2">
        {onToggleActualListings && (
          <button
            type="button"
            onClick={onToggleActualListings}
            className={`rounded-lg bg-white p-2 shadow-lg dark:bg-warm-800 ${actualListingsEnabled ? 'text-red-600 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300' : 'text-warm-500 hover:text-red-600 dark:text-warm-400 dark:hover:text-red-400'}`}
            aria-pressed={actualListingsEnabled}
            aria-busy={actualListingsLoading}
            aria-label={actualListingsEnabled ? 'Hide actual listings' : 'Show actual listings'}
            title={actualListingsEnabled ? 'Hide actual listings' : 'Show actual listings'}
          >
            {actualListingsLoading ? (
              <SpinnerIcon className="h-5 w-5 animate-spin" />
            ) : (
              <HouseIcon className="h-5 w-5" />
            )}
          </button>
        )}
        <button
          onClick={onToggleOverlayPane}
          className={`rounded-lg bg-white p-2 shadow-lg dark:bg-warm-800 ${overlayPaneOpen ? 'text-teal-600 dark:text-teal-400' : 'text-warm-500 hover:text-teal-600 dark:text-warm-400 dark:hover:text-teal-400'}`}
          aria-label="Overlays"
        >
          <EyeIcon className="h-5 w-5" filled={overlayPaneOpen} />
        </button>
        <button
          onClick={onTogglePoiPane}
          className={`rounded-lg bg-white p-2 shadow-lg dark:bg-warm-800 ${poiPaneOpen ? 'text-teal-600 dark:text-teal-400' : 'text-warm-500 hover:text-teal-600 dark:text-warm-400 dark:hover:text-teal-400'}`}
          aria-label={poiButtonLabel}
        >
          <MapPinIcon className="h-5 w-5" />
        </button>
      </div>

      {overlayPaneOpen && (
        <div className="absolute top-24 right-3 left-3 z-20 flex max-h-[60dvh] min-h-0 flex-col overflow-hidden rounded-lg border border-warm-200 bg-white shadow-xl dark:border-warm-700 dark:bg-warm-900">
          {overlayPane}
        </div>
      )}

      {poiPaneOpen && (
        <div
          className="absolute top-24 right-3 left-3 z-20 flex min-h-0 flex-col overflow-hidden rounded-lg border border-warm-200 bg-white shadow-xl dark:border-warm-700 dark:bg-warm-900"
          style={{
            height: `min(22rem, ${floatingPaneAvailableHeight})`,
            maxHeight: floatingPaneAvailableHeight,
          }}
        >
          {poiPane}
        </div>
      )}

      <MobileBottomSheet
        legend={mobileLegend}
        editingBar={editingBar}
        onCoveredHeightChange={onBottomSheetCoveredHeightChange}
      >
        {filtersPane}
      </MobileBottomSheet>

      {mobileDrawerOpen && selectedHexagonId && (
        <Suspense fallback={<PaneFallback />}>
          <MobileDrawer
            onClose={onMobileDrawerClose}
            renderArea={renderAreaPane}
            renderProperties={renderPropertiesPane}
            tab={rightPaneTab}
            onPanelRectChange={onMobileDrawerPanelRectChange}
            onTabChange={onMobileDrawerTabChange}
          />
        </Suspense>
      )}

      {toasts}
      {upgradeModal}
    </div>
  );
}
