import type { FeatureFilters, FeatureMeta } from '../types';
import type { VariantFilterConfig } from './variant-filter';
import {
  SPECIFIC_CRIME_WINDOW_DEFAULT,
  clampSpecificCrimeRange,
  getSpecificCrimeType,
  getSpecificCrimeWindow,
  specificCrimeFeatureName,
  withSpecificCrimeWindow,
  type SpecificCrimeWindow,
} from './crime-filter';

// "Serious crime" and "Minor crime" are aggregate severity rollups (they overlap
// the 14 detailed categories, so they must stay OUT of the "Specific crimes"
// dropdown). Each is a single feature with a 7-year/2-year window toggle, the
// same folding the specific-crimes card has, but with no variant dropdown (one
// variant). Modeled on the multi-filter-name POI pattern: one lib, two filter
// names, distinguished by their key prefix / bare crime type.

export const SERIOUS_CRIME_FILTER_NAME = 'Serious crime';
export const MINOR_CRIME_FILTER_NAME = 'Minor crime';

export const CRIME_SEVERITY_FILTER_NAMES = [
  SERIOUS_CRIME_FILTER_NAME,
  MINOR_CRIME_FILTER_NAME,
] as const;
export type CrimeSeverityFilterName = (typeof CRIME_SEVERITY_FILTER_NAMES)[number];

const CRIME_SEVERITY_TYPE_SET = new Set<string>(CRIME_SEVERITY_FILTER_NAMES);

const CRIME_SEVERITY_DETAILS: Record<
  CrimeSeverityFilterName,
  { description: string; detail: string }
> = {
  [SERIOUS_CRIME_FILTER_NAME]: {
    description: 'Violence, robbery, burglary and weapons possession near the postcode',
    detail:
      'Combined count of the more serious street-level categories (violence and sexual offences, robbery, burglary, possession of weapons), as the average number of recorded incidents per year near the postcode. Toggle between the 7-year and the recent 2-year average.',
  },
  [MINOR_CRIME_FILTER_NAME]: {
    description:
      'Anti-social behaviour, theft, criminal damage, drugs and public order near the postcode',
    detail:
      'Combined count of the lower-severity street-level categories (anti-social behaviour, theft, criminal damage and arson, drugs, public order), as the average number of recorded incidents per year near the postcode. Toggle between the 7-year and the recent 2-year average.',
  },
};

function keyPrefix(filterName: CrimeSeverityFilterName): string {
  return `${filterName}:`;
}

/** The severity filter a name belongs to (from its key prefix or bare feature name), or null. */
export function getCrimeSeverityFilterName(name: string): CrimeSeverityFilterName | null {
  for (const filterName of CRIME_SEVERITY_FILTER_NAMES) {
    if (name.startsWith(keyPrefix(filterName))) return filterName;
  }
  const type = getSpecificCrimeType(name);
  return type && CRIME_SEVERITY_TYPE_SET.has(type) ? (type as CrimeSeverityFilterName) : null;
}

export function isCrimeSeverityFeatureName(name: string): boolean {
  const type = getSpecificCrimeType(name);
  return type != null && CRIME_SEVERITY_TYPE_SET.has(type);
}

export function isCrimeSeverityFilterName(name: string): boolean {
  return getCrimeSeverityFilterName(name) != null;
}

export function createCrimeSeverityFilterKey(
  filterName: CrimeSeverityFilterName,
  featureName: string,
  id: number | string
): string {
  return `${keyPrefix(filterName)}${encodeURIComponent(featureName)}:${id}`;
}

export function getCrimeSeverityFilterKeyId(name: string): string | null {
  const filterName = getCrimeSeverityFilterName(name);
  if (!filterName) return null;
  const prefix = keyPrefix(filterName);
  if (!name.startsWith(prefix)) return null; // a bare feature name has no id
  const rest = name.substring(prefix.length);
  const lastColon = rest.lastIndexOf(':');
  return lastColon === -1 ? null : rest.substring(lastColon + 1);
}

