import type { Geometry } from "geojson";

export interface ParcelAnalysisNoiseSource {
  source_type: "highway" | "railway" | "industrial" | "aerodrome";
  road_class: string | null;
  name: string | null;
  distance_m: number;
  estimated_db: number;
}

export interface ParcelAnalysisNoise {
  score: number;
  estimated_db: number;
  level: "тихо" | "умеренный" | "шумно";
  sources: ParcelAnalysisNoiseSource[];
}

export interface ParcelAnalysisAirQuality {
  pm2_5: number;
  pm10: number;
  no2: number;
  ts: string;
  source: string;
}

export interface ParcelAnalysisWind {
  dominant_direction_deg: number;
  dominant_direction_label: string;
  max_speed_m_s: number | null;
  forecast_days: number;
  source: string;
}

export interface ParcelAnalysisCompetitor {
  obj_id: number;
  comm_name: string | null;
  dev_name: string | null;
  obj_class: string | null;
  flat_count: number | null;
  district_name: string | null;
  distance_m: number;
  // Added manually — backend PR #276 (site_status in /analyze).
  // Codegen will overwrite after backend deploy (additive, backward compat).
  site_status?: string | null; // 'Строящиеся' | 'Сданные' | null
  ready_dt?: string | null; // ISO date
  // #999 (958-B4) — координаты для Leaflet-слоя «Конкуренты» (EPSG:4326).
  // Backend `_competitor_with_coords` (parcels.py) кладёт lat/lon|null. Optional
  // т.к. codegen / тонкий 202-стаб могут их не содержать; слой пропускает null.
  lat?: number | null;
  lon?: number | null;
  // Рыночные метрики из objective_lots (LEFT JOIN → null без маппинга).
  // Используются в popup-слое (цена/распроданность как velocity-прокси).
  // NUMERIC-поля приходят СТРОКОЙ (Pydantic v2 сериализует Decimal в строку),
  // поэтому number | string — приводить к числу через toFiniteNumber перед форматом.
  avg_price_per_m2_rub?: number | string | null;
  units_sold?: number | null;
  units_available?: number | null;
  // #2111 — средняя площадь квартиры (м²) из objective_lots. Backend кладёт через
  // loose competitors[] dict-passthrough (Decimal → строка). Optional + nullable.
  avg_area_pd?: number | string | null;
}

export interface ParcelAnalysisDistrict {
  district_name: string;
  /** #1960: newbuild-consistent медиана цены района (Objective/DOM.РФ basis).
   * null, если ни один basis не разрешился. См. median_price_basis. */
  median_price_per_m2: number | null;
  /** #1960: какой источник дал median_price_per_m2 —
   * objective_district | objective_geo_radius | ekb_districts_reference |
   * quarter_rosreestr | none. */
  median_price_basis?: string;
  dist_to_center: number;
}

export interface ParcelAnalysisPoi {
  name: string | null;
  distance_m: number;
  last_edit: string | null;
  lat: number;
  lon: number;
  // «Максимум информации» по точке из OSM tags (backend `_poi_detail_from_tags`).
  // Все опциональны — присутствуют только когда тег есть в OSM.
  address?: string | null;
  operator?: string | null;
  opening_hours?: string | null;
  website?: string | null;
  phone?: string | null;
}

export interface ParcelAnalysisWeather {
  forecast_days: number;
  temperature: {
    min_c: number | null;
    max_c: number | null;
    avg_max_c: number | null;
    avg_min_c: number | null;
  };
  precipitation_total_mm: number;
  precipitation_days: number;
  uv_index_max: number | null;
  wind: ParcelAnalysisWind;
  source: string;
  note: string;
}

export interface ParcelAnalysisGeology {
  data_available: boolean;
  note: string;
  links: {
    karpinsky_webgis: string;
    efgi_federal_registry: string;
  };
  lat: number;
  lon: number;
}

export interface SeasonStats {
  avg_t_max_c: number;
  avg_t_min_c: number;
  max_t_c: number;
  min_t_c: number;
  avg_precip_per_day_mm: number;
  total_precip_mm: number;
  days_observed: number;
}

export interface SeasonalWeather {
  seasons: {
    winter: SeasonStats | null;
    spring: SeasonStats | null;
    summer: SeasonStats | null;
    autumn: SeasonStats | null;
  };
  period: string;
  model: string;
  source: string;
  note: string;
}

