// Trade-In Estimator — TypeScript types
// Mirrors Pydantic schemas from backend/app/schemas/trade_in.py.

// ── Quota ──
export interface QuotaStatus {
  limit: number;
  used: number;
  remaining: number;
  unlimited: boolean;
}

export type HouseType =
  | "panel"
  | "brick"
  | "monolith"
  | "monolith_brick"
  | "other";

export type RepairState = "needs_repair" | "standard" | "good" | "excellent";

// Runtime-валидаторы enum-значений (#770 finding #30): защищают `as`-cast от
// произвольной строки (битый ?id=-restore / подменённый payload).
export const HOUSE_TYPES: readonly HouseType[] = [
  "panel",
  "brick",
  "monolith",
  "monolith_brick",
  "other",
];
export const REPAIR_STATES: readonly RepairState[] = [
  "needs_repair",
  "standard",
  "good",
  "excellent",
];
export function asHouseType(v: string | null | undefined): HouseType | undefined {
  return v && (HOUSE_TYPES as readonly string[]).includes(v) ? (v as HouseType) : undefined;
}
export function asRepairState(v: string | null | undefined): RepairState | undefined {
  return v && (REPAIR_STATES as readonly string[]).includes(v) ? (v as RepairState) : undefined;
}

export type ConfidenceLevel = "low" | "medium" | "high";

// Точность гео-привязки адреса (из DaData qc_geo): house=0, street=1, approximate≥2.
export type AddressPrecision = "house" | "street" | "approximate";

export interface TradeInEstimateInput {
  address: string; // min 3, max 500
  area_m2: number; // 10 < x < 500
  rooms: number; // 0-10 (0 = студия)
  floor: number | null; // 1-100, optional
  total_floors: number | null; // 1-100, optional
  year_built?: number; // 1800-2100
  house_type?: HouseType;
  repair_state?: RepairState;
  has_balcony?: boolean;
  // CRM-поля (#395)
  ownership_type?: string;
  has_mortgage?: boolean;
  client_name?: string;
  client_phone?: string;
}

export interface AnalogLot {
  address: string;
  area_m2: number;
  rooms: number;
  floor: number | null;
  total_floors: number | null;
  price_rub: number;
  price_per_m2: number;
  listing_date: string | null; // ISO date
  days_on_market: number | null;
  photo_url: string | null;
  // ── Слой 5.2 — clickable links + distance ──
  source: string | null;       // 'avito' / 'cian' / 'domklik' / 'rosreestr'
  source_url: string | null;   // ссылка на оригинал
  distance_m: number | null;   // расстояние до целевой квартиры в метрах
  // ── PR M (#564 Phase 3) — confidence tier для rosreestr deals ──
  // 'T0_per_house' — kadastr_num exact match (currently not available in open dataset)
  // 'T1_per_street' — street-level only (default for all rosreestr deals)
  tier?: string | null;
  // ── ANALYTICS surface — гео-координаты лота для карты (MapCard). Optional +
  //    nullable: старые оценки и лоты без геокодинга их не содержат. Пропускаем
  //    пины с null lat/lon.
  lat?: number | null;
  lon?: number | null;
}

// ── ANALYTICS surface — точка тренда ₽/м² по месяцам (PriceTrendCard) ──
// month — "YYYY-MM"; ppm2 — медиана ₽/м² за месяц по зданию/району.
export interface PriceTrendPoint {
  month: string; // "YYYY-MM"
  ppm2: number;  // ₽/м²
}

export interface CianValuationSummary {
  sale_price_rub: number | null;
  rent_price_rub: number | null;
  chart: Array<{ date: string; price: number }>;
  chart_change_pct: number | null;
  chart_change_direction: 'increase' | 'decrease' | 'neutral' | null;
}

// Базис коэффициента asking→sold (S3): по группе комнат либо городской fallback.
export type RatioBasis = "per_rooms" | "global_fallback";

// ── #651: Avito IMV (Индекс Market Value) якорь для target-дома ──
// Реальная рыночная оценка Avito по дому (house_imv_evaluations) — anchor для
// blend'а медианы. Сурфейсится как референсный маркер. null при отсутствии записи.
export interface AvitoImvSummary {
  recommended_price: number | null; // рекомендованная цена Avito, ₽
  lower_price: number | null;       // нижняя граница IMV-коридора, ₽
  higher_price: number | null;      // верхняя граница IMV-коридора, ₽
  market_count: number | null;      // объём рынка, на котором построена оценка
}

