import React from 'react';
import Box from '@mui/material/Box';
import TextField from '@mui/material/TextField';
import InputAdornment from '@mui/material/InputAdornment';
import Autocomplete from '@mui/material/Autocomplete';
import FormControlLabel from '@mui/material/FormControlLabel';
import Slider from '@mui/material/Slider';
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
import ToggleButton from '@mui/material/ToggleButton';
import useMediaQuery from '@mui/material/useMediaQuery';
import ViewListOutlinedIcon from '@mui/icons-material/ViewListOutlined';
import MapOutlinedIcon from '@mui/icons-material/MapOutlined';
import type { SxProps, Theme } from '@mui/material/styles';
import { useTheme } from '@mui/material/styles';
import { WizardLayout } from '../../templates/WizardLayout';
import { ProviderCard } from '../../molecules/ProviderCard';
import { FilterPanel } from '../../molecules/FilterPanel';
import { MapProviderDrawer } from '../../molecules/MapProviderDrawer';
import { LocationSearchInput } from '../../molecules/LocationSearchInput';
import { HelpBar } from '../../molecules/HelpBar';
import { SortMenu } from '../../molecules/SortMenu';
import {
  ProviderMap,
  type ProviderMapActiveState,
  type ProviderMapHandle,
} from '../../organisms/ProviderMap';
import { Chip } from '../../atoms/Chip';
import { Switch } from '../../atoms/Switch';
import { Typography } from '../../atoms/Typography';
import { Divider } from '../../atoms/Divider';

// ─── Types ───────────────────────────────────────────────────────────────────

/** Provider data for display in the list */
export interface ProviderData {
  /** Unique provider ID (funeralBrandID) */
  id: string;
  /** Provider display name */
  name: string;
  /** Location text (suburb, city) */
  location: string;
  /** Whether this is a verified/trusted partner */
  verified?: boolean;
  /** Hero image URL */
  imageUrl?: string;
  /** Provider logo URL */
  logoUrl?: string;
  /** Average rating (e.g. 4.8) */
  rating?: number;
  /** Number of reviews */
  reviewCount?: number;
  /** Starting price in dollars */
  startingPrice?: number;
  /** Distance from user in km */
  distanceKm?: number;
  /** Brief description */
  description?: string;
  /** Geographic coordinates for map display */
  coords?: { lat: number; lng: number };
}

/** A funeral type option for the filter */
export interface FuneralTypeOption {
  /** Machine-readable value (e.g. "service_and_cremation") */
  value: string;
  /** Human-readable label (e.g. "Service & Cremation") */
  label: string;
}

/** Structured filter state for the providers list */
export interface ProviderFilterValues {
  /** Selected service tradition (null = any) */
  tradition: string | null;
  /** Selected funeral type values (empty = all) */
  funeralTypes: string[];
  /** Show only verified providers */
  verifiedOnly: boolean;
  /** Show only providers offering online arrangements */
  onlineArrangements: boolean;
  /** Price range [min, max] */
  priceRange: [number, number];
}

/** Sort options for the provider list */
export type ProviderSortBy = 'recommended' | 'nearest' | 'price_low' | 'price_high';

/** View mode for the listing */
export type ListViewMode = 'list' | 'map';

/** Props for the ProvidersStep page component */
export interface ProvidersStepProps {
  /** List of providers to display */
  providers: ProviderData[];
  /** Callback when a provider card is clicked — triggers navigation (D-D) */
  onSelectProvider: (id: string) => void;
  /** Search query value */
  searchQuery: string;
  /** Callback when search query changes */
  onSearchChange: (query: string) => void;
  /** Callback when search is submitted */
  onSearch?: (query: string) => void;
  /** Current filter state */
  filterValues: ProviderFilterValues;
  /** Callback when any filter changes */
  onFilterChange: (values: ProviderFilterValues) => void;
  /** Available service tradition options for the autocomplete */
  traditionOptions?: string[];
  /** Available funeral type options */
  funeralTypeOptions?: FuneralTypeOption[];
  /** Minimum price for the slider (default 0) */
  minPrice?: number;
  /** Maximum price for the slider (default 15000) */
  maxPrice?: number;
  /** Current sort order */
  sortBy?: ProviderSortBy;
  /** Callback when sort order changes */
  onSortChange?: (sort: ProviderSortBy) => void;
  /** Current view mode */
  viewMode?: ListViewMode;
  /** Callback when view mode changes */
  onViewModeChange?: (mode: ListViewMode) => void;
  /** Callback for the Back button */
  onBack: () => void;
  /** Map panel content — slot for future map integration */
  mapPanel?: React.ReactNode;
  /** Navigation bar — passed through to WizardLayout */
  navigation?: React.ReactNode;
  /** Progress stepper — passed through to WizardLayout */
  progressStepper?: React.ReactNode;
  /** Running total — passed through to WizardLayout */
  runningTotal?: React.ReactNode;
  /** Whether this is a pre-planning flow (shows softer copy) */
  isPrePlanning?: boolean;
  /** MUI sx prop for the root */
  sx?: SxProps<Theme>;
}

