summary history files

vendor/github.com/olekukonko/tablewriter/stream.go
package tablewriter

import (
	"math"

	"github.com/olekukonko/errors"
	"github.com/olekukonko/tablewriter/pkg/twwidth"
	"github.com/olekukonko/tablewriter/tw"
)

// Close finalizes the table stream.
// It requires the stream to be started (by calling NewStreamTable).
// It calls the renderer's Close method to render final elements (like the bottom border) and close the stream.
func (t *Table) Close() error {
	t.logger.Debug("Close() called. Finalizing stream.")

	// Ensure stream was actually started and enabled
	if !t.config.Stream.Enable || !t.hasPrinted {
		t.logger.Warn("Close() called but streaming not enabled or not started. Ignoring Close() actions.")
		// If renderer has a Close method that should always be called, consider that.
		// For Blueprint, Close is a no-op, so returning early is fine.
		// If we always call renderer.Close(), ensure it's safe if renderer.Start() wasn't called.
		// Let's only call renderer.Close if stream was started.
		if t.hasPrinted && t.renderer != nil { // Check if renderer is not nil for safety
			t.renderer.Close() // Still call renderer's close for cleanup
		}
		t.hasPrinted = false // Reset flag
		return nil
	}

	// Render stored footer if any
	if len(t.streamFooterLines) > 0 {
		t.logger.Debug("Close(): Rendering stored footer.")
		if err := t.streamRenderFooter(t.streamFooterLines); err != nil {
			t.logger.Errorf("Close(): Failed to render stream footer: %v", err)
			// Continue to try and close renderer and render bottom border
		}
	}

	// Render the final table bottom border
	t.logger.Debug("Close(): Rendering stream bottom border.")
	if err := t.streamRenderBottomBorder(); err != nil {
		t.logger.Errorf("Close(): Failed to render stream bottom border: %v", err)
		// Continue to try and close renderer
	}

	// Call the underlying renderer's Close method
	err := t.renderer.Close()
	if err != nil {
		t.logger.Errorf("Renderer.Close() failed: %v", err)
	}

	// Reset streaming state
	t.hasPrinted = false
	t.headerRendered = false
	t.firstRowRendered = false
	t.lastRenderedLineContent = nil
	t.lastRenderedMergeState = nil
	t.lastRenderedPosition = ""
	t.streamFooterLines = nil
	// t.streamWidths should persist if we want to make multiple Start/Close calls on same config?
	// For now, let's assume Start re-evaluates. If widths are from StreamConfig, they'd be reused.
	// If derived, they'd be re-derived. Let's clear for true reset.
	t.streamWidths = tw.NewMapper[int, int]()
	t.streamNumCols = 0
	// t.streamRowCounter = 0 // Removed this field

	t.logger.Debug("Stream ended. hasPrinted = false.")
	return err // Return error from renderer.Close or other significant errors
}

// Start initializes the table stream.
// In this streaming model, renderer.Start() is primarily called in NewStreamTable.
// This method serves as a safeguard or point for adding pre-rendering logic.
// Start initializes the table stream.
// It is the entry point for streaming mode.
// Requires t.config.Stream.Enable to be true.
// Returns an error if streaming is disabled or the renderer does not support streaming,
// or if called multiple times on the same stream.
func (t *Table) Start() error {
	t.ensureInitialized() // Ensures basic setup like loggers

	if !t.config.Stream.Enable {
		// Start() should only be called when streaming is explicitly enabled.
		// Otherwise, the user should call Render() for batch mode.
		t.logger.Warn("Start() called but streaming is disabled. Call Render() instead for batch mode.")
		return errors.New("start() called but streaming is disabled")
	}

	if !t.renderer.Config().Streaming {
		// Check if the configured renderer actually supports streaming.
		t.logger.Error("Configured renderer does not support streaming.")
		return errors.Newf("renderer does not support streaming")
	}

	// t.renderer.Start(t.writer)
	// t.renderer.Logger(t.logger)

	if t.hasPrinted {
		// Prevent calling Start() multiple times on the same stream instance.
		t.logger.Warn("Start() called multiple times for the same table stream. Ignoring subsequent calls.")
		return nil
	}

	t.logger.Debug("Starting table stream.")

	// Initialize/reset streaming state flags and buffers
	t.headerRendered = false
	t.firstRowRendered = false
	t.lastRenderedLineContent = nil
	t.lastRenderedPosition = "" // Reset last rendered position
	t.streamFooterLines = nil   // Reset footer buffer
	t.streamNumCols = 0         // Reset derived column count

	// Calculate initial fixed widths if provided in StreamConfig.Widths
	// These widths will be used for all subsequent rendering in streaming mode.
	if t.config.Widths.PerColumn != nil && t.config.Widths.PerColumn.Len() > 0 {
		// Use per-column stream widths if set
		t.logger.Debugf("Using per-column stream widths from StreamConfig: %v", t.config.Widths.PerColumn)
		t.streamWidths = t.config.Widths.PerColumn.Clone()
		// Determine numCols from the highest index in PerColumn map
		maxColIdx := -1
		t.streamWidths.Each(func(col, width int) {
			if col > maxColIdx {
				maxColIdx = col
			}
			// Ensure configured widths are reasonable (>0 becomes >=1, <0 becomes 0)
			if width > 0 && width < 1 {
				t.streamWidths.Set(col, 1)
			} else if width < 0 {
				t.streamWidths.Set(col, 0) // Negative width means hide column
			}
		})
		if maxColIdx >= 0 {
			t.streamNumCols = maxColIdx + 1
			t.logger.Debugf("Derived streamNumCols from PerColumn widths: %d", t.streamNumCols)
		} else {
			// PerColumn map exists but is empty? Or all negative widths? Assume 0 columns for now.
			t.streamNumCols = 0
			t.logger.Debugf("PerColumn widths map is effectively empty or contains only negative values, streamNumCols = 0.")
		}

	} else if t.config.Widths.Global > 0 {
		// Global width is set, but we don't know the number of columns yet.
		// Defer applying global width until the first data (Header or first Row) arrives.
		// Store a placeholder or flag indicating global width should be used.
		// The simple way for now: Keep streamWidths empty, signal the global width preference.
		// The width calculation function called later will need to check StreamConfig.Widths.Global
		// if streamWidths is empty.
		t.logger.Debugf("Global stream width %d set in StreamConfig. Will derive numCols from first data.", t.config.Widths.Global)
		t.streamWidths = tw.NewMapper[int, int]() // Initialize as empty, will be populated later
		// Note: No need to store Global width value here, it's available in t.config.Stream.Widths.Global

	} else {
		// No explicit stream widths in config. They will be calculated from the first data (Header or first Row).
		t.logger.Debug("No explicit stream widths configured in StreamConfig. Will derive from first data.")
		t.streamWidths = tw.NewMapper[int, int]() // Initialize as empty, will be populated later
		t.streamNumCols = 0                       // NumCols will be determined by first data
	}

	// Log warnings if incompatible features are enabled in streaming config
	// Vertical/Hierarchical merges require processing all rows together.
	if t.config.Header.Formatting.MergeMode&(tw.MergeVertical|tw.MergeHierarchical) != 0 {
		t.logger.Warnf("Vertical or Hierarchical merge modes enabled on Header config (%d) but are unsupported in streaming mode. Only Horizontal merge will be considered.", t.config.Header.Formatting.MergeMode)
	}
	if t.config.Row.Formatting.MergeMode&(tw.MergeVertical|tw.MergeHierarchical) != 0 {
		t.logger.Warnf("Vertical or Hierarchical merge modes enabled on Row config (%d) but are unsupported in streaming mode. Only Horizontal merge will be considered.", t.config.Row.Formatting.MergeMode)
	}
	if t.config.Footer.Formatting.MergeMode&(tw.MergeVertical|tw.MergeHierarchical) != 0 {
		t.logger.Warnf("Vertical or Hierarchical merge modes enabled on Footer config (%d) but are unsupported in streaming mode. Only Horizontal merge will be considered.", t.config.Footer.Formatting.MergeMode)
	}
	// AutoHide requires processing all row data to find empty columns.
	if t.config.Behavior.AutoHide.Enabled() {
		t.logger.Warn("AutoHide is enabled in config but is ignored in streaming mode.")
	}

	// Call the renderer's start method for the stream.
	err := t.renderer.Start(t.writer)
	if err == nil {
		t.hasPrinted = true // Mark as started successfully only if renderer.Start works
		t.logger.Debug("Renderer.Start() succeeded. Table stream initiated.")
	} else {
		// Reset state if renderer.Start fails
		t.hasPrinted = false
		t.headerRendered = false
		t.firstRowRendered = false
		t.lastRenderedLineContent = nil
		t.lastRenderedPosition = ""
		t.streamFooterLines = nil
		t.streamWidths = tw.NewMapper[int, int]() // Clear any widths that might have been set
		t.streamNumCols = 0
		t.logger.Errorf("Renderer.Start() failed: %v. Streaming initialization failed.", err)
	}
	return err
}

