import { useMemo, useState } from 'react';
import type {
  FeatureFilters,
  FeatureMeta,
  HexagonStatsResponse,
  PostcodeFeature,
} from '../../types';
import type { TravelTimeEntry } from '../../hooks/useTravelTime';
import type { HexagonLocation } from '../../lib/external-search';
import { formatValue, formatFilterValue, calculateHistogramMean } from '../../lib/format';
import { groupFeaturesByCategory } from '../../lib/features';
import { STACKED_GROUPS, STACKED_ENUM_GROUPS } from '../../lib/consts';
import { DualHistogram, LoadingSkeleton } from './DualHistogram';
import EnumBarChart from './EnumBarChart';
import StackedBarChart from './StackedBarChart';
import StackedEnumChart from './StackedEnumChart';
import PriceHistoryChart from './PriceHistoryChart';
import ExternalSearchLinks from './ExternalSearchLinks';
import { InfoIcon } from '../ui/icons';
import { CollapsibleGroupHeader } from '../ui/CollapsibleGroupHeader';
import { FeatureInfoPopup } from '../ui/FeatureInfoPopup';
import { EmptyState } from '../ui/EmptyState';
import { FeatureLabel } from '../ui/FeatureLabel';
import StreetViewEmbed from './StreetViewEmbed';
import HistogramLegend from './HistogramLegend';
import JourneyInstructions from './JourneyInstructions';

interface AreaPaneProps {
  stats: HexagonStatsResponse | null;
  globalFeatures: FeatureMeta[];
  loading: boolean;
  hexagonId: string | null;
  isPostcode?: boolean;
  postcodeData?: PostcodeFeature | null;
  onViewProperties: () => void;
  hexagonLocation: HexagonLocation | null;
  filters: FeatureFilters;
  onNavigateToSource?: (slug: string, featureName: string) => void;
  travelTimeEntries?: TravelTimeEntry[];
  isGroupExpanded: (name: string) => boolean;
  onToggleGroup: (name: string) => void;
}