export interface HydrologyNearest {
  subtype: "river" | "stream" | "lake_or_pond" | "canal" | null;
  name: string | null;
  distance_m: number;
}

export interface Hydrology {
  nearest: HydrologyNearest[];
  flood_risk_flag: boolean;
  note: string;
}

export interface GeotechRisk {
  seismic_intensity_balls: number | null;
  seismic_label: string;
  seismic_description: string;
  permafrost: boolean;
  industrial_within_500m: number;
  industrial_contamination_flag: boolean;
  note: string;
}

// P2 (#46) — cad_buildings соседи + overlap
export interface NeighborBuilding {
  cad_num: string;
  building_name: string | null;
  floors: string | null;
  floors_parsed: number | null;
  year_built: number | null;
  area_m2: number | null;
  cost_per_m2: number | null;
  distance_m: number;
  readable_address: string | null;
  // #2111 — cad_buildings.purpose / status через loose neighbors_summary
  // passthrough. Optional + nullable: старый кэш / тонкий стаб могут не содержать;
  // рендерим только при наличии.
  purpose?: string | null;
  status?: string | null;
}

export interface OverlapBuilding {
  cad_num: string;
  building_name: string | null;
  floors: string | null;
  readable_address: string | null;
  overlap_m2: number | null;
}

export interface NeighborsSummary {
  data_available: boolean;
  radius_m?: number;
  count_buildings_100m?: number;
  avg_floors_100m?: number | null;
  max_floors_100m?: number | null;
  median_cost_per_m2_100m?: number | null;
  neighbors?: NeighborBuilding[];
  has_existing_buildings?: boolean;
  overlap_buildings?: OverlapBuilding[];
  note?: string;
}

// P2 (#1871) — staleness/coverage статус источника сделок.
// "ok" — тренд посчитан; "source_stale" — источник сделок устарел (тренд не
// рисуем); "no_deals" — нет сделок в радиусе за период;
// "offer_only" (#2178) — сделки устарели/отсутствуют, но есть живая динамика
// цен предложения (Объектив) → она становится основным содержимым карточки.
export type MarketTrendStatus =
  "ok" | "source_stale" | "no_deals" | "offer_only";

// #2178 — одна точка снапшота динамики цен предложения (Объектив).
export interface OfferTrendPoint {
  date: string; // YYYY-MM-DD (дата weekly-снапшота)
  median_price_m2: number; // медианная цена предложения, ₽/м²
  lots_count: number; // сколько активных лотов в снапшоте
}

// #2178 — динамика цен предложения (Объектив): саб-блок при status "ok" и
// основной контент при status "offer_only". Форма зеркалит OfferPriceTrend
// .as_dict() + метаданные offer_block (source/label/radius_km) из хендлера.
export interface OfferTrend {
  source: string; // «Объектив, цены предложения»
  label: string; // человекочитаемый вердикт по динамике
  radius_km: number;
  points: OfferTrendPoint[];
  delta_pct: number | null; // (последняя медиана − первая) / первая × 100
  lots_latest: number | null; // lots_count последнего снапшота
  complexes_covered: number; // сколько ЖК попало в радиус-выборку
}

export interface MarketTrend {
  // P2 (#1871): обязателен в новых ответах; optional для backward-compat со
  // старыми/кэшированными ответами без поля (трактуем отсутствие как "ok").
  status?: MarketTrendStatus;
  // YYYY-MM-DD — дата последней доступной сделки источника (для caption staleness).
  // При status "offer_only" — дата последнего снапшота предложения.
  as_of_date?: string;
  recent_deals_count: number;
  // Поля ниже присутствуют только при status === "ok" (старый контракт).
  recent_avg_price_per_m2?: number;
  prior_avg_price_per_m2?: number;
  delta_6m_pct?: number;
  prior_deals_count?: number;
  label?: string;
  radius_km?: number;
  // #2178 — динамика цен предложения. При status "ok" — дополнительный саб-блок
  // под сделко-трендом; при "offer_only" — основной контент карточки.
  offer_trend?: OfferTrend | null;
  // #2178 (только status "offer_only"): устарел ли источник сделок (true) или
  // сделок просто нет рядом при живом источнике (false).
  deals_stale?: boolean;
  // #2178 (только status "offer_only"): YYYY-MM-DD последней сделки источника.
  deals_as_of_date?: string | null;
}

