import { useMemo } from "react";
import { SectionHeading } from "../../../components";
import { colors } from "../../../theme/colors";
import { Box, Text, TextAttributes, useUiCapabilities } from "../../../ui";
import type { EarningsEvent } from "../../../types/data-provider";
import { buildEarningsDetail, formatGrowth, rangeBar, type EstimateBlock } from "./detail-model";
interface EarningsDetailViewProps {
event: EarningsEvent;
width: number;
height: number;
}
function signColor(value: number | null): string {
if (value == null) return colors.textDim;
return value > 0 ? colors.positive : value < 0 ? colors.negative : colors.textDim;
}
/**
* Where the consensus sits between the low and high estimates. The desktop
* draws a real track and dot; box-drawing glyphs are for the terminal only.
*/
function EstimateRangeTrack({ position, width }: { position: number; width: number }) {
const { nativePaneChrome } = useUiCapabilities();
if (nativePaneChrome) {
return (
);
}
return {` ${rangeBar(position, width)} `};
}
/** One estimate: the consensus, where it sits in the range, and how it moved. */
function EstimatePanel({ block, width }: { block: EstimateBlock; width: number }) {
const labelWidth = 14;
const barWidth = Math.max(12, width - block.lowText.length - block.highText.length - 4);
return (
{block.consensus ?? "no consensus"}
{block.analysts != null ? (
{` ${block.analysts} ${block.analysts === 1 ? "analyst" : "analysts"}`}
) : null}
{block.position != null ? (
{block.lowText}
{block.highText}
) : block.lowText || block.highText ? (
{`range ${block.lowText || "?"} to ${block.highText || "?"}`}
) : null}
{block.yearAgo || block.growth != null ? (
year ago
{block.yearAgo ?? ""}
{block.growth != null ? (
{`${block.yearAgo ? " " : ""}${formatGrowth(block.growth)}`}
) : null}
) : null}
{block.trend.map((row) => (
{row.label}
{row.value}
{row.changeText ? {` ${row.changeText}`} : null}
))}
{block.revisions.map((row) => {
const net = row.up != null && row.down != null ? row.up - row.down : null;
return (
{`revisions ${row.label}`}
{`${row.up ?? "?"} up · ${row.down ?? "?"} down`}
);
})}
);
}
/**
* Everything the provider knows about one upcoming report, laid out instead
* of squeezed into fifteen columns. The deeper surfaces (ticker, estimates,
* calls, analysts) are the pane footer's `t` `e` `c` `a`, not a row here.
*/
export function EarningsDetailView({ event, width, height }: EarningsDetailViewProps) {
const detail = useMemo(() => buildEarningsDetail(event), [event]);
const inner = Math.max(20, width - 2);
const wide = inner >= 76;
const panelWidth = wide ? Math.floor((inner - 3) / 2) : inner;
const facts = [
detail.date,
detail.timing,
detail.dateStatus ? `date ${detail.dateStatus}` : null,
detail.period,
].filter((part): part is string => !!part);
return (
{detail.date}
{facts.length > 1 ? (
{` ${facts.slice(1).join(" · ")}`}
) : null}
);
}