// ─── Defaults ────────────────────────────────────────────────────────────────

const DEFAULT_TRADITIONS = [
  'None',
  'Anglican',
  "Bahá'í",
  'Baptist',
  'Buddhist',
  'Catholic',
  'Eastern Orthodox',
  'Hindu',
  'Humanist',
  'Indigenous Australian',
  'Jewish',
  'Lutheran',
  'Methodist',
  'Muslim',
  'Non-religious',
  'Pentecostal',
  'Presbyterian',
  'Salvation Army',
  'Secular',
  'Sikh',
  'Uniting Church',
];

const DEFAULT_FUNERAL_TYPES: FuneralTypeOption[] = [
  { value: 'service_and_cremation', label: 'Service & Cremation' },
  { value: 'service_and_burial', label: 'Service & Burial' },
  { value: 'cremation_only', label: 'Cremation Only' },
  { value: 'graveside_burial', label: 'Graveside Burial' },
  { value: 'water_cremation', label: 'Water Cremation' },
  { value: 'burial_only', label: 'Burial Only' },
];

const SORT_OPTIONS: { value: ProviderSortBy; label: string }[] = [
  { value: 'recommended', label: 'Recommended' },
  { value: 'nearest', label: 'Nearest' },
  { value: 'price_low', label: 'Price low to high' },
  { value: 'price_high', label: 'Price high to low' },
];

export const EMPTY_FILTER_VALUES: ProviderFilterValues = {
  tradition: null,
  funeralTypes: [],
  verifiedOnly: false,
  onlineArrangements: false,
  priceRange: [0, 15000],
};

// ─── Shared styles ───────────────────────────────────────────────────────────

/** Section heading inside the filter panel */
const sectionHeadingSx = {
  mb: 1.5,
  display: 'block',
  fontWeight: 600,
  color: 'text.primary',
} as const;

/** Wrapping chip row */
const chipWrapSx = {
  display: 'flex',
  flexWrap: 'wrap',
  gap: 1,
} as const;

/**
 * Shared visual tokens for the ProvidersStep control chips. Search, Filters,
 * Sort by, and the List/Map toggle all reference these so their outline /
 * radius / fill / shadow / height read as one coherent set. Kept on the page
 * (not promoted to a design-system-wide primitive) because this is a
 * page-local "control cluster" pattern — Button and Input already own their
 * own radii in the theme.
 */
const CONTROL_CHROME = {
  height: 32,
  borderColor: 'var(--fa-color-neutral-300)',
  borderRadius: 'var(--fa-button-border-radius-default)',
  bgcolor: 'background.paper',
  boxShadow: 'var(--fa-shadow-sm)',
} as const;

/** sx for an outlined Button carrying CONTROL_CHROME (used for Sort by). */
const controlButtonSx = {
  height: CONTROL_CHROME.height,
  bgcolor: CONTROL_CHROME.bgcolor,
  borderColor: CONTROL_CHROME.borderColor,
  borderRadius: CONTROL_CHROME.borderRadius,
  boxShadow: CONTROL_CHROME.boxShadow,
  textTransform: 'none',
  '&:hover': { bgcolor: CONTROL_CHROME.bgcolor, borderColor: CONTROL_CHROME.borderColor },
  '&:focus-visible': { outline: 'none' },
} as const;