// Market pulse — district absorption + competitor counts (/analyze market layer).
export interface MarketPulseTopSeller {
  obj_id: number;
  comm_name: string | null;
  dev_name: string | null;
  units_sold: number | null;
  avg_price_per_m2_rub: number | null;
}

export interface MarketPulse {
  avg_velocity_m2: number; // средний темп поглощения, м²/мес
  market_avg_price_per_m2: number;
  competitors_total: number; // всего конкурентов в выборке
  competitors_with_price: number;
  coverage_pct: number;
  top_sellers?: MarketPulseTopSeller[];
}

// Market price distribution (district deals) — used for the median row.
export interface MarketPrice {
  p25: number | null;
  median: number | null;
  p75: number | null;
  mean: number | null;
  deals_count: number | null;
  median_6m: number | null;
  median_12m: number | null;
  median_24m: number | null;
  last_deal_date: string | null;
  source: string;
}

// D4 (#36) — 24-month project pipeline competition
export interface PipelineQuarterSlot {
  quarter: string;
  objects: number;
  flats: number;
}

export interface PipelineObject {
  obj_id: number;
  comm_name: string | null;
  dev_name: string | null;
  obj_class: string | null;
  flat_count: number | null;
  ready_dt: string | null;
  // backend имеет explicit `is not None` guard для distance_m (centroid-on-
  // building даёт 0.0 которое falsy, raw Decimal иначе упал бы в JSON), так
  // что nullable отражает defensive Python path.
  distance_m: number | null;
  // #999 (958-B4) — координаты для Leaflet-слоя «Будущие проекты» (EPSG:4326).
  // backend `_aggregate_pipeline.top_objects` кладёт lat/lon|null; слой
  // пропускает объекты без координат.
  lat?: number | null;
  lon?: number | null;
}

export interface Pipeline24mo {
  objects_count: number;
  flats_total: number;
  by_class: Record<string, number>;
  by_quarter: PipelineQuarterSlot[];
  severity: "none" | "low" | "medium" | "high";
  severity_label?: string;
  top_objects: PipelineObject[];
  radius_km?: number;
  horizon_months?: number;
  note?: string;
}

// D2 (#34) — velocity-score: темп продаж конкурентов
export interface VelocityBucketStat {
  units: number;
  sqm: number;
  complexes_count: number;
}

export interface VelocityCompetitor {
  obj_id: number;
  name: string | null;
  obj_class: string | null;
  distance_m: number;
  total_sqm_period: number;
  dev_name?: string | null;
  district_name?: string | null;
  by_room_bucket?: Record<string, number>;
}

export interface VelocityPeriod {
  start: string; // YYYY-MM
  end: string;
}

export interface Velocity {
  competitors_count: number;
  monthly_velocity_sqm: number;
  ekb_median_sqm: number;
  velocity_score: number; // 0..1
  confidence: "high" | "medium" | "low";
  months_observed: number;
  period: VelocityPeriod;
  sample_competitors: VelocityCompetitor[];
  by_room_bucket?: Record<string, VelocityBucketStat>;
  // True если ≥1 конкурент имеет маппинг в objective_complex_mapping.
  // False → конкуренты найдены, velocity=0, данных Objective нет.
  velocity_data_available?: boolean;
  // SF#17: источник данных velocity.
  // 'objective' — Objective (основной), 'rosreestr_fallback' — кадастровый квартал,
  // 'none' — нет данных.
  velocity_source?: "objective" | "rosreestr_fallback" | "none";
  // P2 (#1871) — покрытие/прокси (optional для backward-compat).
  // objective_coverage_pct — доля объёма из реальных данных Objective (0..100).
  objective_coverage_pct?: number;
  // proxy_used — объём оценён по прокси (м²/сделка) вместо прямого замера.
  proxy_used?: boolean;
  // proxy_sqm_per_deal — коэффициент прокси (м² на сделку), null если прокси не применён.
  proxy_sqm_per_deal?: number | null;
}

// G5 (#32) — Gate verdict: can_build_mkd
export type GateVerdictSource = "nspd_dump" | "nspd_dump_partial" | "no_data";

export type GateVerdictLabel =
  "Можно" | "Нельзя" | "С ограничениями" | "Нужна проверка";

