import type { ViewState } from '../types';

export const INITIAL_RETRY_MS = 1000;
export const MAX_RETRY_MS = 10000;

/** Lower percentile for color-range clipping (0–100) */
export const COLOR_RANGE_LOW_PERCENTILE = 5;
/** Upper percentile for color-range clipping (0–100) */
export const COLOR_RANGE_HIGH_PERCENTILE = 95;

export const MAP_BOUNDS: [number, number, number, number] = [-9.5, 49, 5, 57];
export const MAP_MIN_ZOOM = 5.5;

export const BUFFER_MULTIPLIER = 1;

/**
 * Default demo centre (Canary Wharf). Used as the initial map view and the demo
 * free-zone centre when the visitor declines the "check your area" prompt. When
 * they consent, their GPS location is used instead (see lib/api `setDemoCenter`).
 * Keep in sync with FREE_ZONE_FALLBACK_LAT/LON in the Rust server.
 */
export const CANARY_WHARF = { latitude: 51.5054, longitude: -0.0235 };

/** 10 Downing Street — geolocation fallback in dev so the GPS flows stay testable
 *  without a real fix (e.g. over http, where browsers deny geolocation). */
export const DEV_LOCATION = { latitude: 51.5033635, longitude: -0.1276248 };

export const INITIAL_VIEW_STATE: ViewState = {
  longitude: CANARY_WHARF.longitude,
  latitude: CANARY_WHARF.latitude,
  zoom: 14,
  pitch: 0,
};

/** Demo (unlicensed) users can apply at most this many filters simultaneously. */
export const DEMO_MAX_FILTERS = 5;

/**
 * Zoom to H3 resolution mapping thresholds.
 * Returns the H3 resolution to use for a given zoom level.
 */
export const ZOOM_TO_RESOLUTION_THRESHOLDS = [
  { maxZoom: 7, resolution: 5 },
  { maxZoom: 9, resolution: 6 },
  { maxZoom: 10.5, resolution: 7 },
  { maxZoom: 11.5, resolution: 8 },
  { maxZoom: 14, resolution: 9 },
] as const;

export const SMALLEST_VISIBLE_HEXAGON_RESOLUTION = Math.max(
  ...ZOOM_TO_RESOLUTION_THRESHOLDS.map(({ resolution }) => resolution)
);

// Shared limit at which the zoomed-in detail layers — postcodes AND the map
// overlays (noise, crime, trees, property borders) — start showing. Set just
// past the finest hexagon level so detail appears while still relatively
// zoomed out. (Each overlay additionally can't render below its own tile-data
// floor, OVERLAY_MIN_ZOOM, regardless of this limit.)
export const POSTCODE_ZOOM_THRESHOLD = 14;
export const POSTCODE_SEARCH_ZOOM = 16;

export const FEATURE_GRADIENT: { t: number; color: [number, number, number] }[] = [
  { t: 0, color: [46, 204, 113] },
  { t: 0.33, color: [241, 196, 15] },
  { t: 0.66, color: [231, 76, 60] },
  { t: 1, color: [142, 68, 173] },
];

export type GradientStop = { t: number; color: [number, number, number] };

function partyGradient(color: [number, number, number]): GradientStop[] {
  return [
    { t: 0, color: [255, 255, 255] },
    {
      t: 0.5,
      color: [
        Math.round(255 + (color[0] - 255) * 0.45),
        Math.round(255 + (color[1] - 255) * 0.45),
        Math.round(255 + (color[2] - 255) * 0.45),
      ],
    },
    { t: 1, color },
  ];
}

/** UK party colours for the 2024 General Election vote-share map layers. */
export const PARTY_FEATURE_GRADIENTS: Record<string, GradientStop[]> = {
  '% Labour': partyGradient([228, 0, 59]), // Labour red
  '% Conservative': partyGradient([0, 135, 220]), // Conservative blue
  '% Liberal Democrat': partyGradient([255, 100, 0]), // Liberal Democrat orange
  '% Reform UK': partyGradient([18, 182, 207]), // Reform UK cyan
  '% Green': partyGradient([106, 176, 35]), // Green Party green
  '% Other parties': partyGradient([107, 114, 128]), // neutral color for grouped parties
};

