export interface FeatureMeta {
  name: string;
  type: 'numeric' | 'enum';
  group?: string;
  // Numeric-only fields
  min?: number;
  max?: number;
  step?: number;
  histogram?: { min: number; max: number; p1: number; p99: number; counts: number[] };
  // Enum-only fields
  values?: string[];
  counts?: Record<string, number>;
  // Description fields
  description?: string;
  detail?: string;
  source?: string;
  // Display format fields
  prefix?: string;
  suffix?: string;
  raw?: boolean;
  absolute?: boolean;
}

export interface FeatureGroup {
  name: string;
  features: FeatureMeta[];
}

// Filters: feature name -> [selectedMin, selectedMax] for numeric, string[] for enum
export type FeatureFilters = Record<string, [number, number] | string[]>;

export interface HexagonData {
  h3: string;
  count: number;
  lat: number;
  lon: number;
  [key: string]: string | number | null;
}

export interface PostcodeProperties {
  postcode: string;
  count: number;
  centroid: [number, number];
  [key: string]: string | number | [number, number] | null;
}

export type PostcodeFeature = GeoJSON.Feature<
  GeoJSON.Polygon | GeoJSON.MultiPolygon,
  PostcodeProperties
>;

export type PostcodeGeometry = GeoJSON.Polygon | GeoJSON.MultiPolygon;

export interface Bounds {
  south: number;
  west: number;
  north: number;
  east: number;
}

export interface ViewState {
  longitude: number;
  latitude: number;
  zoom: number;
  pitch: number;
  bearing?: number;
}

export interface MapVisibleAreaInsets {
  top?: number;
  right?: number;
  bottom?: number;
  left?: number;
  topRatio?: number;
  rightRatio?: number;
  bottomRatio?: number;
  leftRatio?: number;
}

export interface MapFlyToOptions {
  visibleArea?: MapVisibleAreaInsets;
  visibleViewportArea?: MapVisibleAreaInsets;
}

export interface ViewChangeParams {
  resolution: number;
  bounds: Bounds;
  visibleBounds: Bounds;
  zoom: number;
  latitude: number;
  longitude: number;
  visibleLatitude?: number;
  visibleLongitude?: number;
}

export interface ApiResponse {
  features: HexagonData[];
}

/** GIAS register fields surfaced for state-funded school POIs. Every entry is
 * optional because DfE does not populate every field for every establishment
 * type (a nursery has no sixth form, an FE college reports no FSM, etc.). */
export interface SchoolMetadata {
  phase?: string;
  type?: string;
  type_group?: string;
  age_range?: string;
  gender?: string;
  religious_character?: string;
  admissions_policy?: string;
  nursery_provision?: string;
  sixth_form?: string;
  capacity?: number;
  pupils?: number;
  fsm_percent?: number;
  trust?: string;
  address?: string;
  postcode?: string;
  local_authority?: string;
  website?: string;
  telephone?: string;
  head_name?: string;
  ofsted_rating?: string;
}

export interface POI {
  id: string;
  name: string;
  category: string;
  icon_category?: string;
  group: string;
  lat: number;
  lng: number;
  emoji: string;
  school?: SchoolMetadata;
}

export interface POIResponse {
  pois: POI[];
}

export interface ActualListing {
  lat: number;
  lon: number;
  postcode: string;
  address?: string;
  property_type?: string;
  property_sub_type?: string;
  leasehold_freehold?: string;
  price_qualifier?: string;
  bedrooms?: number;
  bathrooms?: number;
  rooms_total?: number;
  floor_area_sqm?: number;
  asking_price?: number;
  asking_price_per_sqm?: number;
  listing_url: string;
  listing_status?: string;
  listing_date_iso?: string;
  features: string[];
}

export interface ActualListingsResponse {
  listings: ActualListing[];
  total: number;
}

export interface POICategoryGroup {
  name: string;
  categories: string[];
}

export interface POICategoriesResponse {
  groups: POICategoryGroup[];
}

export interface PlaceResult {
  name: string;
  slug: string;
  place_type: string;
  lat: number;
  lon: number;
  city?: string;
}

export interface AddressResult {
  address: string;
  postcode: string;
  lat: number;
  lon: number;
}

export interface JourneyLeg {
  mode: string;
  from?: string;
  to?: string;
  minutes: number;
}

export interface RenovationEvent {
  year: number;
  event: string;
}

/** A point on the tenure timeline: the year an EPC certificate first recorded a
 * new occupancy `status` ("Owner-occupied" / "Rented (private)" /
 * "Rented (social)"). Surfaces when a home was let out vs lived in. */
export interface TenureEvent {
  year: number;
  status: string;
}

export interface HistoricalPrice {
  year: number;
  month: number;
  price: number;
}

export interface Property {
  address?: string;
  postcode?: string;
  property_type?: string;
  built_form?: string;
  duration?: string;
  current_energy_rating?: string;
  potential_energy_rating?: string;
  property_sub_type?: string;
  price_qualifier?: string;
  former_council_house?: string;
  within_conservation_area?: string;
  listed_building?: string;

  // Numeric fields
  lat: number;
  lon: number;

  is_construction_date_approximate?: boolean;
  renovation_history?: RenovationEvent[];
  historical_prices?: HistoricalPrice[];
  tenure_history?: TenureEvent[];

  // All other numeric features (dynamic, including construction_age_band)
  [key: string]:
    | string
    | number
    | boolean
    | RenovationEvent[]
    | HistoricalPrice[]
    | TenureEvent[]
    | string[]
    | undefined;
}

/** Shared paginated list of `Property` records returned by both
 * `/api/hexagon-properties` and `/api/postcode-properties`. */
export interface PropertyListResponse {
  properties: Property[];
  total: number;
  offset: number;
  truncated: boolean;
}

/** @deprecated Use `PropertyListResponse`. Kept as an alias during the
 * rollout so consumers can migrate without breaking. */
export type HexagonPropertiesResponse = PropertyListResponse;

export interface NumericFeatureStats {
  name: string;
  count: number;
  min: number;
  max: number;
  mean: number;
  histogram?: { min: number; max: number; p1: number; p99: number; counts: number[] };
}

export interface EnumFeatureStats {
  name: string;
  counts: Record<string, number>;
}

export interface PricePoint {
  year: number;
  price: number;
}

export interface CrimeYearPoint {
  year: number;
  count: number;
}

export interface CrimeYearStats {
  /** Crime type without the " (avg/yr)" suffix (e.g. "Burglary"). */
  name: string;
  points: CrimeYearPoint[];
}

export interface FilterExclusion {
  name: string;
  kind: 'numeric' | 'enum' | 'poi' | 'travel';
  direction: 'lower_min' | 'raise_max' | 'allow_value' | 'missing_value';
  value?: number;
  min?: number;
  max?: number;
  category?: string;
  relative_difference: number;
  rejected_count: number;
}

export interface HexagonStatsResponse {
  count: number;
  numeric_features: NumericFeatureStats[];
  enum_features: EnumFeatureStats[];
  price_history?: PricePoint[];
  /** Per-crime-type per-year counts averaged across the selection. */
  crime_by_year?: CrimeYearStats[];
  /**
   * Latest year in the crime dataset as a whole. A selection whose series end
   * earlier sits in a force-level publication gap (e.g. Greater Manchester
   * since mid-2019) and its crime figures are captioned as stale.
   */
  crime_latest_year?: number;
  central_postcode?: string;
  filter_exclusions?: FilterExclusion[];
}
