import type { FeatureFilters, FeatureMeta } from '../types';
import type { VariantFilterConfig } from './variant-filter';

export const SPECIFIC_CRIMES_FILTER_NAME = 'Specific crimes';
export const SPECIFIC_CRIMES_FILTER_KEY_PREFIX = `${SPECIFIC_CRIMES_FILTER_NAME}:`;

// The "pick one crime type" filter selects a single street-level category, with
// a toggle to measure it over the 7-year or the recent 2-year window. The 7-year
// window is the default/primary one (also headlined in the area pane).
export const SPECIFIC_CRIME_TYPES = [
  'Violence and sexual offences',
  'Burglary',
  'Robbery',
  'Vehicle crime',
  'Anti-social behaviour',
  'Criminal damage and arson',
  'Other theft',
  'Theft from the person',
  'Shoplifting',
  'Bicycle theft',
  'Drugs',
  'Possession of weapons',
  'Public order',
  'Other crime',
] as const;

export const SPECIFIC_CRIME_WINDOW_DEFAULT = '7y';
export const SPECIFIC_CRIME_WINDOWS = ['7y', '2y'] as const;
export type SpecificCrimeWindow = (typeof SPECIFIC_CRIME_WINDOWS)[number];

/** The backend rate-feature column for a crime type in a given window. */
export function specificCrimeFeatureName(type: string, window: SpecificCrimeWindow): string {
  return `${type} (/yr, ${window})`;
}

// Canonical dropdown enumeration: one feature name per crime type, in the
// default (7-year) window. The window toggle re-points these to the chosen window.
export const SPECIFIC_CRIME_FEATURE_NAMES = SPECIFIC_CRIME_TYPES.map((type) =>
  specificCrimeFeatureName(type, SPECIFIC_CRIME_WINDOW_DEFAULT)
);

// Every recognized specific-crime feature, across all windows. Used to detect /
// resolve a filter key's backend column for either window.
const SPECIFIC_CRIME_FEATURE_NAME_SET = new Set<string>(
  SPECIFIC_CRIME_TYPES.flatMap((type) =>
    SPECIFIC_CRIME_WINDOWS.map((window) => specificCrimeFeatureName(type, window))
  )
);

const SPECIFIC_CRIME_WINDOW_RE = / \(\/yr, (7y|2y)\)$/;

/** The window suffix of a specific-crime feature name (e.g. "2y"), or null. */
export function getSpecificCrimeWindow(featureName: string): SpecificCrimeWindow | null {
  const match = featureName.match(SPECIFIC_CRIME_WINDOW_RE);
  return match ? (match[1] as SpecificCrimeWindow) : null;
}

/** The bare crime type of a specific-crime feature name (e.g. "Burglary"), or null. */
export function getSpecificCrimeType(featureName: string): string | null {
  const match = featureName.match(SPECIFIC_CRIME_WINDOW_RE);
  return match ? featureName.slice(0, match.index) : null;
}

/** The same crime type's feature name in a different window (no-op if unrecognized). */
export function withSpecificCrimeWindow(featureName: string, window: SpecificCrimeWindow): string {
  const type = getSpecificCrimeType(featureName);
  return type ? specificCrimeFeatureName(type, window) : featureName;
}

export function isSpecificCrimeFeatureName(name: string): boolean {
  return SPECIFIC_CRIME_FEATURE_NAME_SET.has(name);
}

export function isSpecificCrimeFilterName(name: string): boolean {
  return isSpecificCrimeFeatureName(name) || name.startsWith(SPECIFIC_CRIMES_FILTER_KEY_PREFIX);
}

export function createSpecificCrimeFilterKey(featureName: string, id: number | string): string {
  return `${SPECIFIC_CRIMES_FILTER_KEY_PREFIX}${encodeURIComponent(featureName)}:${id}`;
}

export function getSpecificCrimeFilterKeyId(name: string): string | null {
  if (!name.startsWith(SPECIFIC_CRIMES_FILTER_KEY_PREFIX)) return null;
  const rest = name.substring(SPECIFIC_CRIMES_FILTER_KEY_PREFIX.length);
  const lastColon = rest.lastIndexOf(':');
  return lastColon === -1 ? null : rest.substring(lastColon + 1);
}

