// price-history.jsx — cross-job material price tracking.
//
// Aggregates every priced line item across ALL jobs/invoices in the shared
// `jobs` collection and groups them by vendor + material identity (the same
// identity the matcher uses: real SKU, else the synonym-normalized description).
// The estimator can search a material — e.g. "1-1/8 ACR" from Porter — and see
// every invoice that material appeared on and what was charged each time, with
// the price trend over time. Pure read/aggregation; no new persistence.

// Unit prices carry more precision than the 2-decimal money formatter (a
// contract unit can be $10.3205), so show 2–4 decimals as needed.
const fmtUnit = (n) => {
  if (n == null || !isFinite(n)) return '—';
  const dp = Math.abs(n) < 100 && Math.round(n * 100) !== n * 100 ? 4 : 2;
  return '$' + n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: dp });
};

const PH_SORTS = [
  { key: 'recent', label: 'Most recent' },
  { key: 'count',  label: 'Most purchased' },
  { key: 'swing',  label: 'Biggest price change' },
  { key: 'name',   label: 'Name (A–Z)' },
];

// Material list comparators — static, so they live at module scope (a stable
// reference per render keeps memoized children honest).
const PH_CMP = {
  recent: (a, b) => (b.lastDate || '').localeCompare(a.lastDate || ''),
  count:  (a, b) => b.count - a.count,
  swing:  (a, b) => Math.abs(b.trendPct) - Math.abs(a.trendPct),
  name:   (a, b) => (a.desc || '').localeCompare(b.desc || ''),
};

// Pretty invoice date ("Mar 5, 2026"); falls back to the raw string.
const phFmtDate = (d) => {
  if (!d) return '—';
  const dt = new Date(d + 'T00:00:00');
  return isNaN(dt) ? d : dt.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
};