// streamAppendRow processes and renders a single row in streaming mode.
// It calculates/uses fixed stream widths, processes content, renders separators and lines,
// and updates streaming state.
// It assumes Start() has already been called and t.hasPrinted is true.
func (t *Table) streamAppendRow(row interface{}) error {
	t.logger.Debugf("streamAppendRow called with row: %v (type: %T)", row, row)

	if !t.config.Stream.Enable {
		return errors.New("streaming mode is disabled")
	}

	rawCellsSlice, err := t.convertCellsToStrings(row, t.config.Row)
	if err != nil {
		t.logger.Errorf("streamAppendRow: Failed to convert row to strings: %v", err)
		return errors.Newf("failed to convert row to strings").Wrap(err)
	}

	if len(rawCellsSlice) == 0 {
		t.logger.Debug("streamAppendRow: No raw cells after conversion, skipping row rendering.")
		if !t.firstRowRendered {
			t.firstRowRendered = true
			t.logger.Debug("streamAppendRow: Marked first row rendered (empty content after processing).")
		}
		return nil
	}

	if err := t.ensureStreamWidthsCalculated(rawCellsSlice, t.config.Row); err != nil {
		return errors.New("failed to establish stream column count/widths").Wrap(err)
	}

	// Now, check for column mismatch if a column count has been established.
	if t.streamNumCols > 0 {
		if len(rawCellsSlice) != t.streamNumCols {
			if t.config.Stream.StrictColumns {
				err := errors.Newf("input row column count (%d) does not match established stream column count (%d) and StrictColumns is enabled", len(rawCellsSlice), t.streamNumCols)
				t.logger.Error(err.Error())
				return err
			}
			// If not strict, retain the old lenient behavior (warn and pad/truncate)
			t.logger.Warnf("streamAppendRow: Input row column count (%d) != stream column count (%d). Padding/Truncating (StrictColumns is false).", len(rawCellsSlice), t.streamNumCols)
			if len(rawCellsSlice) < t.streamNumCols {
				paddedCells := make([]string, t.streamNumCols)
				copy(paddedCells, rawCellsSlice)
				for i := len(rawCellsSlice); i < t.streamNumCols; i++ {
					paddedCells[i] = tw.Empty
				}
				rawCellsSlice = paddedCells
			} else {
				rawCellsSlice = rawCellsSlice[:t.streamNumCols]
			}
		}
	} else if len(rawCellsSlice) > 0 && t.config.Stream.StrictColumns {
		err := errors.Newf("failed to establish stream column count from first data row (%d cells) and StrictColumns is enabled", len(rawCellsSlice))
		t.logger.Error(err.Error())
		return err
	}

	if t.streamNumCols == 0 {
		t.logger.Warn("streamAppendRow: streamNumCols is 0. Cannot render row.")
		return errors.New("cannot render row, column count is zero and could not be determined")
	}

	_, rowMerges, _ := t.prepareWithMerges([][]string{rawCellsSlice}, t.config.Row, tw.Row)
	processedRowLines := t.prepareContent(rawCellsSlice, t.config.Row)
	t.logger.Debugf("streamAppendRow: Processed row lines: %d lines", len(processedRowLines))

	f := t.renderer
	cfg := t.renderer.Config()

	if !t.headerRendered && !t.firstRowRendered && t.lastRenderedPosition == "" {
		if cfg.Borders.Top.Enabled() && cfg.Settings.Lines.ShowTop.Enabled() {
			t.logger.Debug("streamAppendRow: Rendering table top border (first element is a row).")
			var nextCellsCtx map[int]tw.CellContext
			if len(processedRowLines) > 0 {
				firstRowLineResp := t.streamBuildCellContexts(
					tw.Row, 0, 0, processedRowLines, rowMerges, t.config.Row,
				)
				nextCellsCtx = firstRowLineResp.cells
			}
			f.Line(tw.Formatting{
				Row: tw.RowContext{
					Widths:       t.streamWidths,
					ColMaxWidths: tw.CellWidth{PerColumn: t.streamWidths},
					Next:         nextCellsCtx,
					Position:     tw.Row,
					Location:     tw.LocationFirst,
				},
				Level:    tw.LevelHeader,
				IsSubRow: false,

				NormalizedWidths: t.streamWidths,
			})
			t.logger.Debug("streamAppendRow: Top border rendered.")
		}
	}

	shouldDrawHeaderRowSeparator := t.headerRendered && !t.firstRowRendered && cfg.Settings.Lines.ShowHeaderLine.Enabled()
	shouldDrawRowRowSeparator := t.firstRowRendered && cfg.Settings.Separators.BetweenRows.Enabled()

	firstCellForLog := ""
	if len(rawCellsSlice) > 0 {
		firstCellForLog = rawCellsSlice[0]
	}
	t.logger.Debugf("streamAppendRow: Separator Pre-Check for row starting with '%s': headerRendered=%v, firstRowRendered=%v, ShowHeaderLine=%v, BetweenRows=%v, lastRenderedPos=%q",
		firstCellForLog, t.headerRendered, t.firstRowRendered, cfg.Settings.Lines.ShowHeaderLine.Enabled(),
		cfg.Settings.Separators.BetweenRows.Enabled(), t.lastRenderedPosition)
	t.logger.Debugf("streamAppendRow: Separator Decision Flags for row starting with '%s': shouldDrawHeaderRowSeparator=%v, shouldDrawRowRowSeparator=%v",
		firstCellForLog, shouldDrawHeaderRowSeparator, shouldDrawRowRowSeparator)

	if (shouldDrawHeaderRowSeparator || shouldDrawRowRowSeparator) && t.lastRenderedPosition != tw.Position("separator") {
		t.logger.Debugf("streamAppendRow: Rendering separator line for row starting with '%s'.", firstCellForLog)
		prevCellsCtx := t.streamRenderedMergeState(t.lastRenderedLineContent, t.lastRenderedMergeState)
		var nextCellsCtx map[int]tw.CellContext
		if len(processedRowLines) > 0 {
			firstRowLineResp := t.streamBuildCellContexts(tw.Row, 0, 0, processedRowLines, rowMerges, t.config.Row)
			nextCellsCtx = firstRowLineResp.cells
		}
		f.Line(tw.Formatting{
			Row: tw.RowContext{
				Widths:       t.streamWidths,
				ColMaxWidths: tw.CellWidth{PerColumn: t.streamWidths},
				Current:      prevCellsCtx,
				Previous:     nil,
				Next:         nextCellsCtx,
				Position:     tw.Row,
				Location:     tw.LocationMiddle,
			},
			Level:    tw.LevelBody,
			IsSubRow: false,

			NormalizedWidths: t.streamWidths,
		})
		t.lastRenderedPosition = tw.Position("separator")
		t.lastRenderedLineContent = nil
		t.lastRenderedMergeState = nil
		t.logger.Debug("streamAppendRow: Separator line rendered. Updated lastRenderedPosition to 'separator'.")
	} else {
		details := ""
		if !shouldDrawHeaderRowSeparator && !shouldDrawRowRowSeparator {
			details = "neither header/row nor row/row separator was flagged true"
		} else if t.lastRenderedPosition == tw.Position("separator") {
			details = "lastRenderedPosition is already 'separator'"
		} else {
			details = "an unexpected combination of conditions"
		}
		t.logger.Debugf("streamAppendRow: Separator not drawn for row '%s' because %s.", firstCellForLog, details)
	}

	if len(processedRowLines) == 0 {
		t.logger.Debugf("streamAppendRow: No processed row lines to render for row starting with '%s'.", firstCellForLog)
		if !t.firstRowRendered {
			t.firstRowRendered = true
			t.logger.Debugf("streamAppendRow: Marked first row rendered (empty content after processing).")
		}
		return nil
	}

	totalRowLines := len(processedRowLines)
	for i := 0; i < totalRowLines; i++ {
		resp := t.streamBuildCellContexts(tw.Row, 0, i, processedRowLines, rowMerges, t.config.Row)
		t.logger.Debug("streamAppendRow: Rendering row line %d/%d with location %v for row starting with '%s'.", i, totalRowLines, resp.location, firstCellForLog)
		f.Row(resp.cellsContent, tw.Formatting{
			Row: tw.RowContext{
				Widths:       t.streamWidths,
				ColMaxWidths: tw.CellWidth{PerColumn: t.streamWidths},
				Current:      resp.cells,
				Previous:     resp.prevCells,
				Next:         resp.nextCells,
				Position:     tw.Row,
				Location:     resp.location,
			},
			Level:    tw.LevelBody,
			IsSubRow: i > 0,

			NormalizedWidths: t.streamWidths,
			HasFooter:        len(t.streamFooterLines) > 0,
		})
		t.lastRenderedLineContent = resp.cellsContent
		t.lastRenderedMergeState = make(map[int]tw.MergeState)
		for colIdx, cellCtx := range resp.cells {
			t.lastRenderedMergeState[colIdx] = cellCtx.Merge
		}
		t.lastRenderedPosition = tw.Row
	}

	if !t.firstRowRendered {
		t.firstRowRendered = true
		t.logger.Debug("streamAppendRow: Marked first row rendered (after processing content).")
	}

	t.logger.Debug("streamAppendRow: Row processing completed for row starting with '%s'.", firstCellForLog)
	return nil
}