export default function AreaPane({
  stats,
  globalFeatures,
  loading,
  hexagonId,
  isPostcode = false,
  postcodeData,
  onViewProperties,
  hexagonLocation,
  filters,
  onNavigateToSource,
  travelTimeEntries,
  isGroupExpanded,
  onToggleGroup,
}: AreaPaneProps) {
  const propertyCount = isPostcode && postcodeData ? postcodeData.properties.count : stats?.count;
  const featureGroups = useMemo(() => groupFeaturesByCategory(globalFeatures), [globalFeatures]);
  const [infoFeature, setInfoFeature] = useState<FeatureMeta | null>(null);

  const numericByName = useMemo(() => {
    if (!stats) return new Map();
    return new Map(stats.numeric_features.map((feature) => [feature.name, feature]));
  }, [stats]);

  const enumByName = useMemo(() => {
    if (!stats) return new Map();
    return new Map(stats.enum_features.map((feature) => [feature.name, feature]));
  }, [stats]);

  const globalFeatureByName = useMemo(
    () => new Map(globalFeatures.map((f) => [f.name, f])),
    [globalFeatures]
  );

  if (!hexagonId) {
    return (
      <EmptyState
        icon={<InfoIcon className="w-8 h-8 text-warm-300 dark:text-warm-600" />}
        title="No area selected"
        description="Click any coloured area on the map to see crime, schools, prices, and more"
        centered
      />
    );
  }

  return (
    <>
      <div className="h-full overflow-y-auto">
        <div className="p-3">
          <div className="flex items-center gap-2">
            <div>
              <h2 className="text-sm font-semibold dark:text-warm-100">
                {isPostcode ? hexagonId : 'Area Statistics'}
              </h2>
              {isPostcode && (
                <span className="text-xs text-warm-500 dark:text-warm-400">Postcode</span>
              )}
            </div>
            {loading && stats && (
              <div className="w-3 h-3 border-2 border-teal-600 dark:border-teal-400 border-t-transparent rounded-full animate-spin" />
            )}
          </div>
          {propertyCount != null && (
            <p className="text-sm text-warm-600 dark:text-warm-400 mt-1">
              {propertyCount.toLocaleString()} properties
            </p>
          )}
          <p className="text-xs text-warm-500 dark:text-warm-400 mt-1">
            Stats for {isPostcode ? 'current and historical' : 'all'} properties in this{' '}
            {isPostcode ? 'postcode' : 'area'}
            {Object.keys(filters).length > 0 ? ' matching all active filters' : ''}
          </p>
          {stats && stats.count > 0 && (
            <button
              onClick={onViewProperties}
              className="mt-2 w-full text-sm py-1.5 rounded bg-teal-600 hover:bg-teal-700 text-white font-medium"
            >
              View {stats.count.toLocaleString()} Properties
            </button>
          )}
        </div>

        {hexagonLocation && stats && (
          <ExternalSearchLinks location={hexagonLocation} filters={filters} />
        )}
        {(() => {
          const journeyPostcode = isPostcode ? hexagonId : stats?.central_postcode;
          return journeyPostcode && travelTimeEntries && travelTimeEntries.length > 0 ? (
            <JourneyInstructions
              postcode={journeyPostcode}
              entries={travelTimeEntries}
              label={!isPostcode ? journeyPostcode : undefined}
            />
          ) : null;
        })()}
        {loading && !stats ? (
          <LoadingSkeleton />
        ) : stats ? (
          <div>
            {hexagonLocation && <StreetViewEmbed location={hexagonLocation} />}
            <HistogramLegend />
            {stats.price_history &&
              (() => {
                const uniqueYears = new Set(stats.price_history.map((p) => Math.floor(p.year)));
                return uniqueYears.size > 1;
              })() && (
                <div className="mx-3 mt-2 bg-warm-50 dark:bg-warm-800 rounded p-2">
                  <span className="text-xs text-warm-700 dark:text-warm-300">Price History</span>
                  <PriceHistoryChart points={stats.price_history} />
                </div>
              )}
            {featureGroups.map((group) => {
              const hasData = group.features.some(
                (feature) => numericByName.has(feature.name) || enumByName.has(feature.name)
              );
              if (!hasData) return null;

              const stackedCharts = STACKED_GROUPS[group.name];
              const stackedEnumCharts = STACKED_ENUM_GROUPS[group.name];

              const stackedEnumFeatureNames = new Set<string>(
                stackedEnumCharts?.flatMap((c) =>
                  [c.feature, ...c.components].filter((s): s is string => Boolean(s))
                ) ?? []
              );

              const expanded = isGroupExpanded(group.name);

              return (
                <div key={group.name}>
                  <CollapsibleGroupHeader
                    name={group.name}
                    expanded={expanded}
                    onToggle={() => onToggleGroup(group.name)}
                    className="px-3 py-2.5 text-sm font-bold text-warm-500 bg-warm-50 dark:bg-warm-900 dark:text-warm-400 sticky top-0 z-10 hover:bg-warm-100 dark:hover:bg-warm-800"
                  />
                  {expanded && (
                    <div className="px-3 py-2 space-y-3">
                      {stackedCharts
                        ? stackedCharts.map((chart) => {
                            const segments = chart.components
                              .map((name) => ({
                                name,
                                value: numericByName.get(name)?.mean ?? 0,
                              }))
                              .filter((s) => s.value > 0);

                            const aggregateStats = chart.feature
                              ? numericByName.get(chart.feature)
                              : undefined;
                            const total = aggregateStats
                              ? aggregateStats.mean
                              : segments.reduce((sum, s) => sum + s.value, 0);

                            const featureMeta = chart.feature
                              ? globalFeatureByName.get(chart.feature)
                              : undefined;

                            if (total === 0) return null;

                            return (
                              <div
                                key={chart.label}
                                className="bg-warm-50 dark:bg-warm-800 rounded p-2"
                              >
                                <div className="flex justify-between items-baseline mb-1.5">
                                  {featureMeta ? (
                                    <FeatureLabel
                                      feature={{ ...featureMeta, name: chart.label }}
                                      onShowInfo={setInfoFeature}
                                      className="mr-2"
                                    />
                                  ) : (
                                    <span className="text-xs text-warm-700 dark:text-warm-300 truncate mr-2">
                                      {chart.label}
                                    </span>
                                  )}
                                  <span className="text-xs font-semibold text-teal-700 dark:text-teal-400 whitespace-nowrap">
                                    {formatValue(total)}
                                    {chart.unit ? ` ${chart.unit}` : ''}
                                  </span>
                                </div>
                                <StackedBarChart segments={segments} total={total} />
                              </div>
                            );
                          })
                        : group.features
                            .filter((f) => !stackedEnumFeatureNames.has(f.name))
                            .map((feature) => {
                              const numericStats = numericByName.get(feature.name);
                              const enumStats = enumByName.get(feature.name);

                              if (numericStats) {
                                const globalFeature = globalFeatureByName.get(feature.name);
                                const globalHistogram = globalFeature?.histogram;
                                const globalMean = globalHistogram
                                  ? calculateHistogramMean(globalHistogram)
                                  : undefined;

                                return (
                                  <div
                                    key={feature.name}
                                    className="bg-warm-50 dark:bg-warm-800 rounded p-2"
                                  >
                                    <div className="flex justify-between items-baseline">
                                      <FeatureLabel
                                        feature={feature}
                                        onShowInfo={setInfoFeature}
                                        className="mr-2"
                                      />
                                      <span className="text-xs font-semibold text-teal-700 dark:text-teal-400 whitespace-nowrap">
                                        {formatValue(numericStats.mean, feature)}
                                      </span>
                                    </div>
                                    {numericStats.histogram &&
                                      (globalHistogram ? (
                                        <DualHistogram
                                          localCounts={numericStats.histogram.counts}
                                          globalCounts={globalHistogram.counts}
                                          p1={numericStats.histogram.p1}
                                          p99={numericStats.histogram.p99}
                                          globalMean={globalMean}
                                          formatLabel={(v) => formatFilterValue(v, feature.raw)}
                                        />
                                      ) : (
                                        <DualHistogram
                                          localCounts={numericStats.histogram.counts}
                                          globalCounts={numericStats.histogram.counts}
                                          p1={numericStats.histogram.p1}
                                          p99={numericStats.histogram.p99}
                                          formatLabel={(v) => formatFilterValue(v, feature.raw)}
                                        />
                                      ))}
                                  </div>
                                );
                              }

                              if (enumStats) {
                                return (
                                  <div
                                    key={feature.name}
                                    className="bg-warm-50 dark:bg-warm-800 rounded p-2"
                                  >
                                    <FeatureLabel feature={feature} onShowInfo={setInfoFeature} />
                                    <EnumBarChart counts={enumStats.counts} />
                                  </div>
                                );
                              }

                              return null;
                            })}
                      {stackedEnumCharts?.map((chart) => {
                        const featureMeta = chart.feature
                          ? globalFeatureByName.get(chart.feature)
                          : undefined;

                        if (chart.components.length === 1) {
                          const stats = enumByName.get(chart.components[0]);
                          if (!stats) return null;

                          const segments = chart.valueOrder
                            .map((value) => ({ name: value, value: stats.counts[value] ?? 0 }))
                            .filter((s) => s.value > 0);
                          const total = segments.reduce((sum, s) => sum + s.value, 0);
                          if (total === 0) return null;

                          return (
                            <div
                              key={chart.label}
                              className="bg-warm-50 dark:bg-warm-800 rounded p-2"
                            >
                              <div className="flex justify-between items-baseline mb-1.5">
                                {featureMeta ? (
                                  <FeatureLabel
                                    feature={featureMeta}
                                    onShowInfo={setInfoFeature}
                                    className="mr-2"
                                  />
                                ) : (
                                  <span className="text-xs text-warm-700 dark:text-warm-300 truncate mr-2">
                                    {chart.label}
                                  </span>
                                )}
                                <span className="text-xs font-semibold text-teal-700 dark:text-teal-400 whitespace-nowrap">
                                  {total.toLocaleString()}
                                </span>
                              </div>
                              <StackedBarChart
                                segments={segments}
                                total={total}
                                colorMap={Object.fromEntries(
                                  chart.valueOrder.map((v, i) => [v, chart.valueColors[i]])
                                )}
                              />
                            </div>
                          );
                        }

                        const components = chart.components
                          .map((name) => {
                            const stats = enumByName.get(name);
                            return stats ? { label: name, stats } : null;
                          })
                          .filter((c): c is NonNullable<typeof c> => c !== null);

                        if (components.length === 0) return null;

                        return (
                          <div
                            key={chart.label}
                            className="bg-warm-50 dark:bg-warm-800 rounded p-2"
                          >
                            <div className="mb-1.5">
                              {featureMeta ? (
                                <FeatureLabel
                                  feature={{ ...featureMeta, name: chart.label }}
                                  onShowInfo={setInfoFeature}
                                />
                              ) : (
                                <span className="text-xs text-warm-700 dark:text-warm-300">
                                  {chart.label}
                                </span>
                              )}
                            </div>
                            <StackedEnumChart
                              components={components}
                              valueOrder={chart.valueOrder}
                              valueColors={chart.valueColors}
                            />
                          </div>
                        );
                      })}
                    </div>
                  )}
                </div>
              );
            })}
          </div>
        ) : null}
      </div>

      {infoFeature && (
        <FeatureInfoPopup
          feature={infoFeature}
          onClose={() => setInfoFeature(null)}
          onNavigateToSource={onNavigateToSource}
        />
      )}
    </>
  );
}
