1import type { PreviewMiniPlayerFrame } from "../preview/previewMiniPlayerLayout";
2import { DETAILS_CARD_CLEARANCE } from "./chatCanvasLayout";
3
4/** Inset of the card from the canvas edges; the find bar shares it to line up. */
5export const THREAD_DETAILS_CARD_GAP = 12;
6// Keep in sync with --thread-details-panel-width, which sizes the popover and the find bar.
7const THREAD_DETAILS_CARD_WIDTH = 280;
8
9export function resolveThreadDetailsCardDensity(
10 height: number,
11 content: { full: number; compact: number },
12) {
13 if (content.full === 0 || content.full <= height) return "full";
14 if (content.compact === 0 || content.compact <= height) return "compact";
15 return "essential";
16}
17
18/**
19 * The card pins to the top right while a readable chat lane fits beside it.
20 * The chat canvas decides whether chat moves over to make room.
21 */
22export function resolveThreadDetailsCardLayout({
23 container,
24 lane,
25 frame,
26 overlapsDetailsCard = false,
27 topInset = 0,
28}: {
29 container: { width: number; height: number };
30 lane: { padding: number; minChatWidth: number };
31 frame: PreviewMiniPlayerFrame | null;
32 overlapsDetailsCard?: boolean;
33 /** Space taken above the card, such as the open find bar. */
34 topInset?: number;
35}) {
36 const gap = THREAD_DETAILS_CARD_GAP;
37 const width = THREAD_DETAILS_CARD_WIDTH;
38 const x = container.width - width - gap;
39 if (x - DETAILS_CARD_CLEARANCE - lane.padding < lane.minChatWidth) return null;
40 const y = gap + topInset;
41 // Resizing consumes the height above the player. Dragging first tries to
42 // clear the full card and folds it only when there is no readable placement.
43 const height =
44 overlapsDetailsCard && frame && frame.x + frame.width > x - gap && frame.x < x + width + gap
45 ? Math.min(container.height - y - gap, frame.y - y - gap)
46 : container.height - y - gap;
47 if (height < 160) return null;
48 return {
49 x,
50 right: gap,
51 width,
52 y,
53 height,
54 } as const;
55}