"use client";

/**
 * RecommendedProductCard — lower-grid «Recommended Product» card. Renders the
 * recommended класс (tag) + квартирография mix bars from the §22
 * `forecastReport.product_tz`, reusing the exact bar logic of the Scenarios-tab
 * `RecommendedProduct` (доля when an explicit `pct` mix exists, else
 * |deficit_index| as signal strength). While the forecast is pending → the
 * honest «после прогноза» placeholder. "Подробнее" opens the product drawer.
 */

import { useMemo, useState } from "react";

import styles from "@/app/site-finder/analysis/[cad]/ptica/ptica.module.css";
import type {
  ForecastReport,
  ProductMixEntry,
  ReportProductTz,
} from "@/types/forecast";
import {
  DEFICIT_BALANCE_EPS,
  deficitTone,
  deficitWord,
  fmtNum,
} from "@/components/site-finder/ptica/scenarios/scenario-helpers";
import type { DrawerKey } from "@/components/site-finder/ptica/drawers/drawer-keys";

/** Cap meaningful bars so long mixes stay readable (one row per format). */
const MAX_BARS = 6;
/** |deficit_index| at/above this is the degenerate rail value (no signal). */
const DEFICIT_RAIL_EPS = 0.999;

interface Props {
  /** §22 forecast report — quartirografiya comes from `product_tz`. */
  forecastReport?: ForecastReport;
  onOpenDrawer: (key: DrawerKey) => void;
}

const TONE_CLASS = {
  good: styles.hbarFillGood,
  bad: styles.hbarFillBad,
  warn: styles.hbarFillBad,
  neutral: styles.hbarFillNeutral,
} as const;

interface MixBar {
  key: string;
  label: string;
  widthPct: number;
  valueStr: string;
  tone: "good" | "warn" | "bad" | "neutral";
}

/** Distinct obj_class values present in the mix, in first-seen order. */
function classesInMix(tz: ReportProductTz): string[] {
  const seen: string[] = [];
  for (const m of tz.mix) {
    if (m.obj_class != null && !seen.includes(m.obj_class)) {
      seen.push(m.obj_class);
    }
  }
  return seen;
}

function buildBars(
  tz: ReportProductTz,
  selectedClass: string | null,
): {
  bars: MixBar[];
  byShare: boolean;
} {
  // §22 mix can carry buckets across MULTIPLE obj_class values (e.g. 3 classes ×
  // 5 buckets = 15 rows). Keep ONLY the selected class so the card shows the
  // single honest квартирография (≈5 rows); if no class is set, keep all.
  const classMix: ProductMixEntry[] =
    selectedClass != null
      ? tz.mix.filter((m) => m.obj_class === selectedClass)
      : tz.mix;
  const rows = classMix.filter(
    (m) => m.pct != null || m.deficit_index != null,
  );
  const byShare = rows.some((m) => m.pct != null);
  const bars = rows.map((m, i): MixBar => {
    if (m.pct != null) {
      const pct = Math.max(0, Math.min(100, m.pct));
      return {
        key: `${m.bucket ?? "?"}-${m.obj_class ?? "?"}-${i}`,
        label: m.bucket ?? "формат",
        widthPct: pct,
        valueStr: `${fmtNum(pct, pct % 1 === 0 ? 0 : 1)}%`,
        tone: "neutral",
      };
    }
    const di = m.deficit_index ?? 0;
    const balanced = Math.abs(di) < DEFICIT_BALANCE_EPS;
    return {
      key: `${m.bucket ?? "?"}-${m.obj_class ?? "?"}-${i}`,
      label: m.bucket ?? "формат",
      widthPct: Math.max(0, Math.min(100, Math.abs(di) * 100)),
      valueStr: `${di > 0 ? "+" : ""}${fmtNum(di, 2)}`,
      tone: balanced ? "neutral" : deficitTone(di),
    };
  });
  return { bars, byShare };
}

function capitalize(text: string): string {
  return text.charAt(0).toUpperCase() + text.slice(1);
}

interface ClassVerdict {
  /** REAL deficit_index for the class (median of its mix rows), or null. */
  deficitIndex: number | null;
  /** REAL per-format signal when uniform across the class (e.g. «избегать»). */
  signal: string | null;
}

/**
 * Honest per-class verdict from the REAL mix: the class deficit_index (its mix
 * rows share one value in the degenerate ±1.0 case) + the per-format `signal`
 * when every format agrees. No fabrication — null when the class is absent.
 */
function classVerdict(
  tz: ReportProductTz,
  selectedClass: string | null,
): ClassVerdict {
  const rows =
    selectedClass != null
      ? tz.mix.filter((m) => m.obj_class === selectedClass)
      : tz.mix;
  const di = rows.find((m) => m.deficit_index != null)?.deficit_index ?? null;
  const signals = rows.map((m) => m.signal).filter((s): s is string => !!s);
  const uniform =
    signals.length > 0 && signals.every((s) => s === signals[0])
      ? signals[0]
      : null;
  return { deficitIndex: di, signal: uniform };
}