// streamBuildCellContexts creates CellContext objects for a given line in streaming mode.
// Parameters:
// - position: The section being processed (Header, Row, Footer).
// - rowIdx: The row index within its section (always 0 for Header/Footer, row number for Row).
// - lineIdx: The line index within the processed lines for this block.
// - processedLines: All multi-lines for the current row/header/footer block.
// - sectionMerges: Merge states for the section or row (map[int]tw.MergeState).
// - sectionConfig: The CellConfig for this section (Header, Row, Footer).
// Returns a renderMergeResponse with Current, Previous, Next cells, cellsContent, and the determined Location.
func (t *Table) streamBuildCellContexts(
	position tw.Position,
	rowIdx, lineIdx int,
	processedLines [][]string,
	sectionMerges map[int]tw.MergeState,
	sectionConfig tw.CellConfig,
) renderMergeResponse {
	t.logger.Debug("streamBuildCellContexts: Building contexts for position=%s, rowIdx=%d, lineIdx=%d", position, rowIdx, lineIdx)
	resp := renderMergeResponse{
		cells:        make(map[int]tw.CellContext),
		prevCells:    nil,
		nextCells:    nil,
		cellsContent: make([]string, t.streamNumCols),
		location:     tw.LocationMiddle,
	}

	if t.streamWidths == nil || t.streamWidths.Len() == 0 || t.streamNumCols == 0 {
		t.logger.Warn("streamBuildCellContexts: streamWidths is not set or streamNumCols is 0. Returning empty contexts.")
		return resp
	}

	currentLineContent := make([]string, t.streamNumCols)
	if lineIdx >= 0 && lineIdx < len(processedLines) {
		currentLineContent = padLine(processedLines[lineIdx], t.streamNumCols)
	} else {
		t.logger.Warnf("streamBuildCellContexts: lineIdx %d out of bounds for processedLines (len %d) at position %s, rowIdx %d. Using empty line.", lineIdx, len(processedLines), position, rowIdx)
		for j := range currentLineContent {
			currentLineContent[j] = tw.Empty
		}
	}
	resp.cellsContent = currentLineContent

	colAligns := t.buildAligns(sectionConfig)
	colPadding := t.buildPadding(sectionConfig.Padding)
	resp.cells = t.buildCoreCellContexts(currentLineContent, sectionMerges, t.streamWidths, colAligns, colPadding, t.streamNumCols)

	if t.lastRenderedLineContent != nil && t.lastRenderedPosition.Validate() == nil {
		resp.prevCells = t.streamRenderedMergeState(t.lastRenderedLineContent, t.lastRenderedMergeState)
	}

	totalLinesInBlock := len(processedLines)
	if lineIdx < totalLinesInBlock-1 {
		resp.nextCells = make(map[int]tw.CellContext)
		nextLineContent := padLine(processedLines[lineIdx+1], t.streamNumCols)
		nextCells := t.buildCoreCellContexts(nextLineContent, sectionMerges, t.streamWidths, colAligns, colPadding, t.streamNumCols)
		for j := 0; j < t.streamNumCols; j++ {
			resp.nextCells[j] = nextCells[j]
		}
	}

	isFirstLineOfBlock := (lineIdx == 0)
	if isFirstLineOfBlock && (t.lastRenderedLineContent == nil || t.lastRenderedPosition != position) {
		resp.location = tw.LocationFirst
	}

	t.logger.Debug("streamBuildCellContexts: Position %s, Row %d, Line %d/%d. Location: %v. Prev Pos: %v. Has Prev: %v.",
		position, rowIdx, lineIdx, totalLinesInBlock, resp.location, t.lastRenderedPosition, t.lastRenderedLineContent != nil)
	return resp
}