// ── #652: коридор реальных ДКП-сделок Росреестра (advisory, не клампит) ──
// Агрегаты ₽/м² по сопоставимым сделкам за период. null / count=0 если по улице
// нет сделок. count..period_months — required (бэкенд не отдаёт частичный объект).
export interface DkpCorridor {
  count: number;          // число ДКП-сделок в выборке
  low_ppm2: number;       // min ₽/м² по сделкам
  median_ppm2: number;    // медиана ₽/м²
  high_ppm2: number;      // max ₽/м²
  period_months: number;  // окно поиска сделок
}

export interface AggregatedEstimate {
  estimate_id: string; // UUID
  median_price_rub: number; // ASKING-ориентир: по чему выставлены аналоги
  range_low_rub: number;
  range_high_rub: number;
  median_price_per_m2: number;
  // ── Ожидаемая цена продажи (S3) — реалистичная цена сделки по ДКП Росреестра,
  //    обычно ~на 15–20% ниже asking. Все поля optional + nullable: старые оценки
  //    их не содержат, а graceful-degradation отдаёт null при пустой ratio-таблице.
  expected_sold_price_rub?: number | null;
  expected_sold_range_low_rub?: number | null;
  expected_sold_range_high_rub?: number | null;
  expected_sold_per_m2?: number | null;
  asking_to_sold_ratio?: number | null; // sold / asking (≈0.82)
  ratio_basis?: RatioBasis | null;       // 'per_rooms' | 'global_fallback'
  confidence: ConfidenceLevel;
  confidence_explanation: string | null;
  n_analogs: number;
  insufficient_data: boolean; // backend #697: true когда median_price_rub <= 0 (нет данных)
  period_months: number; // 24
  analogs: AnalogLot[]; // top 5-10
  actual_deals: AnalogLot[]; // last 12 mo
  expires_at: string; // ISO datetime
  // ── Метаданные ──
  target_address: string | null;
  target_lat: number | null;
  target_lon: number | null;
  sources_used: string[];                // ['avito', 'cian', 'rosreestr']
  data_freshness_minutes: number | null; // «обновлено N минут назад»
  last_scraped_at?: string | null;       // ISO datetime последнего скрейпа источников (optional)
  est_days_on_market: number | null;     // прогноз срока продажи
  // address_precision — точность гео-привязки адреса (из DaData qc_geo):
  // «house» (qc_geo=0, дом точно), «street» (qc_geo=1, до улицы),
  // «approximate» (qc_geo≥2: населённый пункт/город/регион/не распознан).
  // null если DaData не отрабатывала (адрес не геокодирован) / credentials не заданы.
  address_precision?: AddressPrecision | null;
  // ── Параметры оценённой квартиры (для восстановления карточки по ?id=) ──
  area_m2: number | null;
  rooms: number | null;
  floor: number | null;
  total_floors: number | null;
  year_built: number | null;
  house_type: HouseType | null;
  repair_state: RepairState | null;
  has_balcony: boolean | null;
  // ── Cian valuation surface ──
  cian_valuation?: CianValuationSummary | null;
  // ── #651/#652: внешние якоря ──
  // avito_imv    — реальная Avito IMV-оценка дома, anchor для blend'а медианы.
  // dkp_corridor — коридор ₽/м² по ДКП-сделкам Росреестра (advisory). Оба nullable:
  //                null при отсутствии данных (graceful, common case без регрессий).
  avito_imv?: AvitoImvSummary | null;
  dkp_corridor?: DkpCorridor | null;
  // ── ANALYTICS surface (web-native cards) ──
  // price_trend — динамика медианы ₽/м² по месяцам для здания/района
  //               (PriceTrendCard). null / <2 точек → карточка не рендерится.
  //               last_scraped_at объявлен выше (был дубль #688+#689 → ломал build).
  price_trend?: PriceTrendPoint[] | null;
}

// ── Stage 4a/4b response types ──

export interface HouseInfoForEstimate {
  house_id: number | null;
  ext_house_id: string | null;
  address: string | null;
  short_address: string | null;
  lat: number | null;
  lon: number | null;
  year_built: number | null;
  total_floors: number | null;
  house_type: string | null;
  passenger_elevators: number | null;
  cargo_elevators: number | null;
  has_concierge: boolean | null;
  closed_yard: boolean | null;
  has_playground: boolean | null;
  parking_type: string | null;
  developer_name: string | null;
  rating: number | null;
  reviews_count: number | null;
  raw_characteristics: Array<Record<string, unknown>>;
}

export interface PlacementHistoryItem {
  id: number;
  source: string;
  house_id: number;
  ext_item_id: string;
  title: string | null;
  rooms: number | null;
  area_m2: number | null;
  floor: number | null;
  total_floors: number | null;
  start_price: number | null;
  start_price_date: string | null;
  last_price: number | null;
  last_price_date: string | null;
  removed_date: string | null;
  exposure_days: number | null;
  notes: string | null;
}

