import { useState, useEffect, useMemo, useCallback, useRef } from 'react';
import type { FeatureMeta, FeatureFilters, POICategoryGroup, ViewState, PostcodeGeometry } from '../../types';
import type { SearchedLocation } from './LocationSearch';
import type { Page } from '../ui/Header';
import Map from './Map';
import Filters from './Filters';
import POIPane from './POIPane';
import { PropertiesPane } from './PropertiesPane';
import AreaPane from './AreaPane';
import MobileDrawer from './MobileDrawer';
import MapLegend from './MapLegend';
import { TabButton } from '../ui/TabButton';
import { useMapData } from '../../hooks/useMapData';
import { usePOIData } from '../../hooks/usePOIData';
import { useFilters } from '../../hooks/useFilters';
import { useHexagonSelection } from '../../hooks/useHexagonSelection';
import { usePaneResize } from '../../hooks/usePaneResize';
import { useAiFilters } from '../../hooks/useAiFilters';
import { useUrlSync } from '../../hooks/useUrlSync';
import { useTutorial } from '../../hooks/useTutorial';
import { getTutorialStyles } from '../../lib/tutorial-styles';
import Joyride from 'react-joyride';
import {
  useTravelTime,
  MODE_LABELS,
  travelFieldKey,
  type TravelTimeInitial,
} from '../../hooks/useTravelTime';
import { apiUrl, authHeaders, buildFilterString, logNonAbortError } from '../../lib/api';
import { INITIAL_VIEW_STATE } from '../../lib/consts';
import { useLicense } from '../../hooks/useLicense';
import UpgradeModal from '../ui/UpgradeModal';
import { SpinnerIcon } from '../ui/icons/SpinnerIcon';
import { MapPinIcon } from '../ui/icons/MapPinIcon';

export interface ExportState {
  onExport: () => void;
  exporting: boolean;
}

interface MapPageProps {
  features: FeatureMeta[];
  poiCategoryGroups: POICategoryGroup[];
  initialFilters: FeatureFilters;
  initialViewState: ViewState;
  initialPOICategories: Set<string>;
  initialTab: 'properties' | 'area';
  initialLoading: boolean;
  theme: 'light' | 'dark';
  pendingInfoFeature: string | null;
  onClearPendingInfoFeature: () => void;
  onNavigateTo: (page: Page, hash?: string, infoFeature?: string) => void;
  onExportStateChange?: (state: ExportState) => void;
  screenshotMode?: boolean;
  ogMode?: boolean;
  isMobile?: boolean;
  initialTravelTime?: TravelTimeInitial;
  user?: { id: string; subscription: string } | null;
  onLoginClick?: () => void;
  onRegisterClick?: () => void;
}