// streamCalculateWidths determines the fixed column widths for streaming mode.
// It prioritizes widths from StreamConfig.Widths.PerColumn, then StreamConfig.Widths.Global,
// then derives from the provided sample data lines.
// It populates t.streamWidths and t.streamNumCols if they are currently empty.
// The sampleDataLines should be the *raw* input lines (e.g., []string for Header/Footer, or the first row's []string cells for Row).
// The paddingConfig should be the CellPadding config relevant to the sample data (Header/Row/Footer).
// Returns the determined number of columns.
// This function should only be called when t.streamWidths is currently empty.
func (t *Table) streamCalculateWidths(sampling []string, config tw.CellConfig) int {
	if t.streamWidths != nil && t.streamWidths.Len() > 0 {
		t.logger.Debug("streamCalculateWidths: Called when streaming widths are already set (%d columns). Reusing existing.", t.streamNumCols)
		return t.streamNumCols
	}

	t.logger.Debug("streamCalculateWidths: Calculating streaming widths. Sample data cells: %d. Using section config: %+v", len(sampling), config.Formatting)

	determinedNumCols := 0
	if t.config.Widths.PerColumn != nil && t.config.Widths.PerColumn.Len() > 0 {
		maxColIdx := -1
		t.config.Widths.PerColumn.Each(func(col, width int) {
			if col > maxColIdx {
				maxColIdx = col
			}
		})
		determinedNumCols = maxColIdx + 1
		t.logger.Debug("streamCalculateWidths: Determined numCols (%d) from StreamConfig.Widths.PerColumn", determinedNumCols)
	} else if len(sampling) > 0 {
		determinedNumCols = len(sampling)
		t.logger.Debug("streamCalculateWidths: Determined numCols (%d) from sample data length", determinedNumCols)
	} else {
		t.logger.Debug("streamCalculateWidths: Cannot determine numCols (no PerColumn config, no sample data)")
		t.streamNumCols = 0
		t.streamWidths = tw.NewMapper[int, int]()
		return 0
	}

	t.streamNumCols = determinedNumCols
	t.streamWidths = tw.NewMapper[int, int]()

	// Use padding and autowrap from the provided config
	paddingForWidthCalc := config.Padding
	autoWrapForWidthCalc := config.Formatting.AutoWrap

	if t.config.Widths.PerColumn != nil && t.config.Widths.PerColumn.Len() > 0 {
		t.logger.Debug("streamCalculateWidths: Using widths from StreamConfig.Widths.PerColumn")
		for i := 0; i < t.streamNumCols; i++ {
			width, ok := t.config.Widths.PerColumn.OK(i)
			if !ok {
				width = 0
			}
			if width > 0 && width < 1 {
				width = 1
			} else if width < 0 {
				width = 0
			}
			t.streamWidths.Set(i, width)
		}
	} else {
		// No PerColumn config, derive from sampling intelligently
		t.logger.Debug("streamCalculateWidths: Intelligently deriving widths from sample data content and padding.")
		tempRequiredWidths := tw.NewMapper[int, int]() // Widths from updateWidths (content + padding)
		if len(sampling) > 0 {
			// updateWidths calculates: DisplayWidth(content) + padLeft + padRight
			t.updateWidths(sampling, tempRequiredWidths, paddingForWidthCalc)
		}

		ellipsisWidthBuffer := 0
		if autoWrapForWidthCalc == tw.WrapTruncate {
			ellipsisWidthBuffer = twwidth.Width(tw.CharEllipsis)
		}
		varianceBuffer := 2 // Your suggested variance
		minTotalColWidth := tw.MinimumColumnWidth
		// Example: if t.config.Stream.MinAutoColumnWidth > 0 { minTotalColWidth = t.config.Stream.MinAutoColumnWidth }

		for i := 0; i < t.streamNumCols; i++ {
			// baseCellWidth (content_width + padding_width) comes from tempRequiredWidths.Get(i)
			// We need to deconstruct it to apply logic to content_width first.

			sampleContent := ""
			if i < len(sampling) {
				sampleContent = t.Trimmer(sampling[i])
			}
			sampleContentDisplayWidth := twwidth.Width(sampleContent)

			colPad := paddingForWidthCalc.Global
			if i < len(paddingForWidthCalc.PerColumn) && paddingForWidthCalc.PerColumn[i].Paddable() {
				colPad = paddingForWidthCalc.PerColumn[i]
			}
			currentPadLWidth := twwidth.Width(colPad.Left)
			currentPadRWidth := twwidth.Width(colPad.Right)
			currentTotalPaddingWidth := currentPadLWidth + currentPadRWidth

			// Start with the target content width logic
			targetContentWidth := sampleContentDisplayWidth
			if autoWrapForWidthCalc == tw.WrapTruncate {
				// If content is short, ensure it's at least wide enough for an ellipsis
				if targetContentWidth < ellipsisWidthBuffer {
					targetContentWidth = ellipsisWidthBuffer
				}
			}
			targetContentWidth += varianceBuffer // Add variance

			// Now calculate the total cell width based on this buffered content target + padding
			calculatedWidth := targetContentWidth + currentTotalPaddingWidth

			// Apply an absolute minimum total column width
			if calculatedWidth > 0 && calculatedWidth < minTotalColWidth {
				t.logger.Debug("streamCalculateWidths: Col %d, InitialCalcW=%d (ContentTarget=%d + Pad=%d) is less than MinTotalW=%d. Adjusting to MinTotalW.",
					i, calculatedWidth, targetContentWidth, currentTotalPaddingWidth, minTotalColWidth)
				calculatedWidth = minTotalColWidth
			} else if calculatedWidth <= 0 && sampleContentDisplayWidth > 0 { // If content exists but calc width is 0 (e.g. large negative variance)
				// Ensure at least min width or content + padding + buffers
				fallbackWidth := sampleContentDisplayWidth + currentTotalPaddingWidth
				if autoWrapForWidthCalc == tw.WrapTruncate {
					fallbackWidth += ellipsisWidthBuffer
				}
				fallbackWidth += varianceBuffer
				calculatedWidth = tw.Max(minTotalColWidth, fallbackWidth)
				if calculatedWidth <= 0 && (currentTotalPaddingWidth+1) > 0 { // last resort if all else is zero
					calculatedWidth = currentTotalPaddingWidth + 1
				} else if calculatedWidth <= 0 {
					calculatedWidth = 1 // absolute last resort
				}

				t.logger.Debug("streamCalculateWidths: Col %d, CalculatedW was <=0 despite content. Adjusted to %d.", i, calculatedWidth)
			} else if calculatedWidth <= 0 && sampleContentDisplayWidth == 0 {
				// Column is truly empty in sample and buffers didn't make it positive, or minTotalColWidth is 0.
				// Keep width 0 (it will be hidden by renderer if all content is empty for this col)
				// Or, if we want empty columns to have a minimum presence (even if just padding):
				// calculatedWidth = currentTotalPaddingWidth // This would make it just wide enough for padding
				// For now, let truly empty sample + no min width result in 0.
				calculatedWidth = 0 // Explicitly set to 0 if it ended up non-positive and no content
			}

			t.streamWidths.Set(i, calculatedWidth)
			t.logger.Debug("streamCalculateWidths: Col %d, SampleContentW=%d, PadW=%d, EllipsisBufIfTruncate=%d, VarianceBuf=%d -> FinalTotalColW=%d",
				i, sampleContentDisplayWidth, currentTotalPaddingWidth, ellipsisWidthBuffer, varianceBuffer, calculatedWidth)
		}
	}

	// Apply Global Constraint (if t.config.Stream.Widths.Global > 0)
	if t.config.Widths.Global > 0 && t.streamNumCols > 0 {
		t.logger.Debug("streamCalculateWidths: Applying global stream width constraint %d", t.config.Widths.Global)
		currentTotalColumnWidthsSum := 0
		t.streamWidths.Each(func(_, w int) {
			currentTotalColumnWidthsSum += w
		})

		separatorWidth := 0
		if t.renderer != nil {
			rendererConfig := t.renderer.Config()
			if rendererConfig.Settings.Separators.BetweenColumns.Enabled() {
				separatorWidth = twwidth.Width(rendererConfig.Symbols.Column())
			}
		} else {
			separatorWidth = 1 // Default if renderer not available yet
		}

		totalWidthIncludingSeparators := currentTotalColumnWidthsSum
		if t.streamNumCols > 1 {
			totalWidthIncludingSeparators += (t.streamNumCols - 1) * separatorWidth
		}

		if t.config.Widths.Global < totalWidthIncludingSeparators && totalWidthIncludingSeparators > 0 { // Added check for total > 0
			t.logger.Debug("streamCalculateWidths: Total calculated width (%d incl separators) exceeds global stream width (%d). Shrinking.", totalWidthIncludingSeparators, t.config.Widths.Global)

			// Target sum for column widths only (global limit - total separator width)
			targetSumForColumnWidths := t.config.Widths.Global
			if t.streamNumCols > 1 {
				targetSumForColumnWidths -= (t.streamNumCols - 1) * separatorWidth
			}
			if targetSumForColumnWidths < t.streamNumCols && t.streamNumCols > 0 { // Ensure at least 1 per column if possible
				targetSumForColumnWidths = t.streamNumCols
			} else if targetSumForColumnWidths < 0 {
				targetSumForColumnWidths = 0
			}

			scaleFactor := float64(targetSumForColumnWidths) / float64(currentTotalColumnWidthsSum)
			if currentTotalColumnWidthsSum <= 0 {
				scaleFactor = 0
			} // Avoid division by zero or negative scale

			adjustedSum := 0
			for i := 0; i < t.streamNumCols; i++ {
				originalColWidth := t.streamWidths.Get(i)
				if originalColWidth == 0 {
					continue
				} // Don't scale hidden columns

				scaledWidth := 0
				if scaleFactor > 0 {
					scaledWidth = int(math.Round(float64(originalColWidth) * scaleFactor))
				}

				if scaledWidth < 1 && originalColWidth > 0 { // Ensure at least 1 if original had width and scaling made it too small
					scaledWidth = 1
				} else if scaledWidth < 0 { // Should not happen with math.Round on positive*positive
					scaledWidth = 0
				}
				t.streamWidths.Set(i, scaledWidth)
				adjustedSum += scaledWidth
			}

			// Distribute rounding errors to meet targetSumForColumnWidths
			remainingSpace := targetSumForColumnWidths - adjustedSum
			t.logger.Debug("streamCalculateWidths: Scaling complete. TargetSum=%d, AchievedSum=%d, RemSpace=%d", targetSumForColumnWidths, adjustedSum, remainingSpace)
			// Distribute remainingSpace (positive or negative) among non-zero width columns
			if remainingSpace != 0 && t.streamNumCols > 0 {
				colsToAdjust := []int{}
				t.streamWidths.Each(func(col, w int) {
					if w > 0 { // Only consider columns that currently have width
						colsToAdjust = append(colsToAdjust, col)
					}
				})
				if len(colsToAdjust) > 0 {
					for i := 0; i < int(math.Abs(float64(remainingSpace))); i++ {
						colIdx := colsToAdjust[i%len(colsToAdjust)]
						currentColWidth := t.streamWidths.Get(colIdx)
						if remainingSpace > 0 {
							t.streamWidths.Set(colIdx, currentColWidth+1)
						} else if remainingSpace < 0 && currentColWidth > 1 { // Don't reduce below 1
							t.streamWidths.Set(colIdx, currentColWidth-1)
						}
					}
				}
			}
			t.logger.Debug("streamCalculateWidths: Widths after scaling and distribution: %v", t.streamWidths)
		} else {
			t.logger.Debug("streamCalculateWidths: Total calculated width (%d) fits global stream width (%d). No scaling needed.", totalWidthIncludingSeparators, t.config.Widths.Global)
		}
	}

	// Final sanitization
	t.streamWidths.Each(func(col, width int) {
		if width < 0 {
			t.streamWidths.Set(col, 0)
		}
	})

	t.logger.Debug("streamCalculateWidths: Final derived stream widths after all adjustments (%d columns): %v", t.streamNumCols, t.streamWidths)
	return t.streamNumCols
}