export const PARTY_FEATURE_COLORS: Record<string, string> = Object.fromEntries(
  Object.entries(PARTY_FEATURE_GRADIENTS).map(([featureName, gradient]) => {
    const color = gradient[gradient.length - 1].color;
    return [featureName, `rgb(${color[0]}, ${color[1]}, ${color[2]})`];
  })
);

export function getFeatureGradient(featureName: string | null | undefined): GradientStop[] {
  return featureName
    ? (PARTY_FEATURE_GRADIENTS[featureName] ?? FEATURE_GRADIENT)
    : FEATURE_GRADIENT;
}

/** Number of properties gradient — light mode (cream → orange) */
export const DENSITY_GRADIENT: { t: number; color: [number, number, number] }[] = [
  { t: 0, color: [255, 255, 255] },
  { t: 0.1, color: [248, 233, 211] },
  { t: 0.5, color: [255, 221, 173] },
  { t: 0.8, color: [251, 171, 60] },
  { t: 1, color: [255, 162, 31] },
];

/** Number of properties gradient — dark mode (dark warm → bright amber) */
export const DENSITY_GRADIENT_DARK: { t: number; color: [number, number, number] }[] = [
  { t: 0, color: [55, 45, 35] },
  { t: 0.1, color: [85, 65, 40] },
  { t: 0.5, color: [170, 115, 50] },
  { t: 0.8, color: [230, 155, 45] },
  { t: 1, color: [255, 170, 40] },
];

/** Protomaps font glyphs URL (served locally from public/assets/) */
export const GLYPHS_URL = '/assets/fonts/{fontstack}/{range}.pbf';

/** Twemoji base URL (served locally from public/assets/) */
export const TWEMOJI_BASE = '/assets/twemoji/';

/** POI group → RGB color for category-coded map markers */
export const POI_GROUP_COLORS: Record<string, [number, number, number]> = {
  'Public Transport': [59, 130, 246],
  Leisure: [249, 115, 22],
  Education: [139, 92, 246],
  Health: [239, 68, 68],
  'Emergency Services': [220, 38, 38],
  Other: [107, 114, 128],
  Groceries: [34, 197, 94],
  'Local Businesses': [245, 158, 11],
  Culture: [236, 72, 153],
  Services: [6, 182, 212],
  Shops: [99, 102, 241],
};

