/**
 * Shared helpers for Section 6 (forecast) blocks — deficit semantics, confidence
 * mapping, RU number formatting. Kept framework-agnostic (no JSX) so the three
 * forecast blocks share one source of truth.
 */

import type { BadgeVariant } from "@/components/ui/Badge";
import type { ConfidenceLevel, FutureSupply } from "@/types/forecast";

/** Below this |deficit_index| we treat the market as balanced (neutral). */
export const DEFICIT_BALANCE_EPS = 0.05;

/**
 * Deficit semantics (HARD): >0 недонасыщенность (повод строить, success);
 * <0 затоварка (warn/danger); ≈0 баланс (neutral).
 */
export function deficitVariant(deficitIndex: number): BadgeVariant {
  if (deficitIndex > DEFICIT_BALANCE_EPS) return "success";
  if (deficitIndex < -DEFICIT_BALANCE_EPS) return "danger";
  return "neutral";
}

export function deficitWord(deficitIndex: number): string {
  if (deficitIndex > DEFICIT_BALANCE_EPS) return "недонасыщенность";
  if (deficitIndex < -DEFICIT_BALANCE_EPS) return "затоварка";
  return "баланс";
}

/**
 * Bar width (%) for a signed deficit_index (∈ −1..1) on a 0..100 scale. Uses the
 * magnitude so both недонасыщенность (+) and затоварка (−) read as a filled bar
 * (colour carries the sign via `deficitVariant`). Clamped to [0, 100].
 */
export function deficitBarWidthPct(deficitIndex: number): number {
  const pct = Math.abs(deficitIndex) * 100;
  return Math.max(0, Math.min(100, pct));
}

// ── Confidence ────────────────────────────────────────────────────────────────

export const CONFIDENCE_RU: Record<ConfidenceLevel, string> = {
  high: "высокая",
  medium: "средняя",
  low: "низкая",
};

export function confidenceVariant(level: ConfidenceLevel): BadgeVariant {
  if (level === "high") return "success";
  if (level === "medium") return "warning";
  return "danger";
}

/**
 * ConfidenceBadge expects a numeric 0..1 value but the report carries a level
 * only. Derive a representative midpoint so the numeric badge reads sensibly
 * if/when reused.
 */
export function confidenceLevelToValue(level: ConfidenceLevel): number {
  if (level === "high") return 0.85;
  if (level === "medium") return 0.55;
  return 0.25;
}

// ── Scoring (§13.6 — product scores #985 + special indices #986) ───────────────

/**
 * Product-score semantics (HARD): every §14.2 score is ∈ [0,1] with «выше =
 * лучше для девелопера» (risk scores supply_risk / future_competition /
 * mortgage_sensitivity are pre-inverted at the source — so score>0.5 =
 * favourable). The backend anchors quality on 0.5 = баланс/midpoint
 * (`product_scoring.py:127` `_MARKET_FIT_MIDPOINT = 0.5`; inverted-risk reasons
 * flip favourable/unfavourable at risk<0.5 → score>0.5), so the gradient is
 * anchored on 0.5 too: a narrow ±0.05 neutral band straddles the balance point —
 * ≥0.55 success / 0.45–0.55 warning / <0.45 danger. This is NOT the deficit
 * mapping (signed [−1,1]) — do not reuse deficitVariant here.
 */
export const SCORE_GOOD_THRESHOLD = 0.55;
export const SCORE_WEAK_THRESHOLD = 0.45;

export function scoreVariant(value: number): BadgeVariant {
  if (value >= SCORE_GOOD_THRESHOLD) return "success";
  if (value >= SCORE_WEAK_THRESHOLD) return "warning";
  return "danger";
}

/**
 * Bar width (%) for a 0..1 score on a 0..100 scale (direct, no magnitude trick —
 * the score is already unsigned 0..1). Clamped to [0, 100].
 */
export function scoreBarWidthPct(value: number): number {
  return Math.max(0, Math.min(100, value * 100));
}

/**
 * RU labels for the 10 §14.2 product-score keys (#985). The MD/PDF exporters
 * render the raw English key — these are the established in-app RU names, kept
 * here so the block and any future reuse share one source of truth. Unknown
 * keys fall back to the raw key (graceful).
 */
export const PRODUCT_SCORE_RU: Record<string, string> = {
  market_fit: "Соответствие рынку",
  demand: "Спрос",
  supply_risk: "Риск избытка предложения",
  future_competition: "Будущая конкуренция",
  price_feasibility: "Доступность цены",
  infra_fit: "Инфраструктура",
  mortgage_sensitivity: "Чувствительность к ставке",
  differentiation: "Дифференциация",
  commercial: "Коммерция",
  confidence: "Надёжность данных",
};

/**
 * RU labels for the 6 §25 special-index keys (#986). The per-entry `label` field
 * is a value-descriptor («6 мес» / «Комфорт»), not a metric name, so it is shown
 * as supplementary context — these are the metric names. Unknown keys fall back
 * to the raw key (graceful).
 */
export const SPECIAL_INDEX_RU: Record<string, string> = {
  launch_window: "Окно запуска",
  product_void: "Белые пятна продукта",
  cannibalization: "Каннибализация",
  competitor_strength: "Сила конкурентов",
  artificial_demand: "Искусственный спрос",
  cost_of_error: "Цена ошибки",
};

// ── Future supply (§9.3 — 6.6 evidence panel) ──────────────────────────────────

/**
 * Does the §9.3 future-supply payload carry a GENUINELY-COMPUTED pressure signal
 * worth rendering the supply panel for? Single source of truth — gates both the
 * 6.6 block's panel and Section6Forecast's 6.6 sub-block (so they can't drift).
 *
 * Gates on the `_round_or_none` metrics ONLY (index / months_of_pressure /
 * future_units_by_horizon / monthly_absorption_units) — DELIBERATELY excludes
 * `open_units` / `hidden_units`. Those are integer stocks that are 0-NOT-NULL at
 * the source: when `supply_layers` is empty (e.g. before the Monday 06:00 worker
 * loads it) the SQL returns 0 rows → open/hidden = 0 while every computed metric
 * is null. Triggering on open/hidden would render «Открытый сток: 0 ед. / Скрытый
 * запас: 0 ед.», falsely reading as "zero future supply = safe" when the truth is
 * "supply data not loaded yet" (null ≠ 0). Gating on the computed metrics keeps
 * the panel OFF until a real pressure index exists; open/hidden are still DISPLAYED
 * once it does (a genuine 0 then is honest).
 */
export function hasSupplySignal(fs: FutureSupply | null): boolean {
  if (fs == null) return false;
  const b = fs.breakdown;
  return (
    fs.index != null ||
    b.months_of_pressure != null ||
    b.future_units_by_horizon != null ||
    b.monthly_absorption_units != null
  );
}

// ── Number formatting ─────────────────────────────────────────────────────────

/**
 * Fixed-decimal RU number (тонкая неразрывная группировка тысяч). Нормализует
 * ведущий знак минуса в Unicode «−» (U+2212) per ui-microcopy (≥ ≤ ± Unicode).
 * `.replace` бьёт только первое вхождение = ведущий знак (RU-группировка — NBSP,
 * не дефис), так что числовая часть не затрагивается.
 */
export function fmtNum(value: number, digits = 1): string {
  return value
    .toLocaleString("ru", {
      minimumFractionDigits: digits,
      maximumFractionDigits: digits,
    })
    .replace("-", "−");
}