// streamRenderBottomBorder renders the bottom border of the table in streaming mode.
// It uses the fixed streamWidths and the last rendered content to create the border context.
// It assumes Start() has been called and t.hasPrinted is true.
// Returns an error if rendering fails.
func (t *Table) streamRenderBottomBorder() error {
	if t.streamWidths == nil || t.streamWidths.Len() == 0 {
		t.logger.Debug("streamRenderBottomBorder: No stream widths available, skipping bottom border.")
		return nil
	}

	cfg := t.renderer.Config()
	if !cfg.Borders.Bottom.Enabled() || !cfg.Settings.Lines.ShowBottom.Enabled() {
		t.logger.Debug("streamRenderBottomBorder: Bottom border disabled in config, skipping.")
		return nil
	}

	// The bottom border's "Current" context is the last rendered content line
	currentCells := make(map[int]tw.CellContext)
	if t.lastRenderedLineContent != nil {
		// Use a helper to convert last rendered state to cell contexts
		currentCells = t.streamRenderedMergeState(t.lastRenderedLineContent, t.lastRenderedMergeState)
	} else {
		// No content was ever rendered, but we might still want a bottom border if a top border was drawn.
		// Create empty cell contexts.
		for i := 0; i < t.streamNumCols; i++ {
			currentCells[i] = tw.CellContext{Width: t.streamWidths.Get(i)}
		}
		t.logger.Debug("streamRenderBottomBorder: No previous content line, creating empty context for bottom border.")
	}

	f := t.renderer
	f.Line(tw.Formatting{
		Row: tw.RowContext{
			Widths:       t.streamWidths,
			ColMaxWidths: tw.CellWidth{PerColumn: t.streamWidths},
			Current:      currentCells,           // Context of the line *above* the bottom border
			Previous:     nil,                    // No line before this, relative to the border itself (or use lastRendered's previous?)
			Next:         nil,                    // No line after the bottom border
			Position:     t.lastRenderedPosition, // Position of the content above the border (Row or Footer)
			Location:     tw.LocationEnd,         // This is the absolute end
		},
		Level:    tw.LevelFooter, // Bottom border is LevelFooter
		IsSubRow: false,

		NormalizedWidths: t.streamWidths,
	})
	t.logger.Debug("streamRenderBottomBorder: Bottom border rendered.")
	return nil
}