export interface IMVBenchmarkResponse {
  available: boolean;
  cache_key: string | null;
  recommended_price: number | null;
  lower_price: number | null;
  higher_price: number | null;
  market_count: number | null;
  fetched_at: string | null;
  our_median_price: number | null;
  diff_pct: number | null;
}

// ── House Analytics (endpoint: GET /estimate/{id}/house-analytics) ──

export interface PriceHistoryYearPoint {
  year: number;
  source: string; // 'avito_imv' | 'yandex_valuation'
  median_price_per_m2: number;
  n_lots: number;
  median_price_rub: number;
}

export interface CianPriceChangeStats {
  cian_id: string;
  listing_id: number;
  n_changes: number;
  last_change_time: string | null;
  last_diff_percent: number | null;
  total_change_pct: number | null;
  first_seen_price: number | null;
  current_price: number;
}

export interface RecentSoldEntry {
  id: number;
  source: string; // 'avito_imv' | 'yandex_valuation'
  rooms: number | null;
  area_m2: number | null;
  floor: number | null;
  start_price: number | null;
  last_price: number | null;
  removed_date: string | null; // ISO date
  exposure_days: number | null;
  discount_pct: number | null; // (start - last) / start * 100
}

export interface HouseAnalyticsKpi {
  total_lots: number;
  sold_count: number;
  sold_rate_pct: number;
  median_exposure_days: number | null;
  median_bargain_pct: number | null;
}

export interface HouseAnalyticsResponse {
  house_ids: number[];
  radius_m: number; // 0 = exact house, 300 = соседи в радиусе
  price_history: PriceHistoryYearPoint[];
  recent_sold: RecentSoldEntry[];
  kpi: HouseAnalyticsKpi;
}

// ── Street deals (endpoint: GET /trade-in/street-deals) ──

export interface StreetDealsResponse {
  street: string | null;          // "Космонавтов" / null если не извлёкся
  period_from: string;            // ISO date "2025-05-29"
  period_to: string;
  count: number;                  // all matching, не топ-10
  median_price_rub: number;
  median_price_per_m2: number;
  range_low_rub: number;
  range_high_rub: number;
  deals: AnalogLot[];             // top-10 по deal_date DESC
}

// ── Sales vs Listings (endpoint: GET /trade-in/sales-vs-listings) ──
// Phase 2 PR L (#564): для каждой ДКП-сделки возвращает paired listing (если есть).
// LEFT JOIN — все deals сохраняются; listing_* = null при отсутствии match.

export interface SalesListingPair {
  deal_id: number;
  deal_date: string;              // ISO date
  deal_price_rub: number;
  deal_price_per_m2: number;
  deal_area_m2: number;
  deal_rooms: number;
  deal_floor: number | null;
  deal_address: string;

  listing_id: number | null;
  listing_source: string | null;       // 'avito' / 'cian' / 'yandex' / 'n1' / 'domklik'
  listing_source_url: string | null;
  listing_date: string | null;         // ISO date
  listing_price_rub: number | null;
  listing_price_per_m2: number | null;
  listing_area_m2: number | null;

  // Положительный = listing date раньше сделки (типичный кейс).
  // Отрицательный = listing появился позже сделки (отложенный парсинг).
  days_listing_to_deal: number | null;
  // discount_pct = (deal_price - listing_price) / listing_price * 100.
  // Отрицательный = продали дешевле выставленного (нормальный торг).
  discount_pct: number | null;
}

export interface SalesVsListingsResponse {
  street: string | null;
  period_months: number;          // 24 default
  window_days: number;            // 180 default
  area_tolerance: number;         // 0.15 default
  total_deals: number;
  deals_with_listings: number;
  linkage_rate_pct: number;
  median_discount_pct: number | null;
  // Качество данных: house_linked = есть пары ДКП↔listing; street_only = есть
  // сделки, но привязка к конкретному дому/объявлению невозможна; no_data = нет сделок.
  data_quality: "house_linked" | "street_only" | "no_data";
  pairs: SalesListingPair[];      // все sorted by deal_date DESC
}

// ── Sell-time sensitivity (endpoint: GET /estimate/{id}/sell-time-sensitivity) ──

export interface SellTimeBucket {
  price_premium_label: string; // 'cheap' | 'median' | 'plus5' | 'plus10'
  price_premium_pct: number;   // -5, 0, 5, 10
  median_exposure_days: number | null;
  p25_days: number | null;
  p75_days: number | null;
  n_lots: number;
}

export interface SellTimeSensitivityResponse {
  house_ids: number[];
  radius_m: number;
  target_median_price_per_m2: number | null;
  buckets: SellTimeBucket[]; // ровно 4 элемента: cheap, median, plus5, plus10
}
