import { MIN_PANE_HEIGHT, MIN_PANE_WIDTH } from "./types" import type { CapacityConfig, TmuxPaneInfo } from "./types" import { DIVIDER_SIZE, MAX_GRID_SIZE, computeAgentAreaWidth, } from "./tmux-grid-constants" export interface GridCapacity { cols: number rows: number total: number } export interface GridSlot { row: number col: number } export interface GridPlan { cols: number rows: number slotWidth: number slotHeight: number } type CapacityOptions = CapacityConfig | number | undefined function resolveMinPaneWidth(options?: CapacityOptions): number { if (typeof options === "number") { return Math.max(1, options) } if (options && typeof options.agentPaneWidth === "number") { return Math.max(1, options.agentPaneWidth) } return MIN_PANE_WIDTH } function resolveAgentAreaWidth(windowWidth: number, options?: CapacityOptions): number { if (typeof options === "number") { return computeAgentAreaWidth(windowWidth) } return computeAgentAreaWidth(windowWidth, options) } export function calculateCapacity( windowWidth: number, windowHeight: number, options?: CapacityOptions, mainPaneWidth?: number, ): GridCapacity { const availableWidth = typeof mainPaneWidth === "number" ? Math.max(0, windowWidth - mainPaneWidth - DIVIDER_SIZE) : resolveAgentAreaWidth(windowWidth, options) const minPaneWidth = resolveMinPaneWidth(options) const cols = Math.min( MAX_GRID_SIZE, Math.max( 0, Math.floor( (availableWidth + DIVIDER_SIZE) / (minPaneWidth + DIVIDER_SIZE), ), ), ) const rows = Math.min( MAX_GRID_SIZE, Math.max( 0, Math.floor( (windowHeight + DIVIDER_SIZE) / (MIN_PANE_HEIGHT + DIVIDER_SIZE), ), ), ) return { cols, rows, total: cols * rows } } export function computeGridPlan( windowWidth: number, windowHeight: number, paneCount: number, options?: CapacityOptions, mainPaneWidth?: number, ): GridPlan { const capacity = calculateCapacity(windowWidth, windowHeight, options, mainPaneWidth) const { cols: maxCols, rows: maxRows } = capacity if (maxCols === 0 || maxRows === 0 || paneCount === 0) { return { cols: 1, rows: 1, slotWidth: 0, slotHeight: 0 } } let bestCols = 1 let bestRows = 1 let bestArea = Infinity for (let rows = 1; rows <= maxRows; rows++) { for (let cols = 1; cols <= maxCols; cols++) { if (cols * rows < paneCount) continue const area = cols * rows if (area < bestArea || (area === bestArea && rows < bestRows)) { bestCols = cols bestRows = rows bestArea = area } } } const availableWidth = typeof mainPaneWidth === "number" ? Math.max(0, windowWidth - mainPaneWidth - DIVIDER_SIZE) : resolveAgentAreaWidth(windowWidth, options) const slotWidth = Math.floor(availableWidth / bestCols) const slotHeight = Math.floor(windowHeight / bestRows) return { cols: bestCols, rows: bestRows, slotWidth, slotHeight } } export function mapPaneToSlot( pane: TmuxPaneInfo, plan: GridPlan, mainPaneWidth: number, ): GridSlot { const rightAreaX = mainPaneWidth const relativeX = Math.max(0, pane.left - rightAreaX) const relativeY = pane.top const col = plan.slotWidth > 0 ? Math.min(plan.cols - 1, Math.floor(relativeX / plan.slotWidth)) : 0 const row = plan.slotHeight > 0 ? Math.min(plan.rows - 1, Math.floor(relativeY / plan.slotHeight)) : 0 return { row, col } }