// streamRenderFooter renders the stored footer lines in streaming mode.
// It's called by Close(). It renders the Row/Footer separator line first.
// It assumes Start() has been called and t.hasPrinted is true.
// Returns an error if rendering fails.
func (t *Table) streamRenderFooter(processedFooterLines [][]string) error {
	t.logger.Debug("streamRenderFooter: Rendering %d processed footer lines.", len(processedFooterLines))

	if t.streamWidths == nil || t.streamWidths.Len() == 0 || t.streamNumCols == 0 {
		t.logger.Warn("streamRenderFooter: No stream widths or columns defined. Cannot render footer.")
		return errors.New("cannot render stream footer without defined column widths")
	}

	if len(processedFooterLines) == 0 {
		t.logger.Debug("streamRenderFooter: No footer lines to render.")
		return nil
	}

	f := t.renderer
	cfg := t.renderer.Config()

	//  Render Row/Footer or Header/Footer Separator Line
	// This separator is drawn if ShowFooterLine is enabled AND there was content before the footer.
	// The last rendered position (t.lastRenderedPosition) should be Row or Header or "separator".
	if (t.lastRenderedPosition == tw.Row || t.lastRenderedPosition == tw.Header || t.lastRenderedPosition == tw.Position("separator")) &&
		cfg.Settings.Lines.ShowFooterLine.Enabled() {

		t.logger.Debug("streamRenderFooter: Rendering Row/Footer or Header/Footer separator line.")

		// Previous context is the last line rendered before this footer
		prevCells := t.streamRenderedMergeState(t.lastRenderedLineContent, t.lastRenderedMergeState)

		// Next context is the first line of this footer
		var nextCells map[int]tw.CellContext = nil
		if len(processedFooterLines) > 0 {
			// Need merge states for the footer section.
			// Since footer is processed once and stored, detect merges on its raw input once.
			// This requires access to the *original* raw footer strings passed to Footer().
			// For simplicity now, assume no complex horizontal merges in footer for this separator line context.
			// A better approach: streamStoreFooter should also calculate and store footerMerges.
			// For now, create nextCells without specific merge info for the separator line.
			// Or, call prepareWithMerges on the *stored processed* lines, which might be okay for simple cases.
			// Let's pass nil for sectionMerges to streamBuildCellContexts for this specific Next context.
			// It will result in default (no-merge) states.

			// For now, let's build nextCells manually for the separator line context
			nextCells = make(map[int]tw.CellContext)
			firstFooterLineContent := padLine(processedFooterLines[0], t.streamNumCols)
			// Footer merges should be calculated in streamStoreFooter and stored if needed.
			// For now, assume no merges for this 'Next' context.
			for j := 0; j < t.streamNumCols; j++ {
				nextCells[j] = tw.CellContext{Data: firstFooterLineContent[j], Width: t.streamWidths.Get(j)}
			}
		}

		separatorLevel := tw.LevelFooter // Line before footer section is LevelFooter
		separatorPosition := tw.Footer   // Positioned relative to the footer it precedes
		separatorLocation := tw.LocationMiddle

		f.Line(tw.Formatting{
			Row: tw.RowContext{
				Widths:       t.streamWidths,
				ColMaxWidths: tw.CellWidth{PerColumn: t.streamWidths},
				Current:      prevCells, // Context of line above separator
				Previous:     nil,       // No line before Current in this specific context
				Next:         nextCells, // Context of line below separator (first footer line)
				Position:     separatorPosition,
				Location:     separatorLocation,
			},
			Level:    separatorLevel,
			IsSubRow: false,

			NormalizedWidths: t.streamWidths,
		})
		t.lastRenderedPosition = tw.Position("separator") // Update state
		t.lastRenderedLineContent = nil
		t.lastRenderedMergeState = nil
		t.logger.Debug("streamRenderFooter: Footer separator line rendered.")
	}
	//  End Render Separator Line

	// Detect horizontal merges for the footer section based on its (assumed stored) raw input.
	// This is tricky because streamStoreFooter gets []string, but prepareWithMerges expects [][]string.
	// For simplicity, if complex merges are needed in footer, streamStoreFooter should
	// have received raw data, called prepareWithMerges, and stored those merges.
	// For now, assume no complex horizontal merges in footer or pass nil for sectionMerges.
	// Let's assume footerMerges were calculated and stored as `t.streamFooterMerges map[int]tw.MergeState`
	// by `streamStoreFooter`. For this example, we'll pass nil, meaning no merges.
	var footerMerges map[int]tw.MergeState = nil // Placeholder

	totalFooterLines := len(processedFooterLines)
	for i := 0; i < totalFooterLines; i++ {
		resp := t.streamBuildCellContexts(
			tw.Footer,
			0, // Row index within Footer (always 0)
			i, // Line index
			processedFooterLines,
			footerMerges, // Pass footer-specific merges if calculated and stored
			t.config.Footer,
		)

		// Special Location logic for the *very last line* of the table if this footer line is it.
		// This is complex because bottom border might follow.
		// Let streamBuildCellContexts handle LocationFirst/Middle for now.
		// streamRenderBottomBorder will handle the final LocationEnd for its line.
		// If this footer line is the last content and no bottom border, *it* should be LocationEnd.

		// If this is the last line of the last content block (footer), and no bottom border will be drawn,
		// its Location should be End.
		isLastLineOfTableContent := (i == totalFooterLines-1) &&
			(!cfg.Borders.Bottom.Enabled() || !cfg.Settings.Lines.ShowBottom.Enabled())
		if isLastLineOfTableContent {
			resp.location = tw.LocationEnd
			t.logger.Debug("streamRenderFooter: Setting LocationEnd for last footer line as no bottom border will follow.")
		}

		f.Footer([][]string{resp.cellsContent}, tw.Formatting{
			Row: tw.RowContext{
				Widths:       t.streamWidths,
				ColMaxWidths: tw.CellWidth{PerColumn: t.streamWidths},
				Current:      resp.cells,
				Previous:     resp.prevCells,
				Next:         resp.nextCells, // Next is nil if last line of footer block
				Position:     tw.Footer,
				Location:     resp.location,
			},
			Level:    tw.LevelFooter,
			IsSubRow: (i > 0),

			NormalizedWidths: t.streamWidths,
		})

		t.lastRenderedLineContent = resp.cellsContent
		t.lastRenderedMergeState = make(map[int]tw.MergeState)
		for colIdx, cellCtx := range resp.cells {
			t.lastRenderedMergeState[colIdx] = cellCtx.Merge
		}
		t.lastRenderedPosition = tw.Footer
	}

	t.logger.Debug("streamRenderFooter: Footer content rendering completed.")
	return nil
}