/** sx for the FilterPanel wrapper — targets its internal trigger Button. */
const filterTriggerSx = {
  '& .MuiButton-root': controlButtonSx,
} as const;

/** sx for a ToggleButtonGroup carrying CONTROL_CHROME (used for List/Map). */
const controlToggleSx = {
  borderRadius: CONTROL_CHROME.borderRadius,
  boxShadow: CONTROL_CHROME.boxShadow,
  '& .MuiToggleButton-root': {
    height: CONTROL_CHROME.height,
    px: 1.5,
    py: 0,
    textTransform: 'none',
    fontSize: 'var(--fa-button-font-size-sm)',
    fontWeight: 600,
    borderColor: CONTROL_CHROME.borderColor,
    bgcolor: CONTROL_CHROME.bgcolor,
    '&:hover': { bgcolor: CONTROL_CHROME.bgcolor },
    '&.Mui-selected': {
      bgcolor: 'var(--fa-color-brand-100)',
      color: 'primary.main',
      '&:hover': { bgcolor: 'var(--fa-color-brand-200)' },
    },
  },
} as const;

/** sx for the Autocomplete/TextField search input carrying CONTROL_CHROME.
 *  Absolute-anchors the end adornment (commit button) to the right edge —
 *  MUI's stock Autocomplete does this on `.MuiAutocomplete-endAdornment`,
 *  but overriding `InputProps.endAdornment` puts the content in a
 *  `.MuiInputAdornment-positionEnd` (which is static by default), so the
 *  button slides left as chips/draft fill the input. `paddingRight` on the
 *  OutlinedInput reserves the lane so input content can't run under it. */
const controlInputSx = {
  '& .MuiOutlinedInput-root': {
    bgcolor: CONTROL_CHROME.bgcolor,
    boxShadow: CONTROL_CHROME.boxShadow,
    borderRadius: CONTROL_CHROME.borderRadius,
    pr: 5,
    position: 'relative',
  },
  '& .MuiOutlinedInput-root .MuiInputAdornment-positionEnd': {
    position: 'absolute',
    right: 8,
    top: '50%',
    transform: 'translateY(-50%)',
    height: 'auto',
    maxHeight: 'none',
    m: 0,
  },
  '& .MuiOutlinedInput-notchedOutline': {
    borderColor: CONTROL_CHROME.borderColor,
    borderWidth: 1,
  },
  '& .MuiOutlinedInput-root.Mui-focused': {
    boxShadow: CONTROL_CHROME.boxShadow,
    '& .MuiOutlinedInput-notchedOutline': {
      borderColor: CONTROL_CHROME.borderColor,
      borderWidth: 1,
    },
  },
} as const;

// ─── Component ───────────────────────────────────────────────────────────────

/**
 * Step 2 — Provider selection page for the FA arrangement wizard.
 *
 * List + Map split layout. Left panel shows a scrollable list of
 * provider cards with search and filter button. Right panel is a
 * slot for future map integration.
 *
 * Click-to-navigate (D-D): clicking a provider card triggers
 * navigation directly — no selection state or Continue button.
 *
 * Filters: location (chip + search), service tradition (autocomplete),
 * funeral type (horizontal scroll chips), verified only (switch),
 * online arrangements (switch), price range (slider + compact inputs).
 *
 * Pure presentation component — props in, callbacks out.
 *
 * Spec: documentation/steps/steps/02_providers.yaml
 */