/** POI category → icon/logo URL for branded and transport categories */
export const POI_CATEGORY_LOGOS: Record<string, string> = {
  Airport: '/assets/twemoji/2708.png',
  Aldi: '/assets/poi-icons/logos/aldi.svg',
  Amazon: '/assets/poi-icons/brands_2024/amazon_fresh.svg',
  Asda: '/assets/poi-icons/logos/asda.svg',
  'Asda Express': '/assets/poi-icons/logos/asda.svg',
  'Asda Living': '/assets/poi-icons/logos/asda.svg',
  'Asda PFS': '/assets/poi-icons/logos/asda.svg',
  'Asda Supercentre': '/assets/poi-icons/logos/asda.svg',
  'Asda Supermarket': '/assets/poi-icons/logos/asda.svg',
  'Asda Superstore': '/assets/poi-icons/logos/asda.svg',
  Bakery: '/assets/twemoji/1f950.png',
  Booths: '/assets/poi-icons/brands_2024/booths.svg',
  Budgens: '/assets/poi-icons/brands_2024/budgens.svg',
  'Bus station': '/assets/twemoji/1f68c.png',
  'Bus stop': '/assets/twemoji/1f68f.png',
  'Butcher & Fishmonger': '/assets/twemoji/1f969.png',
  Centra: '/assets/poi-icons/logos/centra.svg',
  'Co-op': '/assets/poi-icons/logos/coop.svg',
  COOK: '/assets/poi-icons/brands_2024/cook.svg',
  'Convenience Store': '/assets/twemoji/1f3ea.png',
  Costco: '/assets/poi-icons/logos/costco.svg',
  'Deli & Specialty': '/assets/twemoji/1f9c6.png',
  'Dunnes Stores': '/assets/poi-icons/brands_2024/dunnes_stores.svg',
  Farmfoods: '/assets/poi-icons/brands_2023/supermarkets/farmfoods.svg',
  Ferry: '/assets/twemoji/26f4.png',
  Greengrocer: '/assets/twemoji/1f96c.png',
  'Heron Foods': '/assets/poi-icons/brands_2023/supermarkets/heron_foods.svg',
  Iceland: '/assets/poi-icons/brands_2024/iceland.svg',
  Lidl: '/assets/poi-icons/logos/lidl.svg',
  Makro: '/assets/poi-icons/brands_2024/makro.svg',
  'M&S': '/assets/poi-icons/brands_2024/mns.svg',
  'M&S Clothing': '/assets/poi-icons/brands_2024/mns.svg',
  'M&S Food': '/assets/poi-icons/visuals/mns.svg',
  'M&S Hospital': '/assets/poi-icons/brands_2024/mns.svg',
  'M&S MSA': '/assets/poi-icons/brands_2024/mns.svg',
  'M&S Outlet': '/assets/poi-icons/brands_2024/mns.svg',
  Morrisons: '/assets/poi-icons/logos/morrisons.svg',
  'Morrisons Daily': '/assets/poi-icons/brands_2024/morrisons_daily.svg',
  'Off-Licence': '/assets/twemoji/1f377.png',
  'Planet Organic': '/assets/poi-icons/logos/planet_organic.svg',
  'Rail station': '/assets/twemoji/1f686.png',
  School: '/assets/twemoji/1f3eb.png',
  'Nursery school': '/assets/twemoji/1f9f8.png',
  'Primary school': '/assets/twemoji/1f392.png',
  'Secondary school': '/assets/twemoji/1f3eb.png',
  'All-through school': '/assets/twemoji/1f3eb.png',
  'Sixth form': '/assets/twemoji/1f4da.png',
  'Further education college': '/assets/twemoji/1f4da.png',
  University: '/assets/twemoji/1f393.png',
  'Special school': '/assets/twemoji/1f91d.png',
  "Sainsbury's": '/assets/poi-icons/logos/sainsburys.svg',
  "Sainsbury's Local": '/assets/poi-icons/brands_2024/sainsburys_local.svg',
  Spar: '/assets/poi-icons/logos/spar.svg',
  Supermarket: '/assets/twemoji/1f6d2.png',
  Tesco: '/assets/poi-icons/logos/tesco.svg',
  'Tesco Express': '/assets/poi-icons/logos/tesco_express.svg',
  'Tesco Extra': '/assets/poi-icons/logos/tesco_extra.svg',
  'Taxi rank': '/assets/twemoji/1f695.png',
  'The Food Warehouse': '/assets/poi-icons/logos/the_food_warehouse.png',
  'Tube station': '/assets/poi-icons/public_transport/london_tube.svg',
  Waitrose: '/assets/poi-icons/logos/waitrose.svg',
  'Little Waitrose': '/assets/poi-icons/brands_2023/supermarkets/little_waitrose.svg',
  'Whole Foods Market': '/assets/poi-icons/brands_2024/wholefoods.svg',
};

/** Categories only shown when zoomed in past MINOR_POI_ZOOM_THRESHOLD */
export const MINOR_POI_CATEGORIES = new Set(['Bus stop', 'Taxi rank', 'EV Charging', 'Playground']);

/** Zoom level below which minor POI categories are hidden */
export const MINOR_POI_ZOOM_THRESHOLD = 14;

/** Supercluster grouping radius in pixels */
export const POI_CLUSTER_RADIUS = 50;

/** Zoom level at which supercluster stops clustering */
export const POI_CLUSTER_MAX_ZOOM = 15;

