import { useTranslation } from 'react-i18next';
import { ts } from '../../../i18n/server';
import { Slider } from '../../ui/Slider';
import { ChevronIcon } from '../../ui/icons';
import { FeatureActions } from '../../ui/FeatureIcons';
import { FeatureLabel } from '../../ui/FeatureLabel';
import type { FeatureFilters, FeatureMeta } from '../../../types';
import type { VariantFilterConfig } from '../../../lib/variant-filter';
import { formatNumber, type PercentileScale } from '../../../lib/format';
import { getFeatureIcon } from '../../../lib/feature-icons';
import { getGroupIcon } from '../../../lib/group-icons';
import { SliderLabels } from './SliderLabels';

/**
 * One card for a "pick a variant, filter its range" aggregate filter
 * (specific crimes, qualifications, …). Behaviour is variant-agnostic: the
 * `config` supplies the dropdown options, labels and helper functions so the
 * same component backs every such filter (see [`VariantFilterConfig`]).
 */
export function VariantFilterCard({
  config,
  features,
  variantFeature,
  filters,
  activeFeature,
  dragValue,
  pinnedFeature,
  filterImpact,
  percentileScale,
  onFilterChange,
  onDragStart,
  onDragChange,
  onDragEnd,
  onTogglePin,
  onShowInfo,
  onRemove,
}: {
  config: VariantFilterConfig;
  features: FeatureMeta[];
  variantFeature: FeatureMeta;
  filters: FeatureFilters;
  activeFeature: string | null;
  dragValue: [number, number] | null;
  pinnedFeature: string | null;
  filterImpact?: number;
  percentileScale?: PercentileScale;
  onFilterChange: (name: string, value: [number, number] | string[]) => void;
  onDragStart: (name: string, initialValue?: [number, number]) => void;
  onDragChange: (value: [number, number]) => void;
  onDragEnd: () => void;
  onTogglePin: (name: string) => void;
  onShowInfo: (feature: FeatureMeta) => void;
  onRemove: () => void;
}) {
  const { t } = useTranslation();
  const variantMeta = config.getFilterMeta(features);
  const selectedFeatureName =
    config.getFeatureName(variantFeature.name) ?? config.getDefaultFeatureName(features);
  const selectedFeature = selectedFeatureName
    ? features.find((feature) => feature.name === selectedFeatureName)
    : undefined;

  // Optional secondary axis: the same variant measured over a different window
  // (e.g. 7- vs 2-year crime rates). The current window comes from the selected
  // feature so the dropdown can re-point each option into that window.
  const windowConfig = config.window;
  const currentWindow =
    (windowConfig && selectedFeatureName ? windowConfig.getWindow(selectedFeatureName) : null) ??
    windowConfig?.options[0]?.id ??
    null;

  const variantOptions = config.featureNames
    .map((canonicalName) => {
      const optionName =
        windowConfig && currentWindow
          ? windowConfig.withWindow(canonicalName, currentWindow)
          : canonicalName;
      const feature = features.find((f) => f.name === optionName);
      if (!feature) return null;
      return {
        value: optionName,
        label: ts(
          config.getOptionLabelSource ? config.getOptionLabelSource(optionName) : optionName
        ),
        feature,
      };
    })
    .filter((option): option is { value: string; label: string; feature: FeatureMeta } =>
      Boolean(option)
    );

  // Only offer windows that actually exist for the selected variant, so a
  // missing backend column can't strand the card on an empty selection.
  const windowOptions =
    windowConfig && selectedFeatureName
      ? windowConfig.options.filter((option) =>
          features.some((f) => f.name === windowConfig.withWindow(selectedFeatureName, option.id))
        )
      : [];

  if (!selectedFeature || variantOptions.length === 0 || !selectedFeatureName) return null;

  const isActive = activeFeature === variantFeature.name;
  const isPinned = pinnedFeature === variantFeature.name;
  const hist = selectedFeature.histogram;
  const dataMin = hist?.min ?? selectedFeature.min ?? 0;
  const dataMax = hist?.max ?? selectedFeature.max ?? 100;
  const displayValue =
    isActive && dragValue
      ? dragValue
      : (filters[variantFeature.name] as [number, number]) || [dataMin, dataMax];
  const scale = percentileScale;
  const clampMin = displayValue[0] <= dataMin;
  const clampMax = displayValue[1] >= dataMax;
  const isAtMin = displayValue[0] === dataMin;
  const isAtMax = displayValue[1] === dataMax;
  const sliderValue: [number, number] = scale
    ? [
        clampMin ? 0 : Math.round(scale.toPercentile(displayValue[0])),
        clampMax ? 100 : Math.round(scale.toPercentile(displayValue[1])),
      ]
    : [
        clampMin ? (selectedFeature.min ?? dataMin) : displayValue[0],
        clampMax ? (selectedFeature.max ?? dataMax) : displayValue[1],
      ];

  const replaceVariantFeature = (nextFeatureName: string) => {
    const nextName = config.replaceFilterKeySelection(variantFeature.name, nextFeatureName);
    if (nextName === variantFeature.name) return;

    const nextFeature = features.find((feature) => feature.name === nextFeatureName);
    const nextDataMin = nextFeature?.histogram?.min ?? nextFeature?.min ?? 0;
    const nextDataMax = nextFeature?.histogram?.max ?? nextFeature?.max ?? Math.max(1, dataMax);
    const nextRange = config.clampRange(
      [
        displayValue[0] <= dataMin ? nextDataMin : displayValue[0],
        displayValue[1] >= dataMax ? nextDataMax : displayValue[1],
      ],
      nextFeature
    );

    onFilterChange(nextName, nextRange);
    if (isPinned) onTogglePin(nextName);
  };

  const switchWindow = (windowId: string) => {
    if (!windowConfig || windowId === currentWindow) return;
    replaceVariantFeature(windowConfig.withWindow(selectedFeatureName, windowId));
  };

  const mobileIconClass = 'w-4 h-4 text-teal-600 dark:text-teal-400 shrink-0';
  const mobileIcon =
    getFeatureIcon(selectedFeature.name, mobileIconClass) ||
    (() => {
      const G = selectedFeature.group ? getGroupIcon(selectedFeature.group) : null;
      return G ? <G className={mobileIconClass} /> : null;
    })();

  return (
    <div
      data-filter-name={config.filterName}
      className={`space-y-1.5 px-2 py-1.5 rounded ${
        isActive
          ? 'ring-2 ring-teal-400 bg-teal-50 dark:bg-teal-900/30'
          : isPinned
            ? 'ring-2 ring-teal-400 bg-teal-50/50 dark:bg-teal-900/20'
            : ''
      }`}
    >
      <div className="relative z-10 flex items-center justify-between gap-1">
        <FeatureLabel
          feature={variantMeta}
          size="sm"
          className="min-w-0 shrink"
          hideIconOnMobile
          wrap
        />
        <FeatureActions
          feature={selectedFeature}
          actionName={variantFeature.name}
          isPinned={isPinned}
          isPreviewing={isActive}
          onTogglePin={onTogglePin}
          onShowInfo={onShowInfo}
          onRemove={onRemove}
        />
      </div>

      {/* A single-variant filter (e.g. Serious/Minor crime) has nothing to pick,
          so the dropdown is hidden and only the window toggle + slider remain. */}
      {variantOptions.length > 1 && (
        <div>
          <label className="mb-1 block text-[10px] font-medium uppercase text-warm-400 dark:text-warm-500">
            {t(config.dropdownLabelKey)}
          </label>
          <div className="relative">
            <select
              value={selectedFeatureName}
              onChange={(e) => replaceVariantFeature(e.target.value)}
              className="w-full appearance-none rounded-md border border-warm-200 bg-warm-50 px-2 py-1.5 pr-8 text-sm font-medium text-navy-950 shadow-inner outline-none transition-colors hover:bg-white focus:border-teal-400 focus:ring-2 focus:ring-teal-200 dark:border-warm-700 dark:bg-navy-900 dark:text-warm-100 dark:hover:bg-navy-800 dark:focus:ring-teal-900/50"
            >
              {variantOptions.map((option) => (
                <option key={option.value} value={option.value}>
                  {option.label}
                </option>
              ))}
            </select>
            <ChevronIcon
              direction="down"
              className="pointer-events-none absolute right-2 top-1/2 h-4 w-4 -translate-y-1/2 text-warm-400 dark:text-warm-500"
            />
          </div>
        </div>
      )}

      {windowConfig && currentWindow && windowOptions.length > 1 && (
        <div>
          {windowConfig.labelKey && (
            <label className="mb-1 block text-[10px] font-medium uppercase text-warm-400 dark:text-warm-500">
              {t(windowConfig.labelKey)}
            </label>
          )}
          <div
            role="group"
            className="inline-flex rounded-md border border-warm-200 bg-warm-50 p-0.5 dark:border-warm-700 dark:bg-navy-900"
          >
            {windowOptions.map((option) => {
              const active = option.id === currentWindow;
              return (
                <button
                  key={option.id}
                  type="button"
                  aria-pressed={active}
                  onClick={() => switchWindow(option.id)}
                  className={`rounded px-2.5 py-1 text-xs font-medium transition-colors ${
                    active
                      ? 'bg-teal-600 text-white shadow-sm'
                      : 'text-warm-500 hover:text-navy-950 dark:text-warm-400 dark:hover:text-warm-100'
                  }`}
                >
                  {t(option.labelKey)}
                </button>
              );
            })}
          </div>
        </div>
      )}

      <div className="flex items-start gap-1.5 md:block">
        {mobileIcon && <div className="shrink-0 pt-0.5 md:hidden">{mobileIcon}</div>}
        <div className="min-w-0 flex-1">
          <Slider
            min={scale ? 0 : (selectedFeature.min ?? dataMin)}
            max={scale ? 100 : (selectedFeature.max ?? dataMax)}
            step={
              scale
                ? 1
                : (selectedFeature.step ??
                  ((selectedFeature.max ?? dataMax) - (selectedFeature.min ?? dataMin)) / 100)
            }
            value={sliderValue}
            onValueChange={
              scale
                ? ([pMin, pMax]) => {
                    const step = selectedFeature.step ?? 1;
                    const snap = (v: number) => Math.round(v / step) * step;
                    onDragChange([
                      pMin <= 0 ? dataMin : snap(scale.toValue(pMin)),
                      pMax >= 100 ? dataMax : snap(scale.toValue(pMax)),
                    ]);
                  }
                : ([min, max]) =>
                    onDragChange([
                      min <= (selectedFeature.min ?? dataMin) ? dataMin : min,
                      max >= (selectedFeature.max ?? dataMax) ? dataMax : max,
                    ])
            }
            onPointerDown={() => onDragStart(variantFeature.name, displayValue)}
            onPointerUp={() => onDragEnd()}
          />
          <SliderLabels
            min={scale ? 0 : (selectedFeature.min ?? dataMin)}
            max={scale ? 100 : (selectedFeature.max ?? dataMax)}
            value={sliderValue}
            displayValues={displayValue}
            isAtMin={isAtMin}
            isAtMax={isAtMax}
            raw={selectedFeature.raw}
            feature={selectedFeature}
            onValueChange={(v) =>
              onFilterChange(variantFeature.name, config.clampRange(v, selectedFeature))
            }
          />
          {filterImpact != null && filterImpact > 0 && (
            <p className="-mt-1 ml-2.5 text-[10px] text-warm-400 dark:text-warm-500">
              {t('filters.filtersOut', { value: formatNumber(filterImpact) })}
            </p>
          )}
        </div>
      </div>
    </div>
  );
}