export const ProvidersStep: React.FC<ProvidersStepProps> = ({
  providers,
  onSelectProvider,
  searchQuery,
  onSearchChange,
  onSearch,
  filterValues,
  onFilterChange,
  traditionOptions = DEFAULT_TRADITIONS,
  funeralTypeOptions = DEFAULT_FUNERAL_TYPES,
  minPrice = 0,
  maxPrice = 15000,
  sortBy = 'recommended',
  onSortChange,
  viewMode = 'list',
  onViewModeChange,
  onBack,
  mapPanel,
  navigation,
  progressStepper,
  runningTotal,
  isPrePlanning = false,
  sx,
}) => {
  const subheading = isPrePlanning
    ? 'Take your time exploring providers. You can always come back and choose a different one.'
    : 'These providers are near your location. Each has their own packages and pricing.';

  // ─── Mobile map-first plumbing ───
  const theme = useTheme();
  const isMobile = useMediaQuery(theme.breakpoints.down('md'));
  const mapRef = React.useRef<ProviderMapHandle>(null);
  const [mapActive, setMapActive] = React.useState<ProviderMapActiveState | null>(null);
  const showMobileMapLayout = isMobile && viewMode === 'map';

  // ─── Price input local state (commits on blur / Enter) ───
  const [priceMinInput, setPriceMinInput] = React.useState(String(filterValues.priceRange[0]));
  const [priceMaxInput, setPriceMaxInput] = React.useState(String(filterValues.priceRange[1]));

  // Sync local state when slider (or clear) changes the value
  const rangeMin = filterValues.priceRange[0];
  const rangeMax = filterValues.priceRange[1];
  React.useEffect(() => {
    setPriceMinInput(String(rangeMin));
    setPriceMaxInput(String(rangeMax));
  }, [rangeMin, rangeMax]);

  const commitPriceRange = () => {
    let lo = parseInt(priceMinInput, 10);
    let hi = parseInt(priceMaxInput, 10);
    if (isNaN(lo)) lo = minPrice;
    if (isNaN(hi)) hi = maxPrice;
    lo = Math.max(minPrice, Math.min(lo, maxPrice));
    hi = Math.max(minPrice, Math.min(hi, maxPrice));
    if (lo > hi) [lo, hi] = [hi, lo];
    const newRange: [number, number] = [lo, hi];
    if (newRange[0] !== filterValues.priceRange[0] || newRange[1] !== filterValues.priceRange[1]) {
      onFilterChange({ ...filterValues, priceRange: newRange });
    }
  };

  // ─── Active filter count ───
  const activeCount =
    (searchQuery.trim() ? 1 : 0) +
    (filterValues.tradition ? 1 : 0) +
    filterValues.funeralTypes.length +
    (filterValues.verifiedOnly ? 1 : 0) +
    (filterValues.onlineArrangements ? 1 : 0) +
    (filterValues.priceRange[0] !== minPrice || filterValues.priceRange[1] !== maxPrice ? 1 : 0);

  const handleClear = () => {
    onSearchChange('');
    onFilterChange({
      ...EMPTY_FILTER_VALUES,
      priceRange: [minPrice, maxPrice],
    });
  };

  const handleFuneralTypeToggle = (value: string) => {
    const current = filterValues.funeralTypes;
    const next = current.includes(value) ? current.filter((v) => v !== value) : [...current, value];
    onFilterChange({ ...filterValues, funeralTypes: next });
  };

  // ─── Shared JSX fragments (used by desktop + mobile-map layouts) ───────────

  /** The full filter-dialog content — used by both desktop's sticky FilterPanel
   *  and the mobile-map floating FilterPanel. */
  const filterDialogChildren = (
    <>
      {/* ── Service tradition ── */}
      <Box>
        <Typography variant="labelLg" sx={sectionHeadingSx}>
          Service tradition
        </Typography>
        <Autocomplete
          value={filterValues.tradition}
          onChange={(_, newValue) => onFilterChange({ ...filterValues, tradition: newValue })}
          options={traditionOptions}
          renderInput={(params) => (
            <TextField {...params} placeholder="Search traditions..." size="small" />
          )}
          clearOnEscape
          size="small"
        />
      </Box>

      <Divider />

      {/* ── Funeral type ── */}
      <Box>
        <Typography variant="labelLg" sx={sectionHeadingSx}>
          Funeral type
        </Typography>
        <Box sx={chipWrapSx}>
          {funeralTypeOptions.map((option) => (
            <Chip
              key={option.value}
              label={option.label}
              selected={filterValues.funeralTypes.includes(option.value)}
              onClick={() => handleFuneralTypeToggle(option.value)}
              variant="outlined"
              size="medium"
            />
          ))}
        </Box>
      </Box>

      <Divider />

      {/* ── Provider features ── Switch aligned to the first text line so
          wrapped labels read cleanly on narrow screens */}
      <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
        <FormControlLabel
          control={
            <Switch
              checked={filterValues.verifiedOnly}
              onChange={(_, checked) => onFilterChange({ ...filterValues, verifiedOnly: checked })}
            />
          }
          label="Verified providers only"
          sx={{
            mx: 0,
            alignItems: 'flex-start',
            '& .MuiFormControlLabel-label': { pt: 0.75 },
          }}
        />
        <FormControlLabel
          control={
            <Switch
              checked={filterValues.onlineArrangements}
              onChange={(_, checked) =>
                onFilterChange({ ...filterValues, onlineArrangements: checked })
              }
            />
          }
          label="Online arrangements available"
          sx={{
            mx: 0,
            alignItems: 'flex-start',
            '& .MuiFormControlLabel-label': { pt: 0.75 },
          }}
        />
      </Box>

      <Divider />

      {/* ── Price range ── */}
      <Box>
        <Typography variant="labelLg" sx={sectionHeadingSx}>
          Price range
        </Typography>
        <Box sx={{ px: 2.5, mb: 1 }}>
          <Slider
            value={filterValues.priceRange}
            onChange={(_, newValue) =>
              onFilterChange({
                ...filterValues,
                priceRange: newValue as [number, number],
              })
            }
            min={minPrice}
            max={maxPrice}
            step={100}
            valueLabelDisplay="auto"
            valueLabelFormat={(v) => `$${v.toLocaleString('en-AU')}`}
            color="primary"
          />
        </Box>
        <Box sx={{ display: 'flex', gap: 1, alignItems: 'center' }}>
          <TextField
            size="small"
            value={priceMinInput}
            onChange={(e) => setPriceMinInput(e.target.value.replace(/[^0-9]/g, ''))}
            onBlur={commitPriceRange}
            onKeyDown={(e) => e.key === 'Enter' && commitPriceRange()}
            InputProps={{
              startAdornment: <InputAdornment position="start">$</InputAdornment>,
            }}
            inputProps={{
              inputMode: 'numeric',
              'aria-label': 'Minimum price',
              style: { padding: '6px 0' },
            }}
            sx={{ flex: 1, '& .MuiOutlinedInput-root': { fontSize: '0.875rem' } }}
          />
          <Typography variant="caption" color="text.secondary">
            –
          </Typography>
          <TextField
            size="small"
            value={priceMaxInput}
            onChange={(e) => setPriceMaxInput(e.target.value.replace(/[^0-9]/g, ''))}
            onBlur={commitPriceRange}
            onKeyDown={(e) => e.key === 'Enter' && commitPriceRange()}
            InputProps={{
              startAdornment: <InputAdornment position="start">$</InputAdornment>,
            }}
            inputProps={{
              inputMode: 'numeric',
              'aria-label': 'Maximum price',
              style: { padding: '6px 0' },
            }}
            sx={{ flex: 1, '& .MuiOutlinedInput-root': { fontSize: '0.875rem' } }}
          />
        </Box>
      </Box>
    </>
  );

  // ─── Mobile map-first layout ───────────────────────────────────────────────

  if (showMobileMapLayout) {
    return (
      <Box
        sx={{
          display: 'flex',
          flexDirection: 'column',
          height: '100vh',
          overflow: 'hidden',
          bgcolor: 'background.default',
        }}
      >
        {navigation}

        <Box component="main" sx={{ position: 'relative', flex: 1, minHeight: 0 }}>
          {/* Full-bleed map */}
          <Box sx={{ position: 'absolute', inset: 0, display: 'flex' }}>
            <ProviderMap
              ref={mapRef}
              providers={providers}
              onSelectProvider={onSelectProvider}
              externalisePopups
              onActiveChange={setMapActive}
            />
          </Box>

          {/* Floating control strip — no container chrome; each control has
              its own fill/border so it reads cleanly over any map tile */}
          <Box
            sx={{
              position: 'absolute',
              top: 12,
              left: 12,
              right: 12,
              zIndex: 2,
              display: 'flex',
              flexDirection: 'column',
              gap: 1,
            }}
          >
            {/* Search input — committed-chip pattern, chrome via controlInputSx */}
            <LocationSearchInput
              value={searchQuery}
              onChange={onSearchChange}
              onCommit={onSearch}
              aria-label="Search providers by town or suburb"
              sx={controlInputSx}
            />

            {/* Control row: Filters, Sort by, view toggle.
                Each control reads as part of one chip set — shared outline,
                radius, fill, and shadow via CONTROL_CHROME. */}
            <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
              <FilterPanel activeCount={activeCount} onClear={handleClear} sx={filterTriggerSx}>
                {filterDialogChildren}
              </FilterPanel>

              {/* Sort — compact trigger on the mobile floating strip */}
              <SortMenu
                value={sortBy}
                onChange={(v) => onSortChange?.(v as ProviderSortBy)}
                options={SORT_OPTIONS}
                variant="compact"
                sx={controlButtonSx}
              />

              {/* View toggle — right-aligned; same outline/radius/fill/shadow
                  as Filters + Sort, with brand fill on the selected side. */}
              <ToggleButtonGroup
                value={viewMode}
                exclusive
                onChange={(_, val) => val && onViewModeChange?.(val as ListViewMode)}
                size="small"
                aria-label="View mode"
                sx={[{ ml: 'auto', flexShrink: 0 }, controlToggleSx]}
              >
                <ToggleButton value="list" aria-label="List view">
                  List
                </ToggleButton>
                <ToggleButton value="map" aria-label="Map view">
                  Map
                </ToggleButton>
              </ToggleButtonGroup>
            </Box>
          </Box>

          {/* Bottom drawer — slides up when a pin/cluster is active */}
          <MapProviderDrawer
            active={mapActive}
            onClose={() => mapRef.current?.clearActive()}
            onSelectProvider={onSelectProvider}
            onDrillIntoProvider={(id) => mapRef.current?.drillIntoProvider(id)}
          />
        </Box>

        {/* Sticky help bar — shared HelpBar molecule so this footer stays
            identical to WizardLayout's (which we bypass in this branch). */}
        <HelpBar />
      </Box>
    );
  }

  // ─── Desktop + mobile-list layout ──────────────────────────────────────────

  return (
    <WizardLayout
      variant="list-map"
      navigation={navigation}
      progressStepper={progressStepper}
      runningTotal={runningTotal}
      showBackLink
      backLabel="Back"
      onBack={onBack}
      sx={sx}
      secondaryPanel={
        <Box sx={{ position: 'relative', flex: 1, display: 'flex' }}>
          {/* Floating view toggle — same chrome as the sticky-bar controls,
              anchored to the map panel's top-left. */}
          <ToggleButtonGroup
            value={viewMode}
            exclusive
            onChange={(_, val) => val && onViewModeChange?.(val as ListViewMode)}
            size="small"
            aria-label="View mode"
            sx={[
              { position: 'absolute', top: 12, left: 12, zIndex: 1 },
              controlToggleSx,
              { '& .MuiToggleButton-root': { gap: 0.75 } },
            ]}
          >
            <ToggleButton value="list" aria-label="List view">
              <ViewListOutlinedIcon sx={{ fontSize: 16 }} />
              List
            </ToggleButton>
            <ToggleButton value="map" aria-label="Map view">
              <MapOutlinedIcon sx={{ fontSize: 16 }} />
              Map
            </ToggleButton>
          </ToggleButtonGroup>

          {/* Map content */}
          {mapPanel || (
            <Box
              sx={{
                flex: 1,
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'center',
                bgcolor: 'var(--fa-color-surface-cool)',
                borderLeft: '1px solid',
                borderColor: 'divider',
              }}
            >
              <Typography variant="body1" color="text.secondary">
                Map coming soon
              </Typography>
            </Box>
          )}
        </Box>
      }
    >
      {/* Heading — scrolls with listings */}
      <Typography variant="h4" component="h1" sx={{ mb: 0.5, pt: 2 }} tabIndex={-1}>
        Find a funeral director
      </Typography>
      <Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
        {subheading}
      </Typography>

      {/* Sticky controls — search + filters pinned while listings scroll */}
      <Box
        sx={{
          position: 'sticky',
          top: 0,
          zIndex: 1,
          bgcolor: 'background.default',
          pt: 3,
          pb: 1.5,
          mx: { xs: -2, md: -3 },
          px: { xs: 2, md: 3 },
          borderBottom: '1px solid',
          borderColor: 'divider',
        }}
      >
        {/* Location search — committed location renders as a chip inside
            the input. Shared with the mobile-map floating strip via the
            LocationSearchInput molecule. */}
        <LocationSearchInput
          value={searchQuery}
          onChange={onSearchChange}
          onCommit={onSearch}
          aria-label="Search providers by town or suburb"
          sx={[controlInputSx, { mb: 1.5 }]}
        />

        {/* Control bar — filters + sort */}
        <Box
          sx={{
            display: 'flex',
            alignItems: 'center',
            gap: 1,
          }}
        >
          <FilterPanel activeCount={activeCount} onClear={handleClear} sx={filterTriggerSx}>
            {filterDialogChildren}
          </FilterPanel>

          {/* Sort — compact "Sort by" on mobile (grouped left next to
              Filters); verbose "Sort: <label>" on desktop (pushed right). */}
          <Box sx={{ ml: { xs: 0, md: 'auto' } }}>
            <SortMenu
              value={sortBy}
              onChange={(v) => onSortChange?.(v as ProviderSortBy)}
              options={SORT_OPTIONS}
              variant={isMobile ? 'compact' : 'verbose'}
              sx={controlButtonSx}
            />
          </Box>

          {/* Mobile-only view toggle — pinned to the right via ml: auto on xs.
              Shares the same CONTROL_CHROME as Filters + Sort. */}
          <ToggleButtonGroup
            value={viewMode}
            exclusive
            onChange={(_, val) => val && onViewModeChange?.(val as ListViewMode)}
            size="small"
            aria-label="View mode"
            sx={[
              { display: { xs: 'inline-flex', md: 'none' }, ml: 'auto', flexShrink: 0 },
              controlToggleSx,
            ]}
          >
            <ToggleButton value="list" aria-label="List view">
              List
            </ToggleButton>
            <ToggleButton value="map" aria-label="Map view">
              Map
            </ToggleButton>
          </ToggleButtonGroup>
        </Box>

        {/* Results count — below controls */}
        <Typography
          variant="caption"
          color="text.secondary"
          sx={{ mt: 3, display: 'block' }}
          aria-live="polite"
        >
          <Box component="span" sx={{ fontWeight: 600, color: 'text.primary' }}>
            {providers.length}
          </Box>{' '}
          provider{providers.length !== 1 ? 's' : ''} found
        </Typography>
      </Box>

      {/* Provider list — click-to-navigate (D-D) */}
      <Box
        role="list"
        aria-label="Funeral providers"
        sx={{
          display: 'flex',
          flexDirection: 'column',
          gap: 2,
          pb: 3,
          pt: 2,
          px: { xs: 2, md: 3 },
          mx: { xs: -2, md: -3 },
          bgcolor: 'var(--fa-color-surface-subtle)',
        }}
      >
        {providers.map((provider) => (
          <ProviderCard
            key={provider.id}
            name={provider.name}
            location={provider.location}
            verified={provider.verified}
            imageUrl={provider.imageUrl}
            logoUrl={provider.logoUrl}
            rating={provider.rating}
            reviewCount={provider.reviewCount}
            startingPrice={provider.startingPrice}
            onClick={() => onSelectProvider(provider.id)}
            aria-label={`${provider.name}, ${provider.location}${provider.rating ? `, rated ${provider.rating}` : ''}${provider.startingPrice ? `, from $${provider.startingPrice}` : ''}`}
          />
        ))}

        {providers.length === 0 && (
          <Box sx={{ py: 6, textAlign: 'center' }}>
            <Typography variant="body1" color="text.secondary" sx={{ mb: 1 }}>
              No providers found matching your search.
            </Typography>
            <Typography variant="body2" color="text.secondary">
              Try adjusting your search or clearing filters.
            </Typography>
          </Box>
        )}
      </Box>
    </WizardLayout>
  );
};

ProvidersStep.displayName = 'ProvidersStep';
export default ProvidersStep;