/** Zoom level at which individual POI cards are shown without hovering */
export const POI_AUTO_CARD_ZOOM_THRESHOLD = POI_CLUSTER_MAX_ZOOM + 1;

/**
 * Groups whose features should be collapsed into stacked bar charts.
 * Keyed by feature group name. Each entry defines one stacked chart.
 */
export const STACKED_GROUPS: Record<
  string,
  {
    /** Display label for the chart */
    label: string;
    /** If set, use this feature's stats for the total and info popup. Otherwise sum components. */
    feature?: string;
    /** If set, display this feature's mean as the primary value (e.g. per-1k rate) instead of the absolute total. */
    rateFeature?: string;
    /** Suffix shown after the total value (e.g. "avg/yr") */
    unit?: string;
    /** Feature names that make up the segments */
    components: string[];
  }[]
> = {
  Crime: [
    {
      label: 'Serious crime',
      feature: 'Serious crime (avg/yr)',
      unit: '',
      components: [
        'Violence and sexual offences (avg/yr)',
        'Robbery (avg/yr)',
        'Burglary (avg/yr)',
        'Possession of weapons (avg/yr)',
      ],
    },
    {
      label: 'Minor crime',
      feature: 'Minor crime (avg/yr)',
      unit: '',
      components: [
        'Anti-social behaviour (avg/yr)',
        'Criminal damage and arson (avg/yr)',
        'Shoplifting (avg/yr)',
        'Bicycle theft (avg/yr)',
        'Theft from the person (avg/yr)',
        'Other theft (avg/yr)',
        'Vehicle crime (avg/yr)',
        'Public order (avg/yr)',
        'Drugs (avg/yr)',
        'Other crime (avg/yr)',
      ],
    },
  ],
  Neighbours: [
    {
      label: 'Ethnic composition',
      unit: '%',
      components: [
        '% White',
        '% South Asian',
        '% East Asian',
        '% SE Asian',
        '% Black',
        '% Mixed',
        '% Other',
      ],
    },
    {
      label: 'Political vote share',
      unit: '%',
      components: [
        '% Labour',
        '% Conservative',
        '% Liberal Democrat',
        '% Reform UK',
        '% Green',
        '% Other parties',
      ],
    },
  ],
};

/**
 * Groups whose enum features should be collapsed into compact multi-row charts.
 * Keyed by feature group name. Each entry defines one stacked enum chart.
 */
export const STACKED_ENUM_GROUPS: Record<
  string,
  {
    /** Display label for the chart */
    label: string;
    /** If set, use this feature for the info popup */
    feature?: string;
    /** Enum feature names that make up the rows */
    components: string[];
    /** Value order for consistent segment ordering */
    valueOrder: string[];
    /** Colors for each value (matches valueOrder) */
    valueColors: string[];
  }[]
> = {
  Property: [
    {
      label: 'Property type',
      feature: 'Property type',
      components: ['Property type'],
      valueOrder: ['Detached', 'Semi-Detached', 'Terraced', 'Flats/Maisonettes', 'Other'],
      valueColors: ['#f97316', '#3b82f6', '#22c55e', '#ec4899', '#6b7280'],
    },
    {
      label: 'Leasehold/Freehold',
      feature: 'Leasehold/Freehold',
      components: ['Leasehold/Freehold'],
      valueOrder: ['Freehold', 'Leasehold'],
      valueColors: ['#3b82f6', '#f59e0b'],
    },
  ],
};

/**
 * Maximally-distinguishable palette for discrete enum features on the map.
 * 10 colors chosen for perceptual distinctness in both light and dark modes.
 */
export const ENUM_PALETTE: [number, number, number][] = [
  [59, 130, 246], // blue-500
  [249, 115, 22], // orange-500
  [139, 92, 246], // violet-500
  [34, 197, 94], // green-500
  [239, 68, 68], // red-500
  [6, 182, 212], // cyan-500
  [236, 72, 153], // pink-500
  [245, 158, 11], // amber-500
  [20, 184, 166], // teal-500
  [107, 114, 128], // gray-500
];