export interface GateBlocker {
  code: string;
  detail: string;
}

export interface GateWarning {
  code: string;
  detail: string;
}

export interface GateVerdict {
  can_build_mkd: boolean | "unknown";
  verdict_label: GateVerdictLabel;
  blockers: GateBlocker[];
  warnings: GateWarning[];
  checks_performed: string[];
  source: GateVerdictSource;
}

export interface ParcelLocation {
  distance_to_center_km: number;
  center_bonus: number;
  ekb_center: { lat: number; lon: number };
  note: string;
}

// P1 (#45) — physical suitability участка.
// label — base value один из BaseLabel'ов либо combo "микро, <first-penalty>"
export type GeometrySuitabilityBaseLabel =
  "микро" | "подходящий" | "сложная форма" | "слабо подходит";

export interface GeometrySuitability {
  data_available: boolean;
  area_ha?: number;
  area_m2?: number;
  perimeter_m?: number;
  aspect_ratio?: number;
  convex_hull_ratio?: number;
  min_inscribed_rect_dim_m?: number;
  suitability_score?: number;
  // string — допускаем combo-label "микро, узкий"; см. GeometrySuitabilityBaseLabel
  label?: string;
  penalties?: string[];
  recommendation?: string;
  note: string;
}

export interface SuccessRankingBucket {
  bucket: string;
  /** #1955: класс ЖК ('Комфорт' / 'Типовой' / 'Бизнес' / 'не указан' / …) —
   * различает строки с одинаковым area-label, убирает визуальные дубли бакетов. */
  obj_class: string;
  success_score: number;
  n_deals: number;
  avg_price_per_m2: number | null;
  avg_area_m2: number | null;
}

export interface ParcelSuccessRecommendation {
  district: string;
  /** SF-20: strong = ≥30 сделок в группе; weak = 15-29 сделок (ориентировочно) */
  data_confidence: "strong" | "weak";
  ranking: SuccessRankingBucket[];
  top_bucket: SuccessRankingBucket;
  note: string;
}

// X1 (#47) — per-factor breakdown с verbal explain
export interface FactorContribution {
  factor: string;
  category: string;
  category_ru: string;
  group: string;
  value: number;
  weight: number;
  contribution: number;
  contribution_pct: number;
  verbal: string;
  lat: number | null;
  lon: number | null;
}

export interface ScoreGroupTotal {
  group: string;
  contribution: number;
  count: number;
  contribution_pct: number;
}