// streamRenderHeader processes and renders the header section in streaming mode.
// It calculates/uses fixed stream widths, processes content, renders borders/lines,
// and updates streaming state.
// It assumes Start() has already been called and t.hasPrinted is true.
func (t *Table) streamRenderHeader(headers []string) error {
	t.logger.Debug("streamRenderHeader called with headers: %v", headers)

	if !t.config.Stream.Enable {
		return errors.New("streaming mode is disabled")
	}

	if t.headerRendered {
		t.logger.Warn("streamRenderHeader called but header already rendered. Ignoring.")
		return nil
	}

	if err := t.ensureStreamWidthsCalculated(headers, t.config.Header); err != nil {
		return err
	}

	_, headerMerges, _ := t.prepareWithMerges([][]string{headers}, t.config.Header, tw.Header)
	processedHeaderLines := t.prepareContent(headers, t.config.Header)
	t.logger.Debug("streamRenderHeader: Processed header lines: %d", len(processedHeaderLines))

	if t.streamNumCols > 0 {
		t.headerRendered = true
	}
	if len(processedHeaderLines) == 0 && t.streamNumCols == 0 {
		t.logger.Debug("streamRenderHeader: No header content and no columns determined.")
		return nil
	}

	f := t.renderer
	cfg := t.renderer.Config()

	if t.lastRenderedPosition == "" && cfg.Borders.Top.Enabled() && cfg.Settings.Lines.ShowTop.Enabled() {
		t.logger.Debug("streamRenderHeader: Rendering table top border.")
		var nextCellsCtx map[int]tw.CellContext
		if len(processedHeaderLines) > 0 {
			firstHeaderLineResp := t.streamBuildCellContexts(
				tw.Header, 0, 0, processedHeaderLines, headerMerges, t.config.Header,
			)
			nextCellsCtx = firstHeaderLineResp.cells
		}
		f.Line(tw.Formatting{
			Row: tw.RowContext{
				Widths:       t.streamWidths,
				ColMaxWidths: tw.CellWidth{PerColumn: t.streamWidths},
				Next:         nextCellsCtx,
				Position:     tw.Header,
				Location:     tw.LocationFirst,
			},
			Level:    tw.LevelHeader,
			IsSubRow: false,

			NormalizedWidths: t.streamWidths,
		})
		t.logger.Debug("streamRenderHeader: Top border rendered.")
	}

	hasTopPadding := t.config.Header.Padding.Global.Top != tw.Empty
	if hasTopPadding {
		resp := t.streamBuildCellContexts(tw.Header, 0, -1, nil, headerMerges, t.config.Header)
		resp.cellsContent = t.buildPaddingLineContents(t.config.Header.Padding.Global.Top, t.streamWidths, t.streamNumCols, headerMerges)
		resp.location = tw.LocationFirst
		t.logger.Debug("streamRenderHeader: Rendering header top padding line: %v (loc: %v)", resp.cellsContent, resp.location)
		f.Header([][]string{resp.cellsContent}, tw.Formatting{
			Row: tw.RowContext{
				Widths:       t.streamWidths,
				ColMaxWidths: tw.CellWidth{PerColumn: t.streamWidths},
				Current:      resp.cells,
				Previous:     resp.prevCells,
				Next:         resp.nextCells,
				Position:     tw.Header,
				Location:     resp.location,
			},
			Level:    tw.LevelHeader,
			IsSubRow: true,

			NormalizedWidths: t.streamWidths,
		})
		t.lastRenderedLineContent = resp.cellsContent
		t.lastRenderedMergeState = make(map[int]tw.MergeState)
		for colIdx, cellCtx := range resp.cells {
			t.lastRenderedMergeState[colIdx] = cellCtx.Merge
		}
		t.lastRenderedPosition = tw.Header
	}

	totalHeaderLines := len(processedHeaderLines)
	for i := 0; i < totalHeaderLines; i++ {
		resp := t.streamBuildCellContexts(tw.Header, 0, i, processedHeaderLines, headerMerges, t.config.Header)
		t.logger.Debug("streamRenderHeader: Rendering header content line %d/%d with location %v", i, totalHeaderLines, resp.location)
		f.Header([][]string{resp.cellsContent}, tw.Formatting{
			Row: tw.RowContext{
				Widths:       t.streamWidths,
				ColMaxWidths: tw.CellWidth{PerColumn: t.streamWidths},
				Current:      resp.cells,
				Previous:     resp.prevCells,
				Next:         resp.nextCells,
				Position:     tw.Header,
				Location:     resp.location,
			},
			Level:    tw.LevelHeader,
			IsSubRow: i > 0,

			NormalizedWidths: t.streamWidths,
		})
		t.lastRenderedLineContent = resp.cellsContent
		t.lastRenderedMergeState = make(map[int]tw.MergeState)
		for colIdx, cellCtx := range resp.cells {
			t.lastRenderedMergeState[colIdx] = cellCtx.Merge
		}
		t.lastRenderedPosition = tw.Header
	}

	hasBottomPadding := t.config.Header.Padding.Global.Bottom != tw.Empty
	if hasBottomPadding {
		resp := t.streamBuildCellContexts(tw.Header, 0, totalHeaderLines, nil, headerMerges, t.config.Header)
		resp.cellsContent = t.buildPaddingLineContents(t.config.Header.Padding.Global.Bottom, t.streamWidths, t.streamNumCols, headerMerges)
		resp.location = tw.LocationEnd
		t.logger.Debug("streamRenderHeader: Rendering header bottom padding line: %v (loc: %v)", resp.cellsContent, resp.location)
		f.Header([][]string{resp.cellsContent}, tw.Formatting{
			Row: tw.RowContext{
				Widths:       t.streamWidths,
				ColMaxWidths: tw.CellWidth{PerColumn: t.streamWidths},
				Current:      resp.cells,
				Previous:     resp.prevCells,
				Next:         resp.nextCells,
				Position:     tw.Header,
				Location:     resp.location,
			},
			Level:    tw.LevelHeader,
			IsSubRow: true,

			NormalizedWidths: t.streamWidths,
		})
		t.lastRenderedLineContent = resp.cellsContent
		t.lastRenderedMergeState = make(map[int]tw.MergeState)
		for colIdx, cellCtx := range resp.cells {
			t.lastRenderedMergeState[colIdx] = cellCtx.Merge
		}
		t.lastRenderedPosition = tw.Header
	}

	if cfg.Settings.Lines.ShowHeaderLine.Enabled() && (t.firstRowRendered || len(t.streamFooterLines) > 0) {
		t.logger.Debug("streamRenderHeader: Rendering header separator line.")
		resp := t.streamBuildCellContexts(tw.Header, 0, totalHeaderLines-1, processedHeaderLines, headerMerges, t.config.Header)
		f.Line(tw.Formatting{
			Row: tw.RowContext{
				Widths:       t.streamWidths,
				ColMaxWidths: tw.CellWidth{PerColumn: t.streamWidths},
				Current:      resp.cells,
				Previous:     resp.prevCells,
				Next:         nil,
				Position:     tw.Header,
				Location:     tw.LocationMiddle,
			},
			Level:    tw.LevelBody,
			IsSubRow: false,

			NormalizedWidths: t.streamWidths,
		})
		t.lastRenderedPosition = tw.Position("separator")
		t.lastRenderedLineContent = nil
		t.lastRenderedMergeState = nil
	}

	t.logger.Debug("streamRenderHeader: Header content rendering completed.")
	return nil
}