/**
 * Per-feature color definitions for enum values on the map and dashboard.
 * Keys are feature names (as returned by the server), values map enum value → RGB.
 */
export const ENUM_COLOR_OVERRIDES: Record<string, Record<string, [number, number, number]>> = {
  'Property type': {
    Detached: [249, 115, 22], // orange
    'Semi-Detached': [59, 130, 246], // blue
    Terraced: [34, 197, 94], // green
    'Flats/Maisonettes': [236, 72, 153], // pink
    Other: [107, 114, 128], // gray
  },
  'Leasehold/Freehold': {
    Freehold: [59, 130, 246],
    Leasehold: [245, 158, 11],
  },
  'Former council house': {
    Yes: [239, 68, 68],
    No: [34, 197, 94],
  },
  'Within conservation area': {
    Yes: [20, 184, 166],
    No: [107, 114, 128],
  },
  'Listed building': {
    Yes: [245, 158, 11],
    No: [107, 114, 128],
  },
  'Current energy rating': {
    A: [22, 163, 74],
    B: [132, 204, 22],
    C: [234, 179, 8],
    D: [245, 158, 11],
    E: [249, 115, 22],
    F: [239, 68, 68],
    G: [126, 34, 206],
  },
  'Potential energy rating': {
    A: [22, 163, 74],
    B: [132, 204, 22],
    C: [234, 179, 8],
    D: [245, 158, 11],
    E: [249, 115, 22],
    F: [239, 68, 68],
    G: [126, 34, 206],
  },
};

/**
 * Build the 10-color shader palette for a given enum feature.
 * The trailing slots are invisible for features with fewer than 10 enum values.
 */
export function getEnumPaletteForFeature(
  featureName: string,
  values: string[]
): [number, number, number][] {
  const overrides = ENUM_COLOR_OVERRIDES[featureName];
  if (!overrides) {
    throw new Error(`Missing enum color definitions for '${featureName}'`);
  }

  const palette: [number, number, number][] = [];
  for (let i = 0; i < 10; i++) {
    if (i < values.length) {
      const color = overrides[values[i]];
      if (!color) {
        throw new Error(`Missing enum color for '${featureName}' value '${values[i]}'`);
      }
      palette.push(color);
    } else {
      palette.push([0, 0, 0]);
    }
  }
  return palette;
}

/** Look up the configured color for a specific enum value. */
export function getEnumValueColor(
  featureName: string,
  valueName: string
): [number, number, number] {
  const color = ENUM_COLOR_OVERRIDES[featureName]?.[valueName];
  if (!color) {
    throw new Error(`Missing enum color for '${featureName}' value '${valueName}'`);
  }
  return color;
}

/** Explicit colors for stacked bar segments. */
export const STACKED_SEGMENT_COLORS: Record<string, string> = {
  'Violence and sexual offences (avg/yr)': '#ef4444',
  'Robbery (avg/yr)': '#f97316',
  'Burglary (avg/yr)': '#eab308',
  'Possession of weapons (avg/yr)': '#8b5cf6',
  'Anti-social behaviour (avg/yr)': '#14b8a6',
  'Criminal damage and arson (avg/yr)': '#f97316',
  'Shoplifting (avg/yr)': '#ec4899',
  'Bicycle theft (avg/yr)': '#22c55e',
  'Theft from the person (avg/yr)': '#d946ef',
  'Other theft (avg/yr)': '#06b6d4',
  'Vehicle crime (avg/yr)': '#3b82f6',
  'Public order (avg/yr)': '#8b5cf6',
  'Drugs (avg/yr)': '#22c55e',
  'Other crime (avg/yr)': '#6b7280',
  '% White': '#3b82f6',
  '% South Asian': '#f97316',
  '% East Asian': '#eab308',
  '% SE Asian': '#ec4899',
  '% Black': '#8b5cf6',
  '% Mixed': '#14b8a6',
  '% Other': '#6b7280',
  'Anti-social': '#14b8a6',
  Vehicle: '#3b82f6',
  Burglary: '#eab308',
  Other: '#6b7280',
};