export function RecommendedProductCard({
  forecastReport,
  onOpenDrawer,
}: Props) {
  const tz = forecastReport?.product_tz;
  const hasProduct =
    tz != null && (tz.obj_class != null || tz.mix.length > 0 || !!tz.summary);

  // Classes present in the mix (segmented tabs). Default to the recommended
  // class (tz.obj_class) when it is one of them, else the first present class.
  const classes = useMemo(() => (tz ? classesInMix(tz) : []), [tz]);
  const [selectedClass, setSelectedClass] = useState<string | null>(null);
  const activeClass =
    selectedClass != null && classes.includes(selectedClass)
      ? selectedClass
      : tz?.obj_class != null && classes.includes(tz.obj_class)
        ? tz.obj_class
        : (classes[0] ?? null);

  const { bars: allBars, byShare } = tz
    ? buildBars(tz, activeClass)
    : { bars: [], byShare: false };

  // Degenerate signal: when bars are deficit-driven (not explicit доля) and every
  // bar carries the SAME width or sits at the ±1.0 rail, the per-format signal
  // holds no information — show the honest empty state instead of identical bars.
  const degenerateSignal =
    !byShare &&
    allBars.length > 0 &&
    (allBars.every((b) => b.widthPct === allBars[0].widthPct) ||
      allBars.every((b) => b.widthPct >= DEFICIT_RAIL_EPS * 100));

  const bars = degenerateSignal ? [] : allBars.slice(0, MAX_BARS);

  // Honest verdict for the degenerate case (no informative bars): the forecast
  // IS available — it says AVOID. Built from the active class's REAL signal /
  // deficit_index + the gate caveat. NO fabricated доля%/bars.
  const verdict = tz ? classVerdict(tz, activeClass) : null;
  const avoid =
    verdict != null &&
    ((verdict.signal === "избегать") ||
      (verdict.deficitIndex != null && verdict.deficitIndex < 0));
  const gateCaveat = tz?.gate_caveat ?? null;

  return (
    <div id="ptica-product" className={styles.card}>
      <div className={styles.cardHead}>
        <h3 className={styles.cardTitle}>
          Recommended Product
          <span className={styles.cardTitleSub}>продуктовая стратегия</span>
        </h3>
        {hasProduct && activeClass != null && (
          <span className={styles.tag}>{capitalize(activeClass)}</span>
        )}
      </div>

      {/* Segmented class-tabs (prototype .product-tabs > .ptab). One tab per
          obj_class present in the mix; the recommended class is default-active. */}
      {hasProduct && classes.length > 1 && (
        <div className={styles.productTabs} role="tablist">
          {classes.map((cls) => (
            <button
              key={cls}
              type="button"
              role="tab"
              aria-selected={cls === activeClass}
              className={`${styles.ptab} ${
                cls === activeClass ? styles.ptabActive : ""
              }`}
              onClick={() => setSelectedClass(cls)}
            >
              {capitalize(cls)}
            </button>
          ))}
        </div>
      )}

      {hasProduct && bars.length > 0 ? (
        <>
          <div className={styles.mixHead}>
            <span>Квартирография</span>
            <span>{byShare ? "доля" : "сигнал"}</span>
          </div>
          <div className={styles.hbars}>
            {bars.map((b) => (
              <div key={b.key} className={styles.hbar}>
                <span className={styles.hbarName}>{b.label}</span>
                <div className={styles.hbarTrack}>
                  <div
                    className={`${styles.hbarFill} ${TONE_CLASS[b.tone]}`}
                    style={{ width: `${b.widthPct}%` }}
                  />
                </div>
                <span className={styles.hbarValue}>{b.valueStr}</span>
              </div>
            ))}
          </div>
          {!byShare && (
            <p className={styles.panelHint}>
              Длина полосы — сила сигнала по индексу дефицита формата: зелёный —
              недонасыщенность, красный — затоварка.
            </p>
          )}
        </>
      ) : hasProduct && degenerateSignal ? (
        <div className={styles.verdictPanel}>
          <span
            className={`${styles.signalBadge} ${
              avoid ? styles.signalBadgeBad : ""
            }`}
          >
            {avoid ? "Избегать" : (verdict?.signal ?? "Нет сигнала")}
          </span>
          {verdict?.deficitIndex != null && (
            <p className={styles.verdictReason}>
              {capitalize(deficitWord(verdict.deficitIndex))} рынка · индекс
              дефицита {fmtNum(verdict.deficitIndex, 2)}
            </p>
          )}
          {gateCaveat && <p className={styles.verdictCaveat}>{gateCaveat}</p>}
        </div>
      ) : (
        <div className={styles.emptyState}>после прогноза (Сценарии)</div>
      )}

      <button
        type="button"
        className={`${styles.detailBtn} ${styles.detailBtnSpaced}`}
        onClick={() => onOpenDrawer("product")}
        title="Открыть детализацию продукта"
      >
        Подробнее
      </button>
    </div>
  );
}