export function parseSpecificCrimeFilterKey(name: string): string | null {
  if (!name.startsWith(SPECIFIC_CRIMES_FILTER_KEY_PREFIX)) return null;
  const rest = name.substring(SPECIFIC_CRIMES_FILTER_KEY_PREFIX.length);
  const lastColon = rest.lastIndexOf(':');
  if (lastColon === -1) return null;

  const decoded = decodeURIComponent(rest.substring(0, lastColon));
  return isSpecificCrimeFeatureName(decoded) ? decoded : null;
}

export function getSpecificCrimeFeatureName(name: string): string | null {
  if (isSpecificCrimeFeatureName(name)) return name;
  return parseSpecificCrimeFilterKey(name);
}

export function replaceSpecificCrimeFilterKeySelection(key: string, featureName: string): string {
  const id = getSpecificCrimeFilterKeyId(key) ?? '0';
  return createSpecificCrimeFilterKey(featureName, id);
}

export function getDefaultSpecificCrimeFeatureName(features: FeatureMeta[]): string | null {
  return (
    SPECIFIC_CRIME_FEATURE_NAMES.find((name) =>
      features.some((feature) => feature.name === name)
    ) ?? null
  );
}

export function normalizeSpecificCrimeFilters(filters: FeatureFilters): FeatureFilters {
  let changed = false;
  const next: FeatureFilters = {};

  for (const [name, value] of Object.entries(filters)) {
    if (isSpecificCrimeFeatureName(name)) {
      next[createSpecificCrimeFilterKey(name, Object.keys(next).length)] = value;
      changed = true;
      continue;
    }
    next[name] = value;
  }

  return changed ? next : filters;
}

export function getSpecificCrimeFilterMeta(features: FeatureMeta[]): FeatureMeta {
  const sourceFeatureName = getDefaultSpecificCrimeFeatureName(features);
  const sourceFeature = sourceFeatureName
    ? features.find((feature) => feature.name === sourceFeatureName)
    : undefined;

  return {
    name: SPECIFIC_CRIMES_FILTER_NAME,
    type: 'numeric',
    group: 'Crime',
    min: sourceFeature?.min ?? 0,
    max: sourceFeature?.max ?? 100,
    step: 1,
    description:
      'Violence, burglary, robbery, drugs, shoplifting, vehicle crime, anti-social behaviour, public order, theft, and other crime types',
    detail:
      'Filter by one street-level crime category at a time, as the average number of recorded incidents per year near the postcode. Toggle between the 7-year and the recent 2-year average.',
    source: 'crime',
    suffix: '',
  };
}

export function clampSpecificCrimeRange(
  value: [number, number],
  feature?: FeatureMeta
): [number, number] {
  const min = feature?.histogram?.min ?? feature?.min ?? 0;
  const max = feature?.histogram?.max ?? feature?.max ?? Math.max(1, value[1]);
  return [Math.max(min, Math.min(value[0], max)), Math.max(min, Math.min(value[1], max))];
}

export const SPECIFIC_CRIME_VARIANT_CONFIG: VariantFilterConfig = {
  filterName: SPECIFIC_CRIMES_FILTER_NAME,
  featureNames: SPECIFIC_CRIME_FEATURE_NAMES,
  dropdownLabelKey: 'filters.crimeType',
  getFilterMeta: getSpecificCrimeFilterMeta,
  getDefaultFeatureName: getDefaultSpecificCrimeFeatureName,
  getFeatureName: getSpecificCrimeFeatureName,
  replaceFilterKeySelection: replaceSpecificCrimeFilterKeySelection,
  clampRange: clampSpecificCrimeRange,
  // Dropdown shows the bare crime type; the window toggle carries the 7y/2y suffix.
  getOptionLabelSource: (name) => getSpecificCrimeType(name) ?? name,
  window: {
    labelKey: 'filters.crimeWindow',
    options: [
      { id: '7y', labelKey: 'filters.crimeWindow7y' },
      { id: '2y', labelKey: 'filters.crimeWindow2y' },
    ],
    getWindow: getSpecificCrimeWindow,
    withWindow: (name, windowId) => withSpecificCrimeWindow(name, windowId as SpecificCrimeWindow),
  },
};