export interface ParcelAnalysis {
  cad_num: string;
  source: "cad_quarter" | "cad_building";
  geom_geojson: Geometry | null;
  district: ParcelAnalysisDistrict | null;
  score: number;
  score_label?: "плохо" | "средне" | "хорошо" | "отлично";
  score_max_reference?: number;
  score_explanation?: string;
  market_trend?: MarketTrend | null;
  // Абсорбция + число конкурентов из рыночного слоя /analyze.
  market_pulse?: MarketPulse | null;
  // Распределение цен сделок района (median и т.п.).
  market_price?: MarketPrice | null;
  score_breakdown: Record<string, ParcelAnalysisPoi[]>;
  score_breakdown_detailed?: FactorContribution[];
  score_top_3_positives?: FactorContribution[];
  score_top_3_negatives?: FactorContribution[];
  score_by_group?: ScoreGroupTotal[];
  poi_count: number;
  competitors: ParcelAnalysisCompetitor[];
  noise: ParcelAnalysisNoise | null;
  air_quality: ParcelAnalysisAirQuality | null;
  wind: ParcelAnalysisWind | null;
  weather?: ParcelAnalysisWeather | null;
  seasonal_weather?: SeasonalWeather | null;
  hydrology?: Hydrology;
  geotech_risk?: GeotechRisk;
  geology?: ParcelAnalysisGeology;
  isochrones_available?: boolean;
  score_without_center?: number;
  location?: ParcelLocation;
  success_recommendation?: ParcelSuccessRecommendation | null;
  // P1 (#45) — physical suitability участка
  geometry_suitability?: GeometrySuitability;
  // P2 (#46) — cad_buildings соседи + overlap check
  neighbors_summary?: NeighborsSummary;
  // X2 (#48) — confidence indicator
  confidence?: number;
  confidence_label?: "high" | "medium" | "low";
  confidence_breakdown?: Record<string, number>;
  confidence_caveats?: string[];
  // D4 (#36) — 24-month project pipeline competition
  pipeline_24mo?: Pipeline24mo;
  // G5 (#32) — gate verdict: МКД buildability
  gate_verdict?: GateVerdict;
  // D2 (#34) — velocity-score: темп продаж конкурентов
  velocity?: Velocity | null;
  // Issue #202 — NSPD frontend integration
  nspd_zoning?: import("./nspd").NspdZoning | null;
  nspd_zouit_overlaps?: import("./nspd").NspdZouitOverlap[];
  nspd_engineering_nearby?: import("./nspd").NspdEngineeringNearby[];
  nspd_risk_zones?: import("./nspd").RiskZone[];
  // Issue #94 PR2 — TIER 4 opportunity layers + red lines
  nspd_opportunity_parcels?: import("./nspd").OpportunityParcel[];
  nspd_red_lines?: import("./nspd").RedLine[];
  nspd_dump?: import("./nspd").NspdDumpMeta | null;
  // B5 extended (added 2026-05-18 — expose в legacy Обзор)
  egrn?: {
    address?: string | null;
    permitted_use_text?: string | null;
    land_category?: string | null;
    parcel_status?: string | null;
    right_type?: string | null;
    cadastral_value_rub?: number | null;
    cost_per_m2_rub?: number | null;
    last_egrn_update_date?: string | null;
  } | null;
  utilities?: {
    summary?: Array<{
      subtype: string;
      nearest_m: number;
      name?: string | null;
      count_within_2km?: number;
    }>;
    power_line_охранная_зона_flag?: boolean;
    note?: string | null;
  } | null;
  // Эпик #1881 PR-4 — финмодель из /analyze (extra=allow на backend, hand-typed
  // здесь). null когда нельзя строить МКД / нет зонинга / нет площади.
  financial_estimate?: ParcelFinancialEstimate | null;
  // #2181 — топ-N вариантов программы (класс × этажность) по NPV. Backend кладёт
  // это поле ТОЛЬКО при отрицательном фин-вердикте (program_optimizer.py);
  // на позитивных участках null / отсутствует. Hand-typed (extra=allow на backend).
  program_alternatives?: ProgramAlternatives | null;
  // #2367 — разрешения на строительство (RS) и вводы в эксплуатацию (RV) в
  // радиусе 500 м по реестру ГИСОГД. Backend держит блок как dict[str, Any] |
  // None, поэтому здесь — generic-объект; строгую форму narrow'ит
  // PermitsNearbyBlock в рантайме. Может отсутствовать/быть null (старый кэш).
  permits_nearby?: Record<string, unknown> | null;
}

/**
 * Один вариант программы застройки (класс × этажность), посчитанный
 * program_optimizer.optimize_program (#2181) через ту же compute_financial, что
 * и основной financial_estimate. Класс/тип застройки приходят строкой (backend
 * extra=allow) — на UI сужаются к HOUSING_CLASS_LABELS / DEVELOPMENT_TYPE_LABELS.
 */
export interface ProgramVariant {
  // "econom" | "comfort" | "business" (Literal HousingClass на backend).
  housing_class: string;
  floors: number;
  // "spot" | "mid_rise" | "high_rise" (по этажности комбо).
  development_type: string;
  // NPV дисконтированного cashflow, ₽ (ключ ранжирования; может быть отрицательным).
  npv_rub: number;
  irr: number;
  roi: number;
  net_profit_rub: number;
  residential_sqm: number;
  apartments_count: number;
  // Цена продажи жилья, ₽/м², фактически использованная в выручке комбо.
  price_per_sqm_used: number;
  // "objective_district_median" | "district_reference" | "class_norm" — тот же
  // honest-набор источников, что у financial_estimate.price_source.
  price_source: string;
  // true — график фаз/продаж типовой (допущение), не график проекта.
  schedule_is_default: boolean;
}

/**
 * Программа варианта, ПЕРЕДАВАЕМАЯ из блока «Как участок сходится» (#2181) в §7
 * «Концепция» по клику «Пересчитать §7 с этой программой» (план #2181, хвост).
 * Узкий срез ProgramVariant: только класс/этажность/тип застройки — §7 сам
 * подбирает тип секции и пятно из каталога по housing_class (как pickDefaultType).
 * housing_class / development_type — строки backend'а; §7 нарроуит их к литералам
 * (asHousingClass / asDevelopmentType) с fallback comfort / mid_rise.
 */