export function parseCrimeSeverityFilterKey(name: string): string | null {
  const filterName = getCrimeSeverityFilterName(name);
  if (!filterName) return null;
  const prefix = keyPrefix(filterName);
  if (!name.startsWith(prefix)) return null;
  const rest = name.substring(prefix.length);
  const lastColon = rest.lastIndexOf(':');
  if (lastColon === -1) return null;

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

export function getCrimeSeverityFeatureName(name: string): string | null {
  if (isCrimeSeverityFeatureName(name)) return name;
  return parseCrimeSeverityFilterKey(name);
}

export function replaceCrimeSeverityFilterKeySelection(key: string, featureName: string): string {
  const filterName = getCrimeSeverityFilterName(key) ?? getCrimeSeverityFilterName(featureName);
  const id = getCrimeSeverityFilterKeyId(key) ?? '0';
  // filterName is always resolvable here: the key carries a prefix and a window
  // switch keeps the same severity type. Fall back defensively to the key as-is.
  if (!filterName) return key;
  return createCrimeSeverityFilterKey(filterName, featureName, id);
}

/** The default (7-year) backend feature for a severity, if present in `features`. */
export function getDefaultCrimeSeverityFeatureName(
  features: FeatureMeta[],
  filterName: CrimeSeverityFilterName
): string | null {
  const name = specificCrimeFeatureName(filterName, SPECIFIC_CRIME_WINDOW_DEFAULT);
  return features.some((feature) => feature.name === name) ? name : null;
}

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

  for (const [name, value] of Object.entries(filters)) {
    if (isCrimeSeverityFeatureName(name)) {
      const filterName = getCrimeSeverityFilterName(name)!;
      next[createCrimeSeverityFilterKey(filterName, name, Object.keys(next).length)] = value;
      changed = true;
      continue;
    }
    next[name] = value;
  }

  return changed ? next : filters;
}

export function getCrimeSeverityFilterMeta(
  features: FeatureMeta[],
  filterName: CrimeSeverityFilterName
): FeatureMeta {
  const sourceFeatureName = getDefaultCrimeSeverityFeatureName(features, filterName);
  const sourceFeature = sourceFeatureName
    ? features.find((feature) => feature.name === sourceFeatureName)
    : undefined;

  return {
    name: filterName,
    type: 'numeric',
    group: 'Crime',
    min: sourceFeature?.min ?? 0,
    max: sourceFeature?.max ?? 100,
    step: 1,
    description: CRIME_SEVERITY_DETAILS[filterName].description,
    detail: CRIME_SEVERITY_DETAILS[filterName].detail,
    source: 'crime',
    suffix: '',
  };
}

export function clampCrimeSeverityRange(
  value: [number, number],
  feature?: FeatureMeta
): [number, number] {
  return clampSpecificCrimeRange(value, feature);
}

/** The variant-filter config for one severity (single variant + 7y/2y window toggle). */
export function getCrimeSeverityVariantConfig(
  filterName: CrimeSeverityFilterName
): VariantFilterConfig {
  return {
    filterName,
    // One variant: the severity itself, in the default window. The card hides the
    // (single-option) dropdown and exposes only the window toggle.
    featureNames: [specificCrimeFeatureName(filterName, SPECIFIC_CRIME_WINDOW_DEFAULT)],
    dropdownLabelKey: 'filters.crimeType',
    getFilterMeta: (features) => getCrimeSeverityFilterMeta(features, filterName),
    getDefaultFeatureName: (features) => getDefaultCrimeSeverityFeatureName(features, filterName),
    getFeatureName: getCrimeSeverityFeatureName,
    replaceFilterKeySelection: replaceCrimeSeverityFilterKeySelection,
    clampRange: clampCrimeSeverityRange,
    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),
    },
  };
}