// Stable material identity within a vendor — mirrors the matcher's itemKeyOf:
// a real vendor SKU is the strongest signal, otherwise the synonym-normalized
// description (so "alum tape" and "aluminum tape" track as one material).
// Canonical vendor key so the same supplier written differently collapses into
// one: case-insensitive; punctuation/symbols (& / . , - etc.) dropped; connector
// words (and/the) and trailing legal/entity suffixes (Inc, LLC, Co, Corp, Ltd, …)
// stripped. So "Porter Pipe & Supply, Inc." / "PORTER PIPE AND SUPPLY" /
// "porter pipe/supply" all map to one vendor. (Display name chosen in groupMaterials.)
const VENDOR_NOISE_RE = /\b(inc|incorporated|llc|llp|lp|co|corp|corporation|company|ltd|limited|plc|and|the)\b/g;
// Branch / location qualifier ("/Chicago Branch", "Chicago Branch", "West Division").
const VENDOR_BRANCH_RE = /\b([a-z]+\s+)?(branch|warehouse|division|dept|department|location)\b/g;
const canonicalVendor = (v) => {
  let s = (v || '').toLowerCase();
  s = s.replace(/[&._,'"()/\\\-]+/g, ' '); // symbols (incl. &, /) → space
  s = s.replace(VENDOR_BRANCH_RE, ' ');    // drop branch/location designations
  s = s.replace(VENDOR_NOISE_RE, ' ');     // connectors + legal suffixes
  return s.replace(/\s+/g, ' ').trim() || 'unknown vendor';
};

const materialKey = (vendor, sku, desc) => {
  const id = (sku && !isAutoSku(sku))
    ? `sku:${sku.toLowerCase()}`
    : `desc:${matchNormalize(desc)}`;
  return `${canonicalVendor(vendor)}::${id}`;
};

// Flatten jobs → invoices → priced line items into individual purchase records.
function buildPurchaseRecords(jobs) {
  const records = [];
  let uid = 0;
  for (const job of jobs || []) {
    const jobVendor = job.quote?.vendor || '';
    for (const inv of (job.invoices || [])) {
      if (inv._hidden) continue;
      const vendor = inv.vendor || jobVendor || 'Unknown vendor';
      for (const it of (inv.items || [])) {
        const qty    = +it.qty || 0;
        // Trust the printed unit; fall back to amount ÷ qty when only a line
        // total is present. Skip lines with no usable price (fees, $0 rows).
        const unit = it.unit > 0
          ? it.unit
          : (qty > 0 && it.amount > 0 ? +(it.amount / qty).toFixed(4) : 0);
        if (!(unit > 0)) continue;
        records.push({
          _uid:      ++uid,
          vendor,
          sku:       it.sku || '',
          desc:      it.desc || '',
          uom:       normalizeUOM(it.uom) || '',
          qty,
          unit,
          amount:    it.amount > 0 ? +it.amount : +(qty * unit).toFixed(2),
          date:      inv.date || '',
          invoiceId: inv.id || '',
          jobId:     job.id,
          jobNumber: job.jobNumber || '',
          jobName:   job.name || job.jobName || '',
        });
      }
    }
  }
  return records;
}

// Group purchase records into materials with price stats and a date-sorted
// purchase history.
function groupMaterials(records) {
  // One clean display name per canonical vendor: the most common raw spelling
  // (tie-break: the longer, more complete one). Keeps every grouped material's
  // vendor label identical so the filter dropdown also dedupes.
  const vendorNames = new Map(); // canonical -> Map(raw spelling -> count)
  for (const r of records) {
    const cv = canonicalVendor(r.vendor);
    let names = vendorNames.get(cv);
    if (!names) { names = new Map(); vendorNames.set(cv, names); }
    names.set(r.vendor, (names.get(r.vendor) || 0) + 1);
  }
  const vendorDisplay = new Map();
  for (const [cv, names] of vendorNames) {
    // Most common spelling; tie-break to the SHORTER (less branch/suffix noise).
    let best = null, bestN = -1;
    for (const [name, n] of names) {
      if (best === null || n > bestN || (n === bestN && name.length < best.length)) { best = name; bestN = n; }
    }
    // Standardized label: one all-caps name per vendor (dropdown + list + detail).
    vendorDisplay.set(cv, (best || '').toUpperCase());
  }

  const map = new Map();
  for (const r of records) {
    const k = materialKey(r.vendor, r.sku, r.desc);
    let g = map.get(k);
    if (!g) {
      g = { key: k, vendor: vendorDisplay.get(canonicalVendor(r.vendor)) || r.vendor, sku: '', desc: '', records: [] };
      map.set(k, g);
    }
    g.records.push(r);
    // Representative SKU = first real one; representative desc = the longest
    // seen (usually the most complete vendor wording).
    if (r.sku && !isAutoSku(r.sku) && (!g.sku || isAutoSku(g.sku))) g.sku = r.sku;
    if ((r.desc || '').length > (g.desc || '').length) g.desc = r.desc;
  }

  const out = [];
  for (const g of map.values()) {
    g.records.sort((a, b) => (a.date || '').localeCompare(b.date || ''));
    const units  = g.records.map((r) => r.unit);
    const first  = g.records[0];
    const latest = g.records[g.records.length - 1];
    g.count    = g.records.length;
    g.min      = Math.min(...units);
    g.max      = Math.max(...units);
    g.avg      = +(units.reduce((s, u) => s + u, 0) / units.length).toFixed(4);
    g.first    = first;
    g.latest   = latest;
    g.lastDate = latest.date || '';
    // Trend = first → latest price change (positive = getting more expensive).
    g.trendPct = first.unit > 0 ? +(((latest.unit - first.unit) / first.unit) * 100).toFixed(1) : 0;
    out.push(g);
  }
  return out;
}

// Responsive area chart of unit price over time (oldest → newest) for the
// detail view. Stretches to its container width; stroke stays crisp via
// non-scaling-stroke. Single price (1 point) renders a flat reference line.
function PriceChart({ records }) {
  if (!records || records.length === 0) return null;
  const W = 600, H = 130, padX = 6, padY = 16;
  const units = records.map((r) => r.unit);
  const min = Math.min(...units), max = Math.max(...units), span = max - min || 1;
  const n = records.length;
  const step = n > 1 ? (W - padX * 2) / (n - 1) : 0;
  const xy = (r, i) => [
    padX + (n > 1 ? i * step : (W - padX * 2) / 2),
    H - padY - ((r.unit - min) / span) * (H - padY * 2),
  ];
  const pts = records.map(xy);
  const line = pts.map((p) => `${p[0].toFixed(1)},${p[1].toFixed(1)}`).join(' ');
  const up  = units[n - 1] >= units[0];
  const col = up ? 'var(--bad)' : 'var(--ok)';
  const area = `${pts[0][0].toFixed(1)},${H - padY} ${line} ${pts[n - 1][0].toFixed(1)},${H - padY}`;
  return (
    <svg className="ph-d-svg" viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="none"
      width="100%" height="130" role="img" aria-label="Unit price over time">
      <polygon points={area} fill={col} opacity="0.08" />
      <polyline points={line} fill="none" stroke={col} strokeWidth="2"
        vectorEffect="non-scaling-stroke" strokeLinejoin="round" strokeLinecap="round" />
      {pts.map((p, i) => (
        <circle key={records[i]._uid} cx={p[0]} cy={p[1]} r="3" fill="var(--surface-1)"
          stroke={col} strokeWidth="1.5" vectorEffect="non-scaling-stroke" />
      ))}
    </svg>
  );
}

function TrendBadge({ pct }) {
  if (pct === 0) return <span className="ph-trend ph-trend-flat">no change</span>;
  const up = pct > 0;
  return (
    <span className={`ph-trend ${up ? 'ph-trend-up' : 'ph-trend-down'}`}>
      {up ? '▲' : '▼'} {Math.abs(pct)}%
    </span>
  );
}

// Compact inline sparkline for list rows and mover cards.
function MiniSpark({ records, w = 76, h = 22 }) {
  if (!records || records.length < 2) return <span className="ph-spark-na" aria-hidden="true">—</span>;
  const units = records.map((r) => r.unit);
  const min = Math.min(...units), max = Math.max(...units), span = max - min || 1;
  const pad = 2, step = (w - pad * 2) / (records.length - 1);
  const pts = records.map((r, i) =>
    `${(pad + i * step).toFixed(1)},${(h - pad - ((r.unit - min) / span) * (h - pad * 2)).toFixed(1)}`
  ).join(' ');
  const up = units[units.length - 1] >= units[0];
  return (
    <svg className="ph-mspark" width={w} height={h} viewBox={`0 0 ${w} ${h}`} aria-hidden="true">
      <polyline points={pts} fill="none" stroke={up ? 'var(--bad)' : 'var(--ok)'} strokeWidth="1.5"
        strokeLinejoin="round" strokeLinecap="round" />
    </svg>
  );
}

// Compact money for KPI tiles ($1.2M / $12.3k / $940).
const fmtMoneyShort = (n) => {
  const a = Math.abs(n || 0);
  if (a >= 1e6) return '$' + (n / 1e6).toFixed(1) + 'M';
  if (a >= 1e3) return '$' + (n / 1e3).toFixed(1) + 'k';
  return '$' + Math.round(n || 0);
};

// Detail: every invoice this material appeared on, oldest → newest, with the
// change vs. the previous purchase so price creep is obvious.
function MaterialDetail({ material, onBack }) {
  const rows = material.records;
  const uom  = material.latest.uom ? `/${material.latest.uom}` : '';

  return (
    <div className="ph-detail">
      <button className="ph-back" onClick={onBack}>
        <Icon name="chev" size={12} style={{ transform: 'rotate(180deg)' }} /> All materials
      </button>

      {/* Hero: identity on the left, current price + trend on the right */}
      <div className="ph-d-hero">
        <div className="ph-d-id">
          <div className="ph-d-vendor">{material.vendor}</div>
          <h2 className="ph-d-desc">{material.desc || '(no description)'}</h2>
          {material.sku && !isAutoSku(material.sku) && <span className="ph-d-sku mono">{material.sku}</span>}
        </div>
        <div className="ph-d-price">
          <span className="ph-d-price-v num">{fmtUnit(material.latest.unit)}</span>
          <span className="ph-d-price-l">latest {uom}</span>
          <TrendBadge pct={material.trendPct} />
        </div>
      </div>

      {/* Price-over-time chart */}
      <div className="ph-d-chart">
        <PriceChart records={rows} />
        <div className="ph-d-chart-cap">
          <span><span className="ph-dim">first</span> {phFmtDate(material.first.date)} · {fmtUnit(material.first.unit)}</span>
          <span><span className="ph-dim">latest</span> {phFmtDate(material.latest.date)} · {fmtUnit(material.latest.unit)}</span>
        </div>
      </div>

      {/* Stat strip */}
      <div className="ph-stats">
        <div className="ph-stat"><span className="ph-stat-l">Average</span><span className="ph-stat-v num">{fmtUnit(material.avg)}</span></div>
        <div className="ph-stat"><span className="ph-stat-l">Lowest</span><span className="ph-stat-v num ph-good">{fmtUnit(material.min)}</span></div>
        <div className="ph-stat"><span className="ph-stat-l">Highest</span><span className="ph-stat-v num ph-bad">{fmtUnit(material.max)}</span></div>
        <div className="ph-stat"><span className="ph-stat-l">Purchases</span><span className="ph-stat-v num">{material.count}</span></div>
      </div>

      {/* Purchase timeline */}
      <div className="ph-d-tablewrap">
        <div className="ph-table ph-dtable" role="table">
          <div className="ph-dtr ph-dtr-head" role="row">
            <span role="columnheader">Date</span>
            <span role="columnheader">Job</span>
            <span role="columnheader">Invoice</span>
            <span role="columnheader" className="ph-num">Qty</span>
            <span role="columnheader" className="ph-num">Unit price</span>
            <span role="columnheader" className="ph-num">Δ vs prev</span>
          </div>
          {rows.map((r, i) => {
            const prev = i > 0 ? rows[i - 1] : null;
            const delta = prev ? +(r.unit - prev.unit).toFixed(4) : 0;
            const deltaPct = prev && prev.unit > 0 ? (delta / prev.unit) * 100 : 0;
            const tone = !prev ? '' : delta > 0.0001 ? 'ph-bad' : delta < -0.0001 ? 'ph-good' : '';
            return (
              <div key={r._uid} className="ph-dtr" role="row">
                <span role="cell" className="ph-nowrap">{phFmtDate(r.date)}</span>
                <span role="cell" className="ph-job" title={r.jobName || r.jobNumber}>{r.jobNumber || r.jobName || '—'}</span>
                <span role="cell" className="mono ph-dim ph-job">{r.invoiceId || '—'}</span>
                <span role="cell" className="ph-num ph-nowrap">{r.qty.toLocaleString()}{r.uom ? ' ' + r.uom : ''}</span>
                <span role="cell" className="ph-num ph-unit ph-nowrap">{fmtUnit(r.unit)}</span>
                <span role="cell" className={`ph-num ph-nowrap ${tone}`}>
                  {!prev ? <span className="ph-dim">first buy</span>
                    : delta === 0 ? <span className="ph-dim">no change</span>
                    : `${delta > 0 ? '+' : '−'}${fmtUnit(Math.abs(delta))} (${deltaPct > 0 ? '+' : '−'}${Math.abs(deltaPct).toFixed(1)}%)`}
                </span>
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
}

function PriceHistoryView({ user }) {
  const [jobs, setJobs]       = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const [query, setQuery]     = React.useState('');
  const [vendor, setVendor]   = React.useState('all');
  const [sortBy, setSortBy]   = React.useState('recent');
  const [selected, setSelected] = React.useState(null); // material key

  React.useEffect(() => {
    const unsub = fbDb.collection('jobs').onSnapshot(
      (snap) => { setJobs(snap.docs.map((d) => ({ id: d.id, ...d.data() }))); setLoading(false); },
      ()      => setLoading(false)
    );
    return unsub;
  }, [user?.uid]);

  const materials = React.useMemo(() => groupMaterials(buildPurchaseRecords(jobs)), [jobs]);

  const vendors = React.useMemo(
    () => Array.from(new Set(materials.map((m) => m.vendor))).sort((a, b) => a.localeCompare(b)),
    [materials]
  );

  const visible = React.useMemo(() => {
    const q = query.trim().toLowerCase();
    const filtered = materials.flatMap((m) => {
      if (vendor !== 'all' && m.vendor !== vendor) return [];
      if (q && !(`${m.desc} ${m.sku} ${m.vendor}`.toLowerCase().includes(q))) return [];
      return [m];
    });
    return filtered.sort(PH_CMP[sortBy] || PH_CMP.recent);
  }, [materials, query, vendor, sortBy]);

  const selectedMaterial = React.useMemo(
    () => materials.find((m) => m.key === selected) || null,
    [materials, selected]
  );

  // Headline numbers + the actionable anomalies (materials whose price is
  // climbing) — surfaced first so the estimator sees where money is leaking
  // instead of scanning a flat list.
  const stats = React.useMemo(() => {
    let spend = 0;
    for (const m of materials) {
      for (const r of m.records) spend += r.amount;
    }
    const increases = materials.filter((m) => m.count >= 2 && m.trendPct > 0);
    const topMovers = increases.sort((a, b) => b.trendPct - a.trendPct).slice(0, 4);
    return { spend, increaseCount: increases.length, topMovers };
  }, [materials]);

  if (selectedMaterial) {
    return (
      <div className="ph">
        <MaterialDetail material={selectedMaterial} onBack={() => setSelected(null)} />
        <PriceHistoryStyles />
      </div>
    );
  }

  return (
    <div className="ph">
      <div className="ph-hd">
        <div>
          <h1 className="ph-title">Price History</h1>
          <p className="ph-sub">Every material you've been invoiced, and what it cost over time.</p>
        </div>
      </div>

      {!loading && materials.length > 0 && (
        <div className="ph-kpis">
          <div className="ph-kpi">
            <span className="ph-kpi-l">Materials tracked</span>
            <span className="ph-kpi-v num">{materials.length}</span>
          </div>
          <div className="ph-kpi">
            <span className="ph-kpi-l">Spend tracked</span>
            <span className="ph-kpi-v num">{fmtMoneyShort(stats.spend)}</span>
          </div>
          <div className="ph-kpi">
            <span className="ph-kpi-l">Vendors</span>
            <span className="ph-kpi-v num">{vendors.length}</span>
          </div>
          <button
            className={`ph-kpi ph-kpi-btn ${sortBy === 'swing' ? 'is-on' : ''}`}
            onClick={() => setSortBy('swing')}
            title="Sort by biggest price change"
          >
            <span className="ph-kpi-l">Getting pricier</span>
            <span className={`ph-kpi-v num ${stats.increaseCount > 0 ? 'ph-bad' : ''}`}>{stats.increaseCount}</span>
          </button>
        </div>
      )}

      {!loading && !query.trim() && stats.topMovers.length > 0 && (
        <div className="ph-movers">
          <div className="ph-movers-h">
            <Icon name="flag" size={13} />
            <span>Biggest price increases</span>
            <span className="ph-movers-sub">first invoice → latest</span>
          </div>
          <div className="ph-movers-grid">
            {stats.topMovers.map((m) => (
              <button key={m.key} className="ph-mover" onClick={() => setSelected(m.key)}>
                <div className="ph-mover-top">
                  <span className="ph-mover-desc" title={m.desc}>{m.desc || '(no description)'}</span>
                  <TrendBadge pct={m.trendPct} />
                </div>
                <div className="ph-mover-mid">
                  <span className="ph-mover-vendor" title={m.vendor}>{m.vendor}</span>
                  <MiniSpark records={m.records} w={64} h={20} />
                </div>
                <div className="ph-mover-bot num">
                  <span className="ph-dim">{fmtUnit(m.first.unit)}</span>
                  <Icon name="arrow" size={11} />
                  <span className="ph-unit">{fmtUnit(m.latest.unit)}</span>
                </div>
              </button>
            ))}
          </div>
        </div>
      )}

      <div className="ph-controls">
        <div className="ph-search">
          <Icon name="search" size={14} />
          <input
            className="ph-search-input"
            placeholder="Search a material, SKU, or vendor… (e.g. 1-1/8 ACR)"
            value={query}
            onChange={(e) => setQuery(e.target.value)}
            aria-label="Search materials"
          />
          {query && <button className="ph-clear" onClick={() => setQuery('')} aria-label="Clear search"><Icon name="x" size={12} /></button>}
        </div>
        <select className="ph-select" value={vendor} onChange={(e) => setVendor(e.target.value)} aria-label="Filter by vendor">
          <option value="all">All vendors</option>
          {vendors.map((v) => <option key={v} value={v}>{v}</option>)}
        </select>
        <select className="ph-select" value={sortBy} onChange={(e) => setSortBy(e.target.value)} aria-label="Sort materials">
          {PH_SORTS.map((s) => <option key={s.key} value={s.key}>{s.label}</option>)}
        </select>
      </div>

      {loading ? (
        <div className="ph-empty">Loading invoices…</div>
      ) : materials.length === 0 ? (
        <div className="ph-empty">
          <Icon name="history" size={20} />
          <div className="ph-empty-h">No invoiced materials yet</div>
          <div className="ph-empty-s">Add invoices to your jobs and their line items will be tracked here automatically.</div>
        </div>
      ) : visible.length === 0 ? (
        <div className="ph-empty">No materials match your search.</div>
      ) : (
        <div className="ph-list" role="table">
          <div className="ph-lrow ph-lhead" role="row">
            <span role="columnheader">Material</span>
            <span role="columnheader">Vendor</span>
            <span role="columnheader" className="ph-num">Buys</span>
            <span role="columnheader" className="ph-num">Latest</span>
            <span role="columnheader" className="ph-spark-head">Trend</span>
            <span role="columnheader" className="ph-num">Change</span>
          </div>
          {visible.map((m) => (
            <button key={m.key} className="ph-lrow ph-lrow-btn" role="row" onClick={() => setSelected(m.key)}>
              <span role="cell" className="ph-mat">
                <span className="ph-mat-desc" title={m.desc}>{m.desc || '(no description)'}</span>
                {m.sku && !isAutoSku(m.sku) && <span className="ph-mat-sku mono">{m.sku}</span>}
              </span>
              <span role="cell" className="ph-vendor">{m.vendor}</span>
              <span role="cell" className="ph-num">{m.count}</span>
              <span role="cell" className="ph-num ph-unit" title={m.min === m.max ? '' : `range ${fmtUnit(m.min)}–${fmtUnit(m.max)}`}>{fmtUnit(m.latest.unit)}</span>
              <span role="cell" className="ph-spark-cell"><MiniSpark records={m.records} /></span>
              <span role="cell" className="ph-num"><TrendBadge pct={m.trendPct} /></span>
            </button>
          ))}
        </div>
      )}

      <PriceHistoryStyles />
    </div>
  );
}

const PriceHistoryStyles = () => (
  <style>{`
    .ph { max-width: 1100px; margin: 0 auto; padding: 20px 16px 60px; }
    .ph-hd { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; margin-bottom: 16px; }
    .ph-title { font-size: 22px; font-weight: 650; margin: 0; }
    .ph-sub { margin: 4px 0 0; color: var(--fg-muted); font-size: 13px; }
    .ph-count { color: var(--fg-dim); font-size: 12px; white-space: nowrap; }

    /* KPI strip */
    .ph-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 10px; margin-bottom: 16px; }
    .ph-kpi { background: var(--surface-2); border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px;
      display: flex; flex-direction: column; gap: 4px; text-align: left; }
    .ph-kpi-l { font-size: 11px; text-transform: uppercase; letter-spacing: .03em; color: var(--fg-dim); }
    .ph-kpi-v { font-size: 21px; font-weight: 680; line-height: 1; }
    .ph-kpi-btn { cursor: pointer; transition: border-color .15s, background .15s; }
    .ph-kpi-btn:hover { border-color: var(--accent); }
    .ph-kpi-btn.is-on { border-color: var(--accent); background: oklch(from var(--accent) l c h / 0.10); }

    /* Top movers (anomaly triage) */
    .ph-movers { margin-bottom: 18px; }
    .ph-movers-h { display: flex; align-items: center; gap: 7px; font-size: 12.5px; font-weight: 600; color: var(--fg-muted); margin-bottom: 8px; }
    .ph-movers-h > :first-child { color: var(--bad); }
    .ph-movers-sub { font-weight: 400; color: var(--fg-dim); font-size: 11px; }
    .ph-movers-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 10px; }
    .ph-mover { text-align: left; background: var(--surface-1); border: 1px solid var(--border); border-left: 3px solid var(--bad);
      border-radius: 9px; padding: 11px 13px; cursor: pointer; display: flex; flex-direction: column; gap: 7px; transition: background .15s; }
    .ph-mover:hover { background: var(--surface-2); }
    .ph-mover-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
    .ph-mover-desc { font-size: 13px; font-weight: 550; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .ph-mover-mid { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
    .ph-mover-vendor { font-size: 11.5px; color: var(--fg-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .ph-mover-bot { display: flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--fg-dim); }

    .ph-mspark { display: block; flex-shrink: 0; }
    .ph-spark-cell { display: flex; justify-content: flex-end; }
    .ph-spark-head { text-align: center; }
    .ph-spark-na { color: var(--fg-dim); }

    .ph-controls { display: flex; gap: 10px; margin-bottom: 16px; flex-wrap: wrap; }
    .ph-search { flex: 1 1 280px; display: flex; align-items: center; gap: 8px; padding: 0 10px;
      background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px; color: var(--fg-dim); }
    .ph-search-input { flex: 1; background: none; border: none; outline: none; color: var(--fg); font-size: 13.5px; padding: 9px 0; }
    .ph-clear { background: none; border: none; color: var(--fg-dim); cursor: pointer; display: flex; padding: 2px; }
    .ph-select { background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px; color: var(--fg);
      font-size: 13px; padding: 0 10px; height: 38px; cursor: pointer; }

    .ph-list, .ph-table { border: 1px solid var(--border); border-radius: 10px; overflow: hidden; background: var(--surface-1); }
    .ph-lrow { display: grid; grid-template-columns: minmax(0,3fr) minmax(0,1.5fr) 46px 92px 92px 104px;
      gap: 12px; align-items: center; padding: 11px 14px; text-align: left; }
    .ph-lhead { font-size: 10.5px; text-transform: uppercase; letter-spacing: .04em; color: var(--fg-dim);
      background: var(--surface-2); border-bottom: 1px solid var(--border); }
    .ph-lrow-btn { width: 100%; background: none; border: none; border-bottom: 1px solid var(--border); cursor: pointer; font: inherit; color: inherit; }
    .ph-lrow-btn:last-child { border-bottom: none; }
    .ph-lrow-btn:hover { background: var(--surface-2); }
    .ph-mat { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
    .ph-mat-desc { font-size: 13.5px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .ph-mat-sku { font-size: 10.5px; color: var(--fg-dim); }
    .ph-vendor { font-size: 12.5px; color: var(--fg-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .ph-num { text-align: right; font-variant-numeric: tabular-nums; }
    .ph-unit { font-weight: 600; }
    .ph-dim { color: var(--fg-dim); }
    .ph-good { color: var(--ok); }
    .ph-bad  { color: var(--bad); }

    .ph-trend { font-size: 11.5px; font-weight: 600; white-space: nowrap; }
    .ph-trend-up   { color: var(--bad); }
    .ph-trend-down { color: var(--ok); }
    .ph-trend-flat { color: var(--fg-dim); font-weight: 500; }

    .ph-empty { text-align: center; color: var(--fg-muted); padding: 60px 20px; display: flex; flex-direction: column; align-items: center; gap: 8px; }
    .ph-empty-h { font-size: 15px; font-weight: 600; color: var(--fg); }
    .ph-empty-s { font-size: 13px; max-width: 380px; }

    .ph-back { display: inline-flex; align-items: center; gap: 6px; background: var(--surface-2); border: 1px solid var(--border);
      border-radius: 8px; color: var(--fg-muted); font-size: 12.5px; font-weight: 500; cursor: pointer;
      padding: 7px 12px 7px 10px; margin-bottom: 16px; transition: background .15s, color .15s, border-color .15s; }
    .ph-back:hover { background: var(--surface-3); color: var(--fg); border-color: var(--fg-dim); }
    .ph-back:active { transform: translateY(0.5px); }
    /* detail — hero */
    .ph-d-hero { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px;
      background: var(--surface-2); border: 1px solid var(--border); border-radius: 12px; padding: 16px 18px; margin-bottom: 14px; }
    .ph-d-id { min-width: 0; }
    .ph-d-vendor { font-size: 11.5px; font-weight: 600; letter-spacing: .03em; color: var(--fg-muted); margin-bottom: 3px; }
    .ph-d-desc { font-size: 20px; font-weight: 650; margin: 0 0 6px; line-height: 1.2; }
    .ph-d-sku { font-size: 11.5px; color: var(--fg-dim); background: var(--surface-3); border: 1px solid var(--border); padding: 2px 7px; border-radius: 5px; }
    .ph-d-price { display: flex; flex-direction: column; align-items: flex-end; gap: 3px; flex-shrink: 0; }
    .ph-d-price-v { font-size: 26px; font-weight: 700; line-height: 1; letter-spacing: -.01em; }
    .ph-d-price-l { font-size: 10.5px; text-transform: uppercase; letter-spacing: .04em; color: var(--fg-dim); }

    /* detail — chart */
    .ph-d-chart { background: var(--surface-1); border: 1px solid var(--border); border-radius: 12px; padding: 12px 14px 10px; margin-bottom: 14px; }
    .ph-d-svg { display: block; width: 100%; height: 130px; }
    .ph-d-chart-cap { display: flex; justify-content: space-between; gap: 12px; margin-top: 6px;
      font-size: 11.5px; color: var(--fg-muted); font-variant-numeric: tabular-nums; }

    .ph-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 10px; margin-bottom: 16px; }
    .ph-stat { background: var(--surface-2); border: 1px solid var(--border); border-radius: 10px; padding: 11px 14px; display: flex; flex-direction: column; gap: 4px; }
    .ph-stat-l { font-size: 10.5px; text-transform: uppercase; letter-spacing: .03em; color: var(--fg-dim); }
    .ph-stat-v { font-size: 17px; font-weight: 650; }

    /* detail — timeline table (scroll-safe on narrow screens) */
    .ph-d-tablewrap { overflow-x: auto; border: 1px solid var(--border); border-radius: 10px; background: var(--surface-1); }
    .ph-dtable { border: none; border-radius: 0; min-width: 640px; }
    .ph-dtr { display: grid; grid-template-columns: 116px minmax(90px,1fr) minmax(90px,1fr) 96px 104px 150px;
      gap: 14px; align-items: center; padding: 10px 16px; border-bottom: 1px solid var(--border); font-size: 13px; }
    .ph-dtr:last-child { border-bottom: none; }
    .ph-dtr:not(.ph-dtr-head):hover { background: var(--surface-2); }
    .ph-dtr-head { font-size: 10px; text-transform: uppercase; letter-spacing: .05em; color: var(--fg-dim);
      background: var(--surface-2); position: sticky; top: 0; }
    .ph-job { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .ph-nowrap { white-space: nowrap; }
  `}</style>
);

if (typeof window !== 'undefined') window.PriceHistoryView = PriceHistoryView;