export interface PendingConceptProgram {
  housing_class: string;
  floors: number;
  development_type: string;
}

/**
 * Блок «Как участок сходится» (#2181) — ранжир вариантов программы. Присутствует
 * только при отрицательном базовом фин-вердикте; иначе null / отсутствует.
 */
export interface ProgramAlternatives {
  // Топ-N вариантов по NPV убыв. (тай-брейк ROI). При all-negative — «наименее убыточные».
  variants: ProgramVariant[];
  // true — хотя бы один вариант с NPV > 0 (участок сходится при другой программе).
  any_viable: boolean;
  // Сколько комбо (класс × этажность) реально посчиталось в переборе.
  grid_size: number;
  // HEAVY caveat — тот же, что у основной финмодели (оценка по МАКС. застройке зоны).
  caveat: string;
}

/**
 * Финансовая оценка участка (#1881). Считается по МАКСИМАЛЬНОЙ застройке НСПД —
 * это ориентир, а НЕ реальная концепция. Все суммы в ₽ (rub), ставки/маржа — доли
 * (0..1), payback_months — null когда проект не окупается в горизонте модели.
 */
export interface ParcelFinancialEstimate {
  revenue_rub: number;
  cost_rub: number;
  net_profit_rub: number;
  roi: number;
  margin_pct: number;
  irr: number;
  irr_is_proxy: boolean;
  // Нежилое (коммерция/офисы 1-го этажа) — additive выручка (СМР уже в construction).
  // 0 когда нежилое не моделировалось (econom/spot share 0 либо старый кэш).
  revenue_office_rub: number;
  npv_rub: number;
  payback_months: number | null;
  discount_rate_used: number;
  price_per_sqm_used: number;
  price_is_calibrated: boolean;
  price_source: string;
  housing_class_inferred: string;
  development_type_inferred: string;
  schedule_is_default: boolean;
  // RANK 1 — реализованное окно продаж (мес), драйвится рыночной абсорбцией
  // района когда schedule_is_default=false; иначе дефолт-норматив. null — окно
  // неизвестно / финмодель не строилась.
  sales_duration_months: number | null;
  teap_synth: {
    residential_area_sqm: number;
    total_floor_area_sqm: number;
    // Нежилое (коммерция/офисы 1-го этажа), кв.м. Вырезано из GFA до расчёта жилой
    // (total_floor_area не меняется). 0 когда нежилое не моделировалось. Optional:
    // живой backend всегда отдаёт, но dev-мок parcel-analyze.json (на main) его не
    // содержит — required форсировал бы правку мока, конфликтную между ветками.
    office_area_sqm?: number;
    parking_spaces: number;
    apartments_count: number;
  };
  caveat: string;
  // #1881 PR-5 — финансирование (кредит) в финмодели. financing_enabled=false →
  // финансирование не моделировалось (UI не показывает строки кредита).
  // annual_rate_used — годовая ставка кредита (доля 0..1). peak_debt_rub —
  // пиковый долг по телу кредита, ₽. total_interest_rub — суммарные проценты, ₽.
  // net_profit_after_financing_rub — чистая прибыль за вычетом процентов, ₽.
  // financing_is_simplified=true → упрощённая модель (100% покрытие кассового
  // разрыва, проценты капитализируются, эскроу не моделируется точно) — caveat.
  financing_enabled: boolean;
  annual_rate_used: number;
  peak_debt_rub: number;
  total_interest_rub: number;
  net_profit_after_financing_rub: number;
  financing_is_simplified: boolean;
  // Levered (equity) IRR — доходность на собственные средства (equity cashflow при
  // LTC). При положительном леверидже > unlevered irr. levered_irr_is_proxy=true →
  // roi-proxy (вырожденный equity-поток / equity не под риском при LTC>=1), не DCF.
  levered_irr: number;
  levered_irr_is_proxy: boolean;
}

export type PoiCategory =
  | "school"
  | "kindergarten"
  | "pharmacy"
  | "hospital"
  | "shop_mall"
  | "shop_supermarket"
  | "shop_small"
  | "park"
  | "tram_stop"
  | "bus_stop"
  | "metro_stop";