// streamRenderedMergeState converts the stored last rendered line content
// and its merge states into a map of CellContext, suitable for providing
// context (e.g., "Current" or "Previous") to the renderer.
// It uses the fixed streamWidths.
func (t *Table) streamRenderedMergeState(
	lineContent []string,
	lineMergeStates map[int]tw.MergeState,
) map[int]tw.CellContext {
	cells := make(map[int]tw.CellContext)
	if t.streamWidths == nil || t.streamWidths.Len() == 0 || t.streamNumCols == 0 {
		t.logger.Warn("streamRenderedMergeState: streamWidths not set or streamNumCols is 0. Returning empty cell contexts.")
		return cells
	}

	// Ensure lineContent is padded to streamNumCols if it's not nil
	var paddedLineContent []string
	if lineContent != nil {
		paddedLineContent = padLine(lineContent, t.streamNumCols)
	} else {
		// If lineContent is nil (e.g. after a separator), create an empty padded line
		paddedLineContent = make([]string, t.streamNumCols)
		for i := range paddedLineContent {
			paddedLineContent[i] = tw.Empty
		}
	}

	for j := 0; j < t.streamNumCols; j++ {
		cellData := paddedLineContent[j]
		colWidth := t.streamWidths.Get(j)
		mergeState := tw.MergeState{} // Default to no merge

		if lineMergeStates != nil {
			if state, ok := lineMergeStates[j]; ok {
				mergeState = state
			}
		}

		// For context purposes (like Previous or Current for a border line),
		// Align and Padding are often less critical than Data, Width, and Merge.
		// We can use default/empty Align and Padding here.
		cells[j] = tw.CellContext{
			Data:    cellData,
			Align:   tw.AlignDefault, // Or tw.AlignNone if preferred for context-only cells
			Padding: tw.Padding{},    // Empty padding
			Width:   colWidth,
			Merge:   mergeState,
		}
	}
	return cells
}

// streamStoreFooter processes the footer content and stores it for later rendering by Close()
// in streaming mode. It ensures stream widths are calculated if not already set.
func (t *Table) streamStoreFooter(footers []string) error {
	t.logger.Debug("streamStoreFooter called with footers: %v", footers)

	if !t.config.Stream.Enable {
		return errors.New("streaming mode is disabled")
	}

	if len(footers) == 0 {
		t.logger.Debug("streamStoreFooter: Empty footer cells, storing empty footer lines.")
		t.streamFooterLines = [][]string{}
		return nil
	}

	if err := t.ensureStreamWidthsCalculated(footers, t.config.Footer); err != nil {
		t.logger.Warnf("streamStoreFooter: Failed to determine column count from footer data: %v", err)
		t.streamFooterLines = [][]string{}
		return nil
	}

	if t.streamNumCols > 0 && len(footers) != t.streamNumCols {
		t.logger.Warnf("streamStoreFooter: Input footer column count (%d) does not match fixed stream column count (%d). Padding/Truncating input footers.", len(footers), t.streamNumCols)
		if len(footers) < t.streamNumCols {
			paddedFooters := make([]string, t.streamNumCols)
			copy(paddedFooters, footers)
			for i := len(footers); i < t.streamNumCols; i++ {
				paddedFooters[i] = tw.Empty
			}
			footers = paddedFooters
		} else {
			footers = footers[:t.streamNumCols]
		}
	}

	if t.streamNumCols == 0 {
		t.logger.Warn("streamStoreFooter: streamNumCols is 0, cannot process/store footer lines meaningfully.")
		t.streamFooterLines = [][]string{}
		return nil
	}

	t.streamFooterLines = t.prepareContent(footers, t.config.Footer)
	t.logger.Debug("streamStoreFooter: Processed and stored footer lines: %d lines. Content: %v", len(t.streamFooterLines), t.streamFooterLines)

	return nil
}