export default function MapPage({
  features,
  poiCategoryGroups,
  initialFilters,
  initialViewState,
  initialPOICategories,
  initialTab,
  initialLoading,
  theme,
  pendingInfoFeature,
  onClearPendingInfoFeature,
  onNavigateTo,
  onExportStateChange,
  screenshotMode,
  ogMode,
  isMobile = false,
  initialTravelTime,
  user,
  onLoginClick,
  onRegisterClick,
}: MapPageProps) {
  const [selectedPOICategories, setSelectedPOICategories] =
    useState<Set<string>>(initialPOICategories);

  const [leftPaneWidth, leftPaneHandlers] = usePaneResize(384, 200, 600, 'left');
  const [rightPaneWidth, rightPaneHandlers] = usePaneResize(288, 200, 500, 'right');

  const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
  const [poiPaneOpen, setPoiPaneOpen] = useState(false);

  const {
    filters,
    activeFeature,
    dragValue,
    pinnedFeature,
    enabledFeatures,
    viewFeature,
    viewSource,
    filterRange,
    handleAddFilter,
    handleFilterChange,
    handleRemoveFilter,
    handleSetFilters,
    handleDragStart,
    handleDragChange,
    handleDragEnd,
    handleTogglePin,
    handleSetPin,
    handleCancelPin,
  } = useFilters({
    initialFilters,
    features,
  });

  const aiFilters = useAiFilters();
  const handleAiFilterSubmit = useCallback(
    async (query: string) => {
      const result = await aiFilters.fetchAiFilters(query);
      if (result) handleSetFilters(result.filters);
    },
    [aiFilters.fetchAiFilters, handleSetFilters]
  );

  const travelTime = useTravelTime(initialTravelTime);

  const handleTravelTimeSetDestination = useCallback(
    (index: number, slug: string, label: string) => {
      travelTime.handleSetDestination(index, slug, label);
      const entry = travelTime.entries[index];
      if (entry) {
        handleSetPin(`tt_${entry.mode}_${slug}`);
      }
    },
    [travelTime.handleSetDestination, travelTime.entries, handleSetPin]
  );

  const handleTravelTimeRemoveEntry = useCallback(
    (index: number) => {
      const entry = travelTime.entries[index];
      if (entry?.slug && pinnedFeature === travelFieldKey(entry)) {
        handleCancelPin();
      }
      travelTime.handleRemoveEntry(index);
    },
    [travelTime.handleRemoveEntry, travelTime.entries, pinnedFeature, handleCancelPin]
  );

  const license = useLicense();

  const mapFlyToRef = useRef<((lat: number, lng: number, zoom: number) => void) | null>(null);

  const mapData = useMapData({
    filters,
    features,
    viewFeature,
    activeFeature,
    dragValue,
    travelTimeEntries: travelTime.entries,
  });

  const selection = useHexagonSelection({
    filters,
    features,
    resolution: mapData.resolution,
  });

  const handleLocationSearchResult = useCallback(
    (result: SearchedLocation | null) => {
      if (result) {
        selection.handleLocationSearch(result.postcode, result.geometry);
        if (isMobile) setMobileDrawerOpen(true);
      } else {
        selection.handleCloseSelection();
      }
    },
    [selection.handleLocationSearch, selection.handleCloseSelection, isMobile]
  );

  const handleZoomToFreeZone = useCallback(() => {
    mapFlyToRef.current?.(
      INITIAL_VIEW_STATE.latitude,
      INITIAL_VIEW_STATE.longitude,
      INITIAL_VIEW_STATE.zoom
    );
  }, []);

  const pois = usePOIData(mapData.bounds, selectedPOICategories);

  useUrlSync(mapData.currentView, filters, features, selectedPOICategories, selection.rightPaneTab, travelTime.entries);

  useEffect(() => {
    mapData.setInitialView(initialViewState);
    selection.setRightPaneTab(initialTab);
  }, []); // eslint-disable-line react-hooks/exhaustive-deps

  const { handleHexagonClick } = selection;
  const handleMobileHexagonClick = useCallback(
    (id: string, isPostcode?: boolean, geometry?: PostcodeGeometry) => {
      handleHexagonClick(id, isPostcode, geometry);
      if (id) {
        setMobileDrawerOpen(true);
      }
    },
    [handleHexagonClick]
  );

  const hexagonLocation = useMemo(() => {
    const hexId = selection.selectedHexagon?.id;
    const isPostcode = selection.selectedHexagon?.type === 'postcode';

    if (isPostcode) {
      // For postcodes, get centroid from postcodeData; postcode string is the selection id
      const postcodeFeature = mapData.postcodeData.find((f) => f.properties.postcode === hexId);
      if (!postcodeFeature?.properties.centroid) return null;
      const [lon, lat] = postcodeFeature.properties.centroid;
      return { lat, lon, resolution: mapData.resolution, postcode: hexId, isPostcode: true };
    } else {
      // For hexagons, get lat/lon from hexagon data; central postcode comes from stats
      const hex = hexId ? mapData.data.find((d) => d.h3 === hexId) : null;
      if (!hex || typeof hex.lat !== 'number' || typeof hex.lon !== 'number') return null;
      return {
        lat: hex.lat as number,
        lon: hex.lon as number,
        resolution: mapData.resolution,
        postcode: selection.areaStats?.central_postcode,
      };
    }
  }, [
    selection.selectedHexagon?.id,
    selection.selectedHexagon?.type,
    mapData.data,
    mapData.postcodeData,
    mapData.resolution,
    selection.areaStats?.central_postcode,
  ]);

  const tutorial = useTutorial(initialLoading, isMobile);

  const [exporting, setExporting] = useState(false);
  const handleExport = useCallback(() => {
    if (!mapData.bounds || exporting) return;
    const { south, west, north, east } = mapData.bounds;
    const params = new URLSearchParams({
      bounds: `${south},${west},${north},${east}`,
    });
    const filterStr = buildFilterString(filters, features);
    if (filterStr) params.set('filters', filterStr);
    const url = apiUrl('export', params);

    setExporting(true);
    fetch(url, authHeaders())
      .then((res) => {
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        return res.blob();
      })
      .then((blob) => {
        const link = document.createElement('a');
        link.href = URL.createObjectURL(blob);
        link.download = 'perfect-postcode-export.xlsx';
        link.click();
        URL.revokeObjectURL(link.href);
      })
      .catch((err) => logNonAbortError('Export failed', err))
      .finally(() => setExporting(false));
  }, [mapData.bounds, filters, features, exporting]);

  useEffect(() => {
    onExportStateChange?.({ onExport: handleExport, exporting });
  }, [handleExport, exporting, onExportStateChange]);

  const mobileLegendMeta = useMemo(
    () => (viewFeature ? features.find((f) => f.name === viewFeature) || null : null),
    [viewFeature, features]
  );
  const mobileDensityRange = useMemo((): [number, number] => {
    const items = mapData.usePostcodeView ? mapData.postcodeData : mapData.data;
    if (items.length === 0) return [0, 1];
    let min = Infinity;
    let max = -Infinity;
    for (const d of items) {
      const c = 'count' in d ? d.count : d.properties.count;
      if (c < min) min = c;
      if (c > max) max = c;
    }
    if (min === Infinity) return [0, 1];
    if (min === max) return [min, min + 1];
    return [min, max];
  }, [mapData.data, mapData.postcodeData, mapData.usePostcodeView]);

  useEffect(() => {
    if (screenshotMode && !mapData.loading && mapData.data.length > 0) {
      window.__screenshot_ready = true;
    }
  }, [screenshotMode, mapData.loading, mapData.data.length]);

  if (screenshotMode) {
    return (
      <div className="h-full w-full">
        <Map
          data={mapData.data}
          postcodeData={mapData.postcodeData}
          usePostcodeView={mapData.usePostcodeView}
          pois={[]}
          onViewChange={mapData.handleViewChange}
          viewFeature={viewFeature}
          colorRange={mapData.colorRange}
          filterRange={filterRange}
          viewSource={viewSource}
          onCancelPin={() => {}}
          features={features}
          selectedHexagonId={null}
          hoveredHexagonId={null}
          onHexagonClick={() => {}}
          onHexagonHover={() => {}}
          initialViewState={initialViewState}
          theme={theme}
          screenshotMode
          ogMode={ogMode}
          bounds={mapData.bounds}
          travelTimeEntries={travelTime.entries}
        />
      </div>
    );
  }

  const renderAreaPane = () => (
    <AreaPane
      stats={selection.areaStats}
      globalFeatures={features}
      loading={selection.loadingAreaStats}
      hexagonId={selection.selectedHexagon?.id || null}
      isPostcode={selection.selectedHexagon?.type === 'postcode'}
      postcodeData={
        selection.selectedHexagon?.type === 'postcode'
          ? mapData.postcodeData.find(
              (f) => f.properties.postcode === selection.selectedHexagon?.id
            ) || null
          : null
      }
      onViewProperties={selection.handleViewPropertiesFromArea}
      onClose={selection.handleCloseSelection}
      hexagonLocation={hexagonLocation}
      filters={filters}
    />
  );

  const renderPropertiesPane = () => (
    <PropertiesPane
      properties={selection.properties}
      total={selection.propertiesTotal}
      loading={selection.loadingProperties}
      hexagonId={selection.selectedHexagon?.id || null}
      onLoadMore={selection.handleLoadMoreProperties}
    />
  );

  const renderPOIPane = () => (
    <POIPane
      groups={poiCategoryGroups}
      selectedCategories={selectedPOICategories}
      onCategoriesChange={setSelectedPOICategories}
      poiCount={pois.length}
    />
  );

  const renderFilters = () => (
    <Filters
      features={features}
      filters={filters}
      activeFeature={activeFeature}
      dragValue={dragValue}
      enabledFeatures={enabledFeatures}
      onAddFilter={handleAddFilter}
      onRemoveFilter={handleRemoveFilter}
      onFilterChange={handleFilterChange}
      onDragStart={handleDragStart}
      onDragChange={handleDragChange}
      onDragEnd={handleDragEnd}
      pinnedFeature={pinnedFeature}
      onTogglePin={handleTogglePin}
      openInfoFeature={pendingInfoFeature}
      onClearOpenInfoFeature={onClearPendingInfoFeature}
      travelTimeEntries={travelTime.entries}
      onTravelTimeAddEntry={travelTime.handleAddEntry}
      onTravelTimeRemoveEntry={handleTravelTimeRemoveEntry}
      onTravelTimeSetDestination={handleTravelTimeSetDestination}
      onTravelTimeRangeChange={travelTime.handleTimeRangeChange}
      onTravelTimeToggleBest={travelTime.handleToggleBest}
      aiFilterLoading={aiFilters.loading}
      aiFilterError={aiFilters.error}
      aiFilterNotes={aiFilters.notes}
      onAiFilterSubmit={handleAiFilterSubmit}
      isLicensed={user?.subscription === 'licensed'}
      onUpgradeClick={() => onNavigateTo('pricing')}
      onResetTutorial={tutorial.resetTutorial}
    />
  );

  if (isMobile) {
    return (
      <div className="flex-1 flex flex-col overflow-hidden relative">
        {initialLoading && (
          <div className="absolute inset-0 z-50 flex items-center justify-center bg-warm-50/80 dark:bg-navy-950/80 backdrop-blur-sm">
            <div className="flex flex-col items-center gap-4">
              <SpinnerIcon className="w-12 h-12 text-teal-600 dark:text-teal-400 animate-spin" />
              <p className="text-warm-600 dark:text-warm-300 text-sm font-medium">
                Connecting to server...
              </p>
            </div>
          </div>
        )}

        <div className="relative overflow-hidden" style={{ flex: '45 0 0' }}>
          <Map
            data={mapData.data}
            postcodeData={mapData.postcodeData}
            usePostcodeView={mapData.usePostcodeView}
            pois={pois}
            onViewChange={mapData.handleViewChange}
            viewFeature={viewFeature}
            colorRange={mapData.colorRange}
            filterRange={filterRange}
            viewSource={viewSource}
            onCancelPin={handleCancelPin}
            features={features}
            selectedHexagonId={selection.selectedHexagon?.id || null}
            hoveredHexagonId={selection.hoveredHexagon}
            onHexagonClick={handleMobileHexagonClick}
            onHexagonHover={selection.handleHexagonHover}
            initialViewState={initialViewState}
            flyToRef={mapFlyToRef}
            theme={theme}
            filters={filters}
            selectedPostcodeGeometry={selection.selectedPostcodeGeometry}
            onLocationSearched={handleLocationSearchResult}
            bounds={mapData.bounds}
            hideLegend
            travelTimeEntries={travelTime.entries}
          />
          {mapData.loading && (
            <div className="absolute inset-0 flex items-center justify-center pointer-events-none">
              <div className="flex items-center gap-2 bg-white/90 dark:bg-warm-800/90 px-4 py-2.5 rounded-lg shadow-lg pointer-events-auto">
                <SpinnerIcon className="w-5 h-5 text-teal-600 dark:text-teal-400 animate-spin" />
                <span className="text-sm font-medium text-warm-700 dark:text-warm-200">Loading...</span>
              </div>
            </div>
          )}
          <button
            onClick={() => setPoiPaneOpen((p) => !p)}
            className={`absolute bottom-2 right-2 z-10 p-2 rounded-lg shadow-lg bg-white dark:bg-warm-800 ${poiPaneOpen ? 'text-teal-600 dark:text-teal-400' : 'text-warm-500 dark:text-warm-400 hover:text-teal-600 dark:hover:text-teal-400'}`}
          >
            <MapPinIcon className="w-5 h-5" />
          </button>
          {poiPaneOpen && (
            <div className="absolute bottom-12 right-2 z-10 w-[calc(100%-1rem)] max-h-[60%] overflow-hidden flex flex-col rounded-lg shadow-xl border border-warm-200 dark:border-warm-700 bg-white dark:bg-warm-900">
              {renderPOIPane()}
            </div>
          )}
        </div>

        <div
          className="bg-white dark:bg-warm-900 border-t border-warm-200 dark:border-warm-700 overflow-hidden flex flex-col"
          style={{ flex: '55 0 0' }}
        >
          {viewFeature && mapData.colorRange ? (
            viewFeature.startsWith('tt_') ? (
              <MapLegend
                featureLabel={`Travel time (${MODE_LABELS[viewFeature.split('_')[1] as keyof typeof MODE_LABELS]})`}
                range={mapData.colorRange}
                showCancel={viewSource === 'eye'}
                onCancel={handleCancelPin}
                mode="feature"
                theme={theme}
                inline
                suffix=" min"
              />
            ) : mobileLegendMeta ? (
              <MapLegend
                featureLabel={
                  viewSource === 'eye'
                    ? `Previewing \u201c${mobileLegendMeta.name}\u201d`
                    : mobileLegendMeta.name
                }
                range={mapData.colorRange}
                showCancel={viewSource === 'eye'}
                onCancel={handleCancelPin}
                mode="feature"
                enumValues={mobileLegendMeta.type === 'enum' ? mobileLegendMeta.values : undefined}
                theme={theme}
                inline
              />
            ) : null
          ) : (
            <MapLegend
              featureLabel="Number of properties"
              range={mobileDensityRange}
              showCancel={false}
              onCancel={handleCancelPin}
              mode="density"
              theme={theme}
              inline
            />
          )}
          <div className="flex-1 min-h-0">
            {renderFilters()}
          </div>
        </div>

        {mobileDrawerOpen && selection.selectedHexagon && (
          <MobileDrawer
            onClose={() => setMobileDrawerOpen(false)}
            renderArea={renderAreaPane}
            renderProperties={renderPropertiesPane}
          />
        )}

        {mapData.licenseRequired && (
          <UpgradeModal
            isLoggedIn={!!user}
            onLoginClick={onLoginClick ?? (() => {})}
            onRegisterClick={onRegisterClick ?? (() => {})}
            onStartCheckout={() => license.startCheckout()}
            onZoomToFreeZone={handleZoomToFreeZone}
          />
        )}
      </div>
    );
  }

  return (
    <div className="flex-1 flex overflow-hidden relative">
      {initialLoading && (
        <div className="absolute inset-0 z-50 flex items-center justify-center bg-warm-50/80 dark:bg-navy-950/80 backdrop-blur-sm">
          <div className="flex flex-col items-center gap-4">
            <SpinnerIcon className="w-12 h-12 text-teal-600 dark:text-teal-400 animate-spin" />
            <p className="text-warm-600 dark:text-warm-300 text-sm font-medium">
              Connecting to server...
            </p>
          </div>
        </div>
      )}

      <Joyride
        steps={tutorial.steps}
        run={tutorial.run}
        continuous
        showProgress
        showSkipButton
        callback={tutorial.handleCallback}
        styles={getTutorialStyles(theme)}
        disableScrolling
        locale={{ last: 'Finish' }}
      />

      <div
        data-tutorial="filters"
        className="flex bg-white dark:bg-navy-950 shadow-lg overflow-hidden"
        style={{ width: leftPaneWidth }}
      >
        <div className="flex-1 flex flex-col overflow-hidden">{renderFilters()}</div>
        <div
          className="w-1.5 cursor-col-resize flex items-center justify-center bg-warm-100 dark:bg-navy-800 hover:bg-warm-200 dark:hover:bg-navy-700 border-x border-warm-200 dark:border-navy-700"
          {...leftPaneHandlers}
        >
          <div className="w-0.5 h-8 rounded bg-warm-300 dark:bg-navy-600" />
        </div>
      </div>

      <div data-tutorial="map" className="flex-1 relative">
        <Map
          data={mapData.data}
          postcodeData={mapData.postcodeData}
          usePostcodeView={mapData.usePostcodeView}
          pois={pois}
          onViewChange={mapData.handleViewChange}
          viewFeature={viewFeature}
          colorRange={mapData.colorRange}
          filterRange={filterRange}
          viewSource={viewSource}
          onCancelPin={handleCancelPin}
          features={features}
          selectedHexagonId={selection.selectedHexagon?.id || null}
          hoveredHexagonId={selection.hoveredHexagon}
          onHexagonClick={selection.handleHexagonClick}
          onHexagonHover={selection.handleHexagonHover}
          initialViewState={initialViewState}
          flyToRef={mapFlyToRef}
          theme={theme}
          filters={filters}
          selectedPostcodeGeometry={selection.selectedPostcodeGeometry}
          onLocationSearched={handleLocationSearchResult}
          bounds={mapData.bounds}
          travelTimeEntries={travelTime.entries}
        />
        {mapData.loading && (
          <div className="absolute inset-0 flex items-center justify-center pointer-events-none">
            <div className="flex items-center gap-2 bg-white/90 dark:bg-warm-800/90 px-4 py-2.5 rounded-lg shadow-lg pointer-events-auto">
              <SpinnerIcon className="w-5 h-5 text-teal-600 dark:text-teal-400 animate-spin" />
              <span className="text-sm font-medium text-warm-700 dark:text-warm-200">Loading...</span>
            </div>
          </div>
        )}
        {/* Floating POI button */}
        <button
          data-tutorial="poi-button"
          onClick={() => setPoiPaneOpen((p) => !p)}
          className={`absolute bottom-4 right-4 z-10 px-3 py-2 rounded-lg shadow-lg bg-white dark:bg-warm-800 flex items-center gap-2 ${poiPaneOpen ? 'text-teal-600 dark:text-teal-400' : 'text-warm-500 dark:text-warm-400 hover:text-teal-600 dark:hover:text-teal-400'}`}
        >
          <MapPinIcon className="w-5 h-5" />
          <span className="text-sm font-medium">Points of interest</span>
        </button>
        {/* Floating POI panel */}
        {poiPaneOpen && (
          <div className="absolute bottom-14 right-4 z-10 w-80 max-h-[60vh] overflow-hidden flex flex-col rounded-lg shadow-xl border border-warm-200 dark:border-warm-700 bg-white dark:bg-warm-900">
            {renderPOIPane()}
          </div>
        )}
      </div>

      {selection.selectedHexagon && (
        <div
          data-tutorial="right-pane"
          className="flex bg-white dark:bg-navy-950 shadow-lg z-10"
          style={{ width: rightPaneWidth }}
        >
          <div
            className="w-1.5 cursor-col-resize flex items-center justify-center bg-warm-100 dark:bg-navy-800 hover:bg-warm-200 dark:hover:bg-navy-700 border-x border-warm-200 dark:border-navy-700"
            {...rightPaneHandlers}
          >
            <div className="w-0.5 h-8 rounded bg-warm-300 dark:bg-navy-600" />
          </div>
          <div className="flex-1 flex flex-col overflow-hidden">
            <div className="flex border-b border-warm-200 dark:border-navy-700 text-sm">
              <TabButton
                label="Area"
                isActive={selection.rightPaneTab === 'area'}
                onClick={() => selection.setRightPaneTab('area')}
              />
              <TabButton
                label="Properties"
                isActive={selection.rightPaneTab === 'properties'}
                onClick={selection.handlePropertiesTabClick}
              />
            </div>

            <div className="flex-1 overflow-hidden">
              {selection.rightPaneTab === 'properties'
                ? renderPropertiesPane()
                : renderAreaPane()}
            </div>
          </div>
        </div>
      )}

      {mapData.licenseRequired && (
        <UpgradeModal
          isLoggedIn={!!user}
          onLoginClick={onLoginClick ?? (() => {})}
          onRegisterClick={onRegisterClick ?? (() => {})}
          onStartCheckout={() => license.startCheckout()}
          onZoomToFreeZone={handleZoomToFreeZone}
        />
      )}
    </div>
  );
}
