/**
 * FormationOverlay - SVG-based overlay for drawing and rendering chart formations
 *
 * Uses SVG layer positioned over the chart container.
 * Syncs with chart via requestAnimationFrame polling (not event subscriptions).
 *
 * Drawing UX for Lines:
 * - Single click places a solid horizontal line with 3 anchors
 * - Center anchor: drag to move line without changing angle
 * - End anchors: drag to pivot from opposite end (change angle)
 * - Hover over any part of line shows all anchors
 *
 * Drawing UX for Channels:
 * - First click places primary line (3 anchors like single line)
 * - Mouse move shows dotted parallel preview line
 * - Second click places parallel line (1 center anchor)
 * - Primary line anchors control both lines (angle/position)
 * - Secondary line's center anchor moves it parallel to primary
 */
class FormationOverlay {
    constructor(chartContainerId, chart, candleSeries) {
        this.chartContainerId = chartContainerId;
        this.chart = chart;
        this.candleSeries = candleSeries;

        // SVG layer reference
        this.svg = null;
        this.container = null;

        // Drawing state
        this.drawingMode = null;  // 'support_resistance', 'channel'
        this.currentPoints = [];
        this.tempLine = null;

        // Draft formation (created on first click, not yet saved)
        this.draftFormation = null;
        this.draftTblKey = '__draft__';

        // Channel drawing state
        this.channelPrimaryLine = null;  // First line placed
        this.channelPreviewLine = null;  // Dotted preview of second line
        this.channelStep = 0;  // 0 = waiting for first click, 1 = waiting for second click

        // Rendered formations: Map<tbl_key, {formation, elements: SVGElement[], lineGroup: SVGGElement}>
        this.renderedFormations = new Map();

        // Selected formation for editing
        this.selectedTblKey = null;

        // Dragging state
        this.isDragging = false;
        this.dragAnchor = null;
        this.dragFormation = null;
        this.dragAnchorType = null;  // 'center', 'point1', 'point2', 'channel_offset'

        // RAF loop state
        this._loopRunning = false;
        this._animationFrameId = null;
        this._lastTimeRange = null;

        // Callback for saving formations
        this.onSaveCallback = null;

        // Callback for when points change during drawing (for UI updates)
        this.onPointsChangedCallback = null;

        // Callback for when draft is ready to save (name input can appear)
        this.onDraftReadyCallback = null;

        // Track points needed for current drawing
        this._pointsNeeded = 0;

        // Default line width in time units (seconds) - line extends this far on each side
        this._defaultLineHalfWidth = 3600 * 4; // 4 hours on each side

        // Colors
        this.defaultColor = '#667eea';
        this.selectedColor = '#ff9500';
        this.anchorColor = '#ffffff';
        this.channelSecondaryColor = '#48bb78';  // Green for secondary line

        // Initialize
        this._createSVGLayer();
        this._startSyncLoop();
        this._attachEventListeners();
    }

    /**
     * Set callback for when a formation is ready to save.
     * @param {Function} callback - Called with formation data
     */
    setOnSaveCallback(callback) {
        this.onSaveCallback = callback;
    }

    /**
     * Set callback for when points change during drawing.
     * @param {Function} callback - Called with (currentPoints, pointsNeeded)
     */
    setOnPointsChangedCallback(callback) {
        this.onPointsChangedCallback = callback;
    }

    /**
     * Set callback for when draft formation is ready to save.
     * @param {Function} callback - Called when user can enter name and save
     */
    setOnDraftReadyCallback(callback) {
        this.onDraftReadyCallback = callback;
    }

    /**
     * Create the SVG overlay layer.
     */
    _createSVGLayer() {
        this.container = document.getElementById(this.chartContainerId);
        if (!this.container) {
            console.error(`FormationOverlay: Container "${this.chartContainerId}" not found`);
            return;
        }

        // Ensure container has relative positioning
        const containerStyle = window.getComputedStyle(this.container);
        if (containerStyle.position === 'static') {
            this.container.style.position = 'relative';
        }

        // Create SVG element
        this.svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        this.svg.setAttribute('class', 'formation-overlay');
        this.svg.style.cssText = `
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            pointer-events: none;
            z-index: 100;
        `;

        // Create groups for layering
        this.linesGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
        this.linesGroup.setAttribute('class', 'formation-lines');

        this.anchorsGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
        this.anchorsGroup.setAttribute('class', 'formation-anchors');
        this.anchorsGroup.style.pointerEvents = 'all';

        this.tempGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
        this.tempGroup.setAttribute('class', 'formation-temp');

        this.svg.appendChild(this.linesGroup);
        this.svg.appendChild(this.anchorsGroup);
        this.svg.appendChild(this.tempGroup);

        this.container.appendChild(this.svg);
    }

    /**
     * Start the requestAnimationFrame sync loop.
     * Uses guards to prevent multiple loops.
     */
    _startSyncLoop() {
        if (this._loopRunning) return;
        this._loopRunning = true;

        const sync = () => {
            if (!this._loopRunning) return;

            // Always update formations on every frame for smooth sync with chart.
            // The update is lightweight - just updating SVG element positions.
            if (this.renderedFormations.size > 0 || this.drawingMode) {
                this._updateAllFormations();
            }

            this._animationFrameId = requestAnimationFrame(sync);
        };

        this._animationFrameId = requestAnimationFrame(sync);
    }

    /**
     * Check if the chart view (time/price range) has changed.
     * Note: Currently unused - we update on every RAF frame for smooth sync.
     * Kept for potential future optimization.
     * @returns {boolean}
     */
    _hasViewChanged() {
        if (!this.chart) return false;

        try {
            const timeScale = this.chart.timeScale();
            const timeRange = timeScale.getVisibleLogicalRange();
            if (!timeRange) return false;

            const nextRange = {
                from: Number(timeRange.from),
                to: Number(timeRange.to)
            };

            const changed = !this._lastTimeRange
                || nextRange.from !== this._lastTimeRange.from
                || nextRange.to !== this._lastTimeRange.to;

            if (changed) {
                this._lastTimeRange = nextRange;
                return true;
            }
        } catch (e) {
            return true;
        }

        return false;
    }

    /**
     * Attach mouse event listeners for drawing and dragging.
     */
    _attachEventListeners() {
        if (!this.container) return;

        // Click handler for drawing
        this.container.addEventListener('click', (e) => {
            if (this.drawingMode) {
                const local = this._eventToLocalCoords(e);
                const coords = local ? this._pixelToChart(local.x, local.y) : null;
                if (coords) {
                    this._handleDrawingClick(coords);
                }
            }
        });

        // Mouse move for temp line preview
        this.container.addEventListener('mousemove', (e) => {
            const local = this._eventToLocalCoords(e);
            const coords = local ? this._pixelToChart(local.x, local.y) : null;

            // Channel preview (parallel line following mouse)
            if (this.drawingMode === 'channel' && this.channelStep === 1 && coords) {
                this._updateChannelPreview(coords);
            }
            // Generic temp line preview
            else if (this.drawingMode && this.currentPoints.length > 0 && coords) {
                this._updateTempLine(coords);
            }

            // Handle dragging
            if (this.isDragging && this.dragAnchor && this.dragFormation && coords) {
                this._handleDrag(coords);
            }
        });

        // Mouse up to end dragging
        this.container.addEventListener('mouseup', () => {
            if (this.isDragging) {
                this._endDrag();
            }
        });

        // Mouse leave to cancel dragging
        this.container.addEventListener('mouseleave', () => {
            if (this.isDragging) {
                this._endDrag();
            }
        });

        // Escape key to cancel drawing
        document.addEventListener('keydown', (e) => {
            if (e.key === 'Escape' && this.drawingMode) {
                this.cancelDrawing();
            }
        });
    }

    /**
     * Convert a mouse event to coordinates relative to the chart container.
     * Uses client coordinates to avoid target-relative offset drift.
     * @param {MouseEvent} event
     * @returns {{x:number, y:number}|null}
     */
    _eventToLocalCoords(event) {
        if (!this.container || typeof event.clientX !== 'number' || typeof event.clientY !== 'number') {
            return null;
        }

        const rect = this.container.getBoundingClientRect();
        return {
            x: event.clientX - rect.left,
            y: event.clientY - rect.top
        };
    }

    /**
     * Convert pixel coordinates to chart time/price.
     * @param {number} x - Pixel X
     * @param {number} y - Pixel Y
     * @returns {{time: number, price: number}|null}
     */
    _pixelToChart(x, y) {
        if (!this.chart || !this.candleSeries) return null;

        try {
            const timeScale = this.chart.timeScale();
            const time = timeScale.coordinateToTime(x);
            const price = this.candleSeries.coordinateToPrice(y);

            if (time === null || price === null) return null;

            return { time: Math.floor(time), price };
        } catch (e) {
            console.warn('FormationOverlay: pixel to chart conversion failed', e);
            return null;
        }
    }

    /**
     * Convert chart time/price to pixel coordinates.
     * @param {number} time - Unix timestamp
     * @param {number} price - Price value
     * @returns {{x: number, y: number}|null}
     */
    _chartToPixel(time, price) {
        if (!this.chart || !this.candleSeries) return null;

        try {
            const timeScale = this.chart.timeScale();
            const x = timeScale.timeToCoordinate(time);
            const y = this.candleSeries.priceToCoordinate(price);

            if (x === null || y === null) return null;

            return { x, y };
        } catch (e) {
            console.warn('FormationOverlay: chart to pixel conversion failed', e);
            return null;
        }
    }

    /**
     * Get pixel position for an anchor point, handling cases where the anchor
     * is outside the visible time range.
     * @param {Object} anchorPoint - {time, price} - the anchor to position
     * @param {Object} linePoint1 - {time, price} - first point defining the line
     * @param {Object} linePoint2 - {time, price} - second point defining the line
     * @returns {{x: number, y: number}|null}
     */
    _getAnchorPixelPosition(anchorPoint, linePoint1, linePoint2) {
        // Try direct conversion first
        const directPixel = this._chartToPixel(anchorPoint.time, anchorPoint.price);
        if (directPixel) {
            return directPixel;
        }

        // Anchor is outside visible time range - calculate position along the line
        if (!this.chart || !this.candleSeries) return null;

        try {
            const timeScale = this.chart.timeScale();
            const visibleRange = timeScale.getVisibleRange();
            if (!visibleRange) return null;

            const leftTime = visibleRange.from;
            const rightTime = visibleRange.to;

            // Calculate line slope
            const dt = linePoint2.time - linePoint1.time;
            const dp = linePoint2.price - linePoint1.price;

            if (Math.abs(dt) < 1) {
                // Near-vertical line in time - use center of viewport for x
                const midTime = (leftTime + rightTime) / 2;
                return this._chartToPixel(midTime, anchorPoint.price);
            }

            const slope = dp / dt;

            // Determine if anchor is to the left or right of visible range
            if (anchorPoint.time < leftTime) {
                // Anchor is to the left - position at left edge of viewport
                const priceAtLeft = slope * (leftTime - linePoint1.time) + linePoint1.price;
                return this._chartToPixel(leftTime, priceAtLeft);
            } else if (anchorPoint.time > rightTime) {
                // Anchor is to the right - position at right edge of viewport
                const priceAtRight = slope * (rightTime - linePoint1.time) + linePoint1.price;
                return this._chartToPixel(rightTime, priceAtRight);
            }

            // Anchor time is in range but price conversion failed - shouldn't happen
            // but fall back to calculating price at anchor time
            const priceAtAnchor = slope * (anchorPoint.time - linePoint1.time) + linePoint1.price;
            return this._chartToPixel(anchorPoint.time, priceAtAnchor);
        } catch (e) {
            console.warn('FormationOverlay: anchor position calculation failed', e);
            return null;
        }
    }

    /**
     * Calculate infinite line endpoints that extend to viewport edges.
     * Uses a robust approach that works even when anchor points are outside visible range.
     * @param {Object} point1 - {time, price}
     * @param {Object} point2 - {time, price}
     * @returns {{start: {x, y}, end: {x, y}}|null}
     */
    _getInfiniteLineEndpoints(point1, point2) {
        if (!this.svg || !this.chart || !this.candleSeries) return null;

        const width = this.svg.clientWidth || this.container.clientWidth;
        const height = this.svg.clientHeight || this.container.clientHeight;

        // Try direct conversion first (works when both points are in visible range)
        let p1 = this._chartToPixel(point1.time, point1.price);
        let p2 = this._chartToPixel(point2.time, point2.price);

        // If direct conversion fails, clamp times to visible range and calculate prices
        if (!p1 || !p2) {
            const timeScale = this.chart.timeScale();
            const visibleRange = timeScale.getVisibleRange();
            if (!visibleRange) return null;

            const leftTime = visibleRange.from;
            const rightTime = visibleRange.to;

            // Calculate slope (price per time unit)
            const dt = point2.time - point1.time;
            const dp = point2.price - point1.price;

            if (Math.abs(dt) < 1) {
                // Near-vertical line - use midpoint time
                const midTime = (leftTime + rightTime) / 2;
                p1 = this._chartToPixel(midTime, point1.price);
                p2 = this._chartToPixel(midTime, point2.price);
            } else {
                const slope = dp / dt;

                // Get two points within visible range using the line equation
                const priceAtLeft = slope * (leftTime - point1.time) + point1.price;
                const priceAtRight = slope * (rightTime - point1.time) + point1.price;

                p1 = this._chartToPixel(leftTime, priceAtLeft);
                p2 = this._chartToPixel(rightTime, priceAtRight);
            }

            if (!p1 || !p2) return null;
        }

        // Now we have two valid pixel points - extend to viewport edges
        const dx = p2.x - p1.x;
        const dy = p2.y - p1.y;

        // Handle vertical line
        if (Math.abs(dx) < 0.001) {
            return {
                start: { x: p1.x, y: 0 },
                end: { x: p1.x, y: height }
            };
        }

        // Handle horizontal line
        if (Math.abs(dy) < 0.001) {
            return {
                start: { x: 0, y: p1.y },
                end: { x: width, y: p1.y }
            };
        }

        // Calculate slope in pixel space
        const slope = dy / dx;
        const b = p1.y - slope * p1.x;  // y-intercept

        // Find intersections with viewport edges
        const intersections = [];

        // Left edge (x = 0)
        const yLeft = b;
        if (yLeft >= 0 && yLeft <= height) {
            intersections.push({ x: 0, y: yLeft });
        }

        // Right edge (x = width)
        const yRight = slope * width + b;
        if (yRight >= 0 && yRight <= height) {
            intersections.push({ x: width, y: yRight });
        }

        // Top edge (y = 0)
        const xTop = -b / slope;
        if (xTop >= 0 && xTop <= width) {
            intersections.push({ x: xTop, y: 0 });
        }

        // Bottom edge (y = height)
        const xBottom = (height - b) / slope;
        if (xBottom >= 0 && xBottom <= width) {
            intersections.push({ x: xBottom, y: height });
        }

        // We need exactly 2 intersections for a line crossing the viewport
        if (intersections.length < 2) {
            // Fallback: use the calculated points
            return { start: p1, end: p2 };
        }

        // Sort by x to get consistent start/end
        intersections.sort((a, b) => a.x - b.x);

        return {
            start: intersections[0],
            end: intersections[intersections.length - 1]
        };
    }

    // ================ Drawing Methods ================

    /**
     * Start drawing a formation.
     * @param {string} type - Formation type ('support_resistance', 'channel')
     */
    startDrawing(type) {
        this.drawingMode = type;
        this.currentPoints = [];
        this.draftFormation = null;
        this._pointsNeeded = this._getPointsNeeded(type);
        this._clearTempElements();

        // Change cursor
        if (this.container) {
            this.container.style.cursor = 'crosshair';
        }

        // Enable pointer events on SVG for clicks
        if (this.svg) {
            this.svg.style.pointerEvents = 'all';
        }

        // Notify of initial state (for support_resistance, we say 1 point needed since single click creates line)
        const displayPoints = (type === 'support_resistance') ? 1 : this._pointsNeeded;
        if (this.onPointsChangedCallback) {
            this.onPointsChangedCallback(0, displayPoints);
        }

        console.log('FormationOverlay: Started drawing', type);
    }

    /**
     * Handle click during drawing mode.
     * @param {Object} coords - {time, price}
     */
    _handleDrawingClick(coords) {
        if (this.drawingMode === 'support_resistance') {
            this._handleLineDrawingClick(coords);
        } else if (this.drawingMode === 'channel') {
            this._handleChannelDrawingClick(coords);
        } else {
            this._handleGenericDrawingClick(coords);
        }
    }

    /**
     * Handle click for line (support_resistance) drawing.
     * Single click creates a horizontal line with 3 anchors.
     * @param {Object} coords - {time, price}
     */
    _handleLineDrawingClick(coords) {
        // If we already have a draft, ignore further clicks (user can drag anchors)
        if (this.draftFormation) {
            return;
        }

        // Create a horizontal line centered at click point
        const centerTime = coords.time;
        const price = coords.price;

        // Create line endpoints on each side of center
        const point1 = { time: centerTime - this._defaultLineHalfWidth, price: price };
        const point2 = { time: centerTime + this._defaultLineHalfWidth, price: price };

        // Create draft formation
        this.draftFormation = {
            tbl_key: this.draftTblKey,
            formation_type: 'support_resistance',
            color: this.defaultColor,
            lines_json: JSON.stringify({
                lines: [{
                    point1: point1,
                    point2: point2,
                    center: { time: centerTime, price: price }
                }]
            })
        };

        // Render the draft formation
        this.renderFormation(this.draftFormation);

        // Change cursor back since line is placed
        if (this.container) {
            this.container.style.cursor = 'default';
        }

        // Notify that draft is ready (for name input)
        if (this.onPointsChangedCallback) {
            this.onPointsChangedCallback(1, 1);
        }
        if (this.onDraftReadyCallback) {
            this.onDraftReadyCallback();
        }

        console.log('FormationOverlay: Draft line created at', coords);
    }

    /**
     * Handle click for channel drawing.
     * Step 1: First click creates primary line (like single line)
     * Step 2: Second click places parallel line at mouse position
     * @param {Object} coords - {time, price}
     */
    _handleChannelDrawingClick(coords) {
        if (this.channelStep === 0) {
            // STEP 1: Create primary line (horizontal at click position)
            const centerTime = coords.time;
            const price = coords.price;

            // Create primary line endpoints
            const point1 = { time: centerTime - this._defaultLineHalfWidth, price: price };
            const point2 = { time: centerTime + this._defaultLineHalfWidth, price: price };

            this.channelPrimaryLine = {
                point1: point1,
                point2: point2,
                center: { time: centerTime, price: price }
            };

            // Create draft formation with just the primary line
            this.draftFormation = {
                tbl_key: this.draftTblKey,
                formation_type: 'channel',
                color: this.defaultColor,
                lines_json: JSON.stringify({
                    lines: [this.channelPrimaryLine]
                })
            };

            // Render the primary line
            this.renderFormation(this.draftFormation);

            this.channelStep = 1;

            // Notify UI of progress
            if (this.onPointsChangedCallback) {
                this.onPointsChangedCallback(1, 2);
            }

            console.log('FormationOverlay: Channel primary line created at', coords);

        } else if (this.channelStep === 1) {
            // STEP 2: Place parallel line at current mouse position
            const priceOffset = coords.price - this.channelPrimaryLine.center.price;

            // Create secondary line parallel to primary
            const secondaryLine = {
                point1: {
                    time: this.channelPrimaryLine.point1.time,
                    price: this.channelPrimaryLine.point1.price + priceOffset
                },
                point2: {
                    time: this.channelPrimaryLine.point2.time,
                    price: this.channelPrimaryLine.point2.price + priceOffset
                },
                center: {
                    time: this.channelPrimaryLine.center.time,
                    price: this.channelPrimaryLine.center.price + priceOffset
                },
                isSecondary: true,  // Flag to identify as secondary line
                priceOffset: priceOffset  // Store offset for parallel constraint
            };

            // Update draft formation with both lines
            this.draftFormation = {
                tbl_key: this.draftTblKey,
                formation_type: 'channel',
                color: this.defaultColor,
                lines_json: JSON.stringify({
                    lines: [this.channelPrimaryLine, secondaryLine]
                })
            };

            // Remove preview line
            this._clearChannelPreview();

            // Render complete channel
            this.renderFormation(this.draftFormation);

            this.channelStep = 2;

            // Change cursor back
            if (this.container) {
                this.container.style.cursor = 'default';
            }

            // Notify that draft is ready
            if (this.onPointsChangedCallback) {
                this.onPointsChangedCallback(2, 2);
            }
            if (this.onDraftReadyCallback) {
                this.onDraftReadyCallback();
            }

            console.log('FormationOverlay: Channel complete with offset', priceOffset);
        }
    }

    /**
     * Update the parallel preview line for channel drawing.
     * @param {Object} coords - Current mouse position {time, price}
     */
    _updateChannelPreview(coords) {
        if (this.drawingMode !== 'channel' || this.channelStep !== 1 || !this.channelPrimaryLine) {
            return;
        }

        // Remove existing preview
        this._clearChannelPreview();

        // Calculate price offset from primary line
        const priceOffset = coords.price - this.channelPrimaryLine.center.price;

        // Create parallel line preview
        const previewPoint1 = {
            time: this.channelPrimaryLine.point1.time,
            price: this.channelPrimaryLine.point1.price + priceOffset
        };
        const previewPoint2 = {
            time: this.channelPrimaryLine.point2.time,
            price: this.channelPrimaryLine.point2.price + priceOffset
        };

        // Get infinite line endpoints
        const endpoints = this._getInfiniteLineEndpoints(previewPoint1, previewPoint2);
        if (!endpoints) return;

        // Create dotted preview line
        this.channelPreviewLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
        this.channelPreviewLine.setAttribute('x1', endpoints.start.x);
        this.channelPreviewLine.setAttribute('y1', endpoints.start.y);
        this.channelPreviewLine.setAttribute('x2', endpoints.end.x);
        this.channelPreviewLine.setAttribute('y2', endpoints.end.y);
        this.channelPreviewLine.setAttribute('stroke', this.channelSecondaryColor);
        this.channelPreviewLine.setAttribute('stroke-width', 2);
        this.channelPreviewLine.setAttribute('stroke-dasharray', '8,4');
        this.channelPreviewLine.style.opacity = '0.7';

        this.tempGroup.appendChild(this.channelPreviewLine);
    }

    /**
     * Clear the channel preview line.
     */
    _clearChannelPreview() {
        if (this.channelPreviewLine) {
            this.channelPreviewLine.remove();
            this.channelPreviewLine = null;
        }
    }

    /**
     * Handle click for generic multi-point drawing (original approach).
     * @param {Object} coords - {time, price}
     */
    _handleGenericDrawingClick(coords) {
        // Don't accept more points than needed
        if (this.currentPoints.length >= this._pointsNeeded) {
            console.log('FormationOverlay: Already have enough points');
            return;
        }

        this.currentPoints.push(coords);

        // Draw anchor at click point
        this._drawTempAnchor(coords);

        // Notify of points change
        if (this.onPointsChangedCallback) {
            this.onPointsChangedCallback(this.currentPoints.length, this._pointsNeeded);
        }

        // Check if drawing is complete
        if (this.currentPoints.length >= this._pointsNeeded) {
            // Drawing complete, change cursor back
            if (this.container) {
                this.container.style.cursor = 'default';
            }
            console.log('FormationOverlay: Points collected', this.currentPoints);
        }
    }

    /**
     * Get number of points needed for a formation type.
     * @param {string} type - Formation type
     * @returns {number}
     */
    _getPointsNeeded(type) {
        const pointsMap = {
            'support_resistance': 2,
            'channel': 3,
            'triangle': 3,
            'head_shoulders': 5,
            'double_bottom': 3,
            'double_top': 3
        };
        return pointsMap[type] || 2;
    }

    /**
     * Draw a temporary anchor circle.
     * @param {Object} coords - {time, price}
     */
    _drawTempAnchor(coords) {
        const pixel = this._chartToPixel(coords.time, coords.price);
        if (!pixel) return;

        const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
        circle.setAttribute('cx', pixel.x);
        circle.setAttribute('cy', pixel.y);
        circle.setAttribute('r', 6);
        circle.setAttribute('fill', this.defaultColor);
        circle.setAttribute('stroke', this.anchorColor);
        circle.setAttribute('stroke-width', 2);

        this.tempGroup.appendChild(circle);
    }

    /**
     * Update the temporary preview line.
     * @param {Object} coords - Current mouse position {time, price}
     */
    _updateTempLine(coords) {
        // Remove existing temp line
        if (this.tempLine) {
            this.tempLine.remove();
            this.tempLine = null;
        }

        if (this.currentPoints.length === 0) return;

        const lastPoint = this.currentPoints[this.currentPoints.length - 1];
        const endpoints = this._getInfiniteLineEndpoints(lastPoint, coords);

        if (!endpoints) return;

        this.tempLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
        this.tempLine.setAttribute('x1', endpoints.start.x);
        this.tempLine.setAttribute('y1', endpoints.start.y);
        this.tempLine.setAttribute('x2', endpoints.end.x);
        this.tempLine.setAttribute('y2', endpoints.end.y);
        this.tempLine.setAttribute('stroke', this.defaultColor);
        this.tempLine.setAttribute('stroke-width', 2);
        this.tempLine.setAttribute('stroke-dasharray', '5,5');
        this.tempLine.style.opacity = '0.7';

        this.tempGroup.appendChild(this.tempLine);
    }

    /**
     * Clear temporary drawing elements.
     */
    _clearTempElements() {
        while (this.tempGroup.firstChild) {
            this.tempGroup.removeChild(this.tempGroup.firstChild);
        }
        this.tempLine = null;
    }

    /**
     * Complete the current drawing and save.
     * @param {string} name - Formation name
     */
    completeDrawing(name) {
        let formationData;

        if (this.draftFormation) {
            // Use the draft formation (for line drawing with 3-anchor UX)
            formationData = {
                name: name,
                formation_type: this.draftFormation.formation_type,
                lines_json: this.draftFormation.lines_json,
                color: this.draftFormation.color
            };

            // Remove the draft from rendered formations
            this.removeFormation(this.draftTblKey);
        } else if (this.drawingMode && this.currentPoints.length >= 2) {
            // Original multi-click approach (for channels, etc.)
            const lines = this._buildLinesFromPoints(this.drawingMode, this.currentPoints);
            formationData = {
                name: name,
                formation_type: this.drawingMode,
                lines_json: JSON.stringify({ lines: lines }),
                color: this.defaultColor
            };
        } else {
            console.warn('FormationOverlay: Not enough points to complete drawing');
            return;
        }

        // Call save callback
        if (this.onSaveCallback) {
            this.onSaveCallback(formationData);
        }

        // Reset drawing state
        this._exitDrawingMode();
    }

    /**
     * Build lines array from clicked points.
     * @param {string} type - Formation type
     * @param {Array} points - Array of {time, price}
     * @returns {Array} Lines array
     */
    _buildLinesFromPoints(type, points) {
        const lines = [];

        if (type === 'support_resistance' && points.length >= 2) {
            // Single line from two points
            lines.push({
                point1: { time: points[0].time, price: points[0].price },
                point2: { time: points[1].time, price: points[1].price }
            });
        } else if (type === 'channel' && points.length >= 3) {
            // First line from points 0-1
            lines.push({
                point1: { time: points[0].time, price: points[0].price },
                point2: { time: points[1].time, price: points[1].price }
            });
            // Second parallel line: same time span, offset by third point's price difference
            const priceOffset = points[2].price - points[0].price;
            lines.push({
                point1: { time: points[0].time, price: points[0].price + priceOffset },
                point2: { time: points[1].time, price: points[1].price + priceOffset }
            });
        }

        return lines;
    }

    /**
     * Cancel the current drawing.
     */
    cancelDrawing() {
        // Remove draft formation if exists
        if (this.draftFormation) {
            this.removeFormation(this.draftTblKey);
        }
        // Clear channel preview
        this._clearChannelPreview();
        this._exitDrawingMode();
    }

    /**
     * Exit drawing mode and reset state.
     */
    _exitDrawingMode() {
        this.drawingMode = null;
        this.currentPoints = [];
        this._pointsNeeded = 0;
        this.draftFormation = null;
        this.onPointsChangedCallback = null;
        this.onDraftReadyCallback = null;

        // Reset channel state
        this.channelPrimaryLine = null;
        this.channelStep = 0;
        this._clearChannelPreview();
        this._clearTempElements();

        // Reset cursor
        if (this.container) {
            this.container.style.cursor = '';
        }

        // Disable pointer events on SVG
        if (this.svg) {
            this.svg.style.pointerEvents = 'none';
        }
    }

    // ================ Rendering Methods ================

    /**
     * Render a formation on the chart.
     * @param {Object} formation - Formation object with tbl_key, lines_json, color, etc.
     */
    renderFormation(formation) {
        if (!formation || !formation.tbl_key) return;

        // Remove existing if updating
        this.removeFormation(formation.tbl_key);

        const linesData = JSON.parse(formation.lines_json || '{}');
        const lines = linesData.lines || [];
        const color = formation.color || this.defaultColor;
        const elements = [];

        // Create a group for this formation (for hover behavior)
        const formationGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
        formationGroup.setAttribute('class', 'formation-group');
        formationGroup.setAttribute('data-tbl-key', formation.tbl_key);
        formationGroup.style.pointerEvents = 'all';

        const isChannel = formation.formation_type === 'channel';

        lines.forEach((line, index) => {
            // Determine line color (secondary channel line uses different color)
            const lineColor = (isChannel && line.isSecondary) ? this.channelSecondaryColor : color;

            // Draw the infinite line with a wider invisible hit area
            const lineHitArea = this._createLineHitArea(line.point1, line.point2, formation.tbl_key, index);
            const lineEl = this._createLine(line.point1, line.point2, lineColor, formation.tbl_key, index);

            if (lineHitArea) {
                formationGroup.appendChild(lineHitArea);
                elements.push(lineHitArea);
            }
            if (lineEl) {
                formationGroup.appendChild(lineEl);
                elements.push(lineEl);
            }

            // Calculate center point (use stored center or calculate midpoint)
            const centerPoint = line.center || {
                time: Math.floor((line.point1.time + line.point2.time) / 2),
                price: (line.point1.price + line.point2.price) / 2
            };

            // For channel secondary line: only 1 center anchor (for offset adjustment)
            if (isChannel && line.isSecondary) {
                const anchorOffset = this._createAnchor(centerPoint, formation.tbl_key, index, 'channel_offset', line.point1, line.point2);
                if (anchorOffset) {
                    formationGroup.appendChild(anchorOffset);
                    elements.push(anchorOffset);
                }
            } else {
                // Primary line or single line: 3 anchors
                const anchor1 = this._createAnchor(line.point1, formation.tbl_key, index, 'point1', line.point1, line.point2);
                const anchorCenter = this._createAnchor(centerPoint, formation.tbl_key, index, 'center', line.point1, line.point2);
                const anchor2 = this._createAnchor(line.point2, formation.tbl_key, index, 'point2', line.point1, line.point2);

                if (anchor1) {
                    formationGroup.appendChild(anchor1);
                    elements.push(anchor1);
                }
                if (anchorCenter) {
                    formationGroup.appendChild(anchorCenter);
                    elements.push(anchorCenter);
                }
                if (anchor2) {
                    formationGroup.appendChild(anchor2);
                    elements.push(anchor2);
                }
            }
        });

        // Add hover behavior to show/hide all anchors in the group
        this._setupGroupHoverBehavior(formationGroup);

        this.linesGroup.appendChild(formationGroup);

        this.renderedFormations.set(formation.tbl_key, {
            formation: formation,
            elements: elements,
            group: formationGroup
        });
    }

    /**
     * Setup hover behavior for a formation group.
     * Hovering over any part of the formation shows all anchors.
     * @param {SVGGElement} group - The formation group element
     */
    _setupGroupHoverBehavior(group) {
        const anchors = group.querySelectorAll('circle');

        group.addEventListener('mouseenter', () => {
            anchors.forEach(anchor => {
                anchor.style.opacity = '1';
            });
        });

        group.addEventListener('mouseleave', () => {
            if (!this.isDragging) {
                anchors.forEach(anchor => {
                    anchor.style.opacity = '0';
                });
            }
        });
    }

    /**
     * Create an invisible hit area for the line (wider than visible line).
     * @param {Object} point1 - {time, price}
     * @param {Object} point2 - {time, price}
     * @param {string} tblKey - Formation tbl_key
     * @param {number} lineIndex - Index of line in formation
     * @returns {SVGLineElement|null}
     */
    _createLineHitArea(point1, point2, tblKey, lineIndex) {
        const endpoints = this._getInfiniteLineEndpoints(point1, point2);
        if (!endpoints) return null;

        const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
        line.setAttribute('x1', endpoints.start.x);
        line.setAttribute('y1', endpoints.start.y);
        line.setAttribute('x2', endpoints.end.x);
        line.setAttribute('y2', endpoints.end.y);
        line.setAttribute('stroke', 'transparent');
        line.setAttribute('stroke-width', 20); // Wide hit area
        line.setAttribute('data-tbl-key', tblKey);
        line.setAttribute('data-line-index', String(lineIndex));
        line.setAttribute('data-line-role', 'hit');
        line.setAttribute('data-hit-area', 'true');
        line.style.cursor = 'pointer';

        return line;
    }

    /**
     * Create an SVG line element.
     * @param {Object} point1 - {time, price}
     * @param {Object} point2 - {time, price}
     * @param {string} color - Line color
     * @param {string} tblKey - Formation tbl_key
     * @param {number} lineIndex - Index of line in formation
     * @returns {SVGLineElement|null}
     */
    _createLine(point1, point2, color, tblKey, lineIndex) {
        const endpoints = this._getInfiniteLineEndpoints(point1, point2);
        if (!endpoints) return null;

        const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
        line.setAttribute('x1', endpoints.start.x);
        line.setAttribute('y1', endpoints.start.y);
        line.setAttribute('x2', endpoints.end.x);
        line.setAttribute('y2', endpoints.end.y);
        line.setAttribute('stroke', color);
        line.setAttribute('stroke-width', 2);
        line.setAttribute('data-tbl-key', tblKey);
        line.setAttribute('data-line-index', String(lineIndex));
        line.setAttribute('data-line-role', 'main');
        line.setAttribute('data-point1-time', point1.time);
        line.setAttribute('data-point1-price', point1.price);
        line.setAttribute('data-point2-time', point2.time);
        line.setAttribute('data-point2-price', point2.price);

        return line;
    }

    /**
     * Create an anchor circle for dragging.
     * @param {Object} point - {time, price} - the anchor point
     * @param {string} tblKey - Formation tbl_key
     * @param {number} lineIndex - Index of line in formation
     * @param {string} pointKey - 'point1', 'point2', 'center', or 'channel_offset'
     * @param {Object} linePoint1 - {time, price} - first point of the line (for position calculation)
     * @param {Object} linePoint2 - {time, price} - second point of the line (for position calculation)
     * @returns {SVGCircleElement|null}
     */
    _createAnchor(point, tblKey, lineIndex, pointKey, linePoint1, linePoint2) {
        // Use robust position calculation that handles anchors outside visible range
        const pixel = this._getAnchorPixelPosition(point, linePoint1 || point, linePoint2 || point);
        if (!pixel) return null;

        const isCenter = pointKey === 'center';
        const isChannelOffset = pointKey === 'channel_offset';

        const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
        circle.setAttribute('cx', pixel.x);
        circle.setAttribute('cy', pixel.y);

        // Anchor styling based on type
        if (isChannelOffset) {
            circle.setAttribute('r', 7);
            circle.setAttribute('fill', this.channelSecondaryColor);  // Green for channel offset
            circle.setAttribute('cursor', 'ns-resize');  // Vertical resize cursor
        } else if (isCenter) {
            circle.setAttribute('r', 8);
            circle.setAttribute('fill', '#28a745');  // Green for center
            circle.setAttribute('cursor', 'grab');
        } else {
            circle.setAttribute('r', 6);
            circle.setAttribute('fill', this.defaultColor);
            circle.setAttribute('cursor', 'crosshair');
        }

        circle.setAttribute('stroke', this.anchorColor);
        circle.setAttribute('stroke-width', 2);
        circle.setAttribute('data-tbl-key', tblKey);
        circle.setAttribute('data-line-index', lineIndex);
        circle.setAttribute('data-point-key', pointKey);
        circle.setAttribute('data-time', point.time);
        circle.setAttribute('data-price', point.price);

        // Make anchors visible on hover (group hover handles visibility)
        circle.style.opacity = '0';
        circle.style.transition = 'opacity 0.2s';

        // Mouse down to start dragging
        circle.addEventListener('mousedown', (e) => {
            e.stopPropagation();
            this._startDrag(circle, tblKey, pointKey);
        });

        return circle;
    }

    /**
     * Update all rendered formations (called when chart view changes).
     */
    _updateAllFormations() {
        // Use in-place updates to prevent flashing
        const formationsSnapshot = Array.from(this.renderedFormations.values()).map(item => item.formation);
        formationsSnapshot.forEach(formation => this._updateFormationPositionsInPlace(formation));

        // Update temp elements if drawing
        if (this.drawingMode && this.currentPoints.length > 0) {
            this._clearTempElements();
            this.currentPoints.forEach(pt => this._drawTempAnchor(pt));
        }
    }

    /**
     * Update a specific formation.
     * @param {Object} formation - Updated formation data
     */
    updateFormation(formation) {
        this.renderFormation(formation);
    }

    /**
     * Update formation positions in place (without removing/recreating elements).
     * Used during dragging to prevent flashing.
     * @param {Object} formation - Formation data with updated lines_json
     */
    _updateFormationPositionsInPlace(formation) {
        const data = this.renderedFormations.get(formation.tbl_key);
        if (!data || !data.group) {
            // Fallback to full render if no existing elements
            this.renderFormation(formation);
            return;
        }

        const linesData = JSON.parse(formation.lines_json || '{}');
        const lines = linesData.lines || [];

        // Update line positions
        lines.forEach((line, index) => {
            const endpoints = this._getInfiniteLineEndpoints(line.point1, line.point2);
            if (!endpoints) return;

            const hitLine = data.group.querySelector(
                `line[data-line-index="${index}"][data-line-role="hit"]`
            );
            const mainLine = data.group.querySelector(
                `line[data-line-index="${index}"][data-line-role="main"]`
            );

            if (hitLine) {
                hitLine.setAttribute('x1', endpoints.start.x);
                hitLine.setAttribute('y1', endpoints.start.y);
                hitLine.setAttribute('x2', endpoints.end.x);
                hitLine.setAttribute('y2', endpoints.end.y);
            }
            if (mainLine) {
                mainLine.setAttribute('x1', endpoints.start.x);
                mainLine.setAttribute('y1', endpoints.start.y);
                mainLine.setAttribute('x2', endpoints.end.x);
                mainLine.setAttribute('y2', endpoints.end.y);
            }
        });

        // Update anchor positions by stable selector (avoids NodeList order drift).
        lines.forEach((line, index) => {
            const isChannel = formation.formation_type === 'channel';
            const centerPoint = line.center || {
                time: Math.floor((line.point1.time + line.point2.time) / 2),
                price: (line.point1.price + line.point2.price) / 2
            };

            if (isChannel && line.isSecondary) {
                // Secondary line: channel offset anchor only.
                const pixel = this._getAnchorPixelPosition(centerPoint, line.point1, line.point2);
                const offsetAnchor = data.group.querySelector(
                    `circle[data-line-index="${index}"][data-point-key="channel_offset"]`
                );
                if (pixel && offsetAnchor) {
                    offsetAnchor.setAttribute('cx', pixel.x);
                    offsetAnchor.setAttribute('cy', pixel.y);
                    offsetAnchor.setAttribute('data-time', centerPoint.time);
                    offsetAnchor.setAttribute('data-price', centerPoint.price);
                }
            } else {
                // Primary/single line: point1, center, point2 anchors.
                const p1Pixel = this._getAnchorPixelPosition(line.point1, line.point1, line.point2);
                const centerPixel = this._getAnchorPixelPosition(centerPoint, line.point1, line.point2);
                const p2Pixel = this._getAnchorPixelPosition(line.point2, line.point1, line.point2);
                const anchorPoint1 = data.group.querySelector(
                    `circle[data-line-index="${index}"][data-point-key="point1"]`
                );
                const anchorCenter = data.group.querySelector(
                    `circle[data-line-index="${index}"][data-point-key="center"]`
                );
                const anchorPoint2 = data.group.querySelector(
                    `circle[data-line-index="${index}"][data-point-key="point2"]`
                );

                if (p1Pixel && anchorPoint1) {
                    anchorPoint1.setAttribute('cx', p1Pixel.x);
                    anchorPoint1.setAttribute('cy', p1Pixel.y);
                    anchorPoint1.setAttribute('data-time', line.point1.time);
                    anchorPoint1.setAttribute('data-price', line.point1.price);
                }
                if (centerPixel && anchorCenter) {
                    anchorCenter.setAttribute('cx', centerPixel.x);
                    anchorCenter.setAttribute('cy', centerPixel.y);
                    anchorCenter.setAttribute('data-time', centerPoint.time);
                    anchorCenter.setAttribute('data-price', centerPoint.price);
                }
                if (p2Pixel && anchorPoint2) {
                    anchorPoint2.setAttribute('cx', p2Pixel.x);
                    anchorPoint2.setAttribute('cy', p2Pixel.y);
                    anchorPoint2.setAttribute('data-time', line.point2.time);
                    anchorPoint2.setAttribute('data-price', line.point2.price);
                }
            }
        });

        // Update the stored formation data
        data.formation = formation;
    }

    /**
     * Remove a formation from the overlay.
     * @param {string} tblKey - Formation tbl_key
     */
    removeFormation(tblKey) {
        const data = this.renderedFormations.get(tblKey);
        if (data) {
            data.elements.forEach(el => el.remove());
            this.renderedFormations.delete(tblKey);
        }
    }

    /**
     * Clear all formations from the overlay.
     */
    clearAllFormations() {
        // Iterate over a stable key snapshot to avoid mutating the Map during iteration.
        const keys = Array.from(this.renderedFormations.keys());
        for (const tblKey of keys) {
            this.removeFormation(tblKey);
        }
    }

    /**
     * Select a formation for editing.
     * @param {string} tblKey - Formation tbl_key
     */
    selectFormation(tblKey) {
        // Deselect previous
        if (this.selectedTblKey) {
            const prevData = this.renderedFormations.get(this.selectedTblKey);
            if (prevData) {
                prevData.elements.forEach(el => {
                    if (el.tagName === 'line' && el.getAttribute('data-line-role') === 'main') {
                        el.setAttribute('stroke', prevData.formation.color || this.defaultColor);
                    }
                    if (el.tagName === 'circle') {
                        const pointKey = el.getAttribute('data-point-key');
                        if (pointKey === 'center') {
                            el.setAttribute('fill', '#28a745');
                        } else if (pointKey === 'channel_offset') {
                            el.setAttribute('fill', this.channelSecondaryColor);
                        } else {
                            el.setAttribute('fill', prevData.formation.color || this.defaultColor);
                        }
                    }
                });
            }
        }

        this.selectedTblKey = tblKey;

        // Highlight selected
        const data = this.renderedFormations.get(tblKey);
        if (data) {
            data.elements.forEach(el => {
                if (el.tagName === 'line' && el.getAttribute('data-line-role') === 'main') {
                    el.setAttribute('stroke', this.selectedColor);
                }
                if (el.tagName === 'circle') {
                    el.setAttribute('fill', this.selectedColor);
                    el.style.opacity = '1';
                }
            });
        }
    }

    // ================ Dragging Methods ================

    /**
     * Start dragging an anchor.
     * @param {SVGCircleElement} anchor - The anchor element
     * @param {string} tblKey - Formation tbl_key
     * @param {string} anchorType - 'point1', 'point2', 'center', or 'channel_offset'
     */
    _startDrag(anchor, tblKey, anchorType) {
        this.isDragging = true;
        this.dragAnchor = anchor;
        this.dragAnchorType = anchorType;

        const data = this.renderedFormations.get(tblKey);
        if (data) {
            // Make a deep copy to avoid modifying during drag
            this.dragFormation = JSON.parse(JSON.stringify(data.formation));
        }

        // Store initial drag state for relative calculations
        const linesData = JSON.parse(this.dragFormation.lines_json || '{}');
        if (linesData.lines && linesData.lines[0]) {
            // Store all initial lines for channel support
            this._dragInitialLines = JSON.parse(JSON.stringify(linesData.lines));
            this._dragInitialLine = this._dragInitialLines[0];
            this._dragStartCoords = {
                time: parseInt(anchor.getAttribute('data-time'), 10),
                price: parseFloat(anchor.getAttribute('data-price'))
            };
        }

        // Visual feedback
        const isCenter = anchorType === 'center';
        const isChannelOffset = anchorType === 'channel_offset';
        anchor.setAttribute('r', (isCenter || isChannelOffset) ? 10 : 8);
        if (isCenter) {
            anchor.style.cursor = 'grabbing';
        }

        // Prevent text selection during drag
        document.body.style.userSelect = 'none';
    }

    /**
     * Handle drag movement.
     * @param {Object} coords - New position {time, price}
     */
    _handleDrag(coords) {
        if (!this.dragAnchor || !this.dragFormation || !this._dragInitialLine) return;

        const lineIndex = parseInt(this.dragAnchor.getAttribute('data-line-index'), 10);
        const pointKey = this.dragAnchorType;

        // Null guards
        if (isNaN(lineIndex) || !pointKey) return;

        // Parse current lines
        let linesData;
        try {
            linesData = JSON.parse(this.dragFormation.lines_json || '{}');
        } catch (e) {
            return;
        }

        if (!linesData.lines || !linesData.lines[0]) return;

        const isChannel = this.dragFormation.formation_type === 'channel';
        const primaryLine = linesData.lines[0];
        const secondaryLine = linesData.lines[1];
        const initialPrimary = this._dragInitialLines[0];
        const initialSecondary = this._dragInitialLines[1];

        if (pointKey === 'channel_offset' && isChannel && secondaryLine) {
            // CHANNEL OFFSET DRAG: Move secondary line parallel to primary (price only)
            const priceDelta = coords.price - this._dragStartCoords.price;
            const currentOffset = (initialSecondary.priceOffset || 0) + priceDelta;

            // Update secondary line position (keep parallel to primary)
            secondaryLine.point1 = {
                time: primaryLine.point1.time,
                price: primaryLine.point1.price + currentOffset
            };
            secondaryLine.point2 = {
                time: primaryLine.point2.time,
                price: primaryLine.point2.price + currentOffset
            };
            secondaryLine.center = {
                time: primaryLine.center.time,
                price: primaryLine.center.price + currentOffset
            };
            secondaryLine.priceOffset = currentOffset;

        } else if (pointKey === 'center' && lineIndex === 0) {
            // PRIMARY CENTER DRAG: Translate both lines together
            const timeDelta = coords.time - this._dragStartCoords.time;
            const priceDelta = coords.price - this._dragStartCoords.price;

            // Move primary line
            primaryLine.point1 = {
                time: initialPrimary.point1.time + timeDelta,
                price: initialPrimary.point1.price + priceDelta
            };
            primaryLine.point2 = {
                time: initialPrimary.point2.time + timeDelta,
                price: initialPrimary.point2.price + priceDelta
            };
            primaryLine.center = {
                time: coords.time,
                price: coords.price
            };

            // Move secondary line if channel
            if (isChannel && secondaryLine && initialSecondary) {
                const offset = initialSecondary.priceOffset || (initialSecondary.center.price - initialPrimary.center.price);
                secondaryLine.point1 = {
                    time: primaryLine.point1.time,
                    price: primaryLine.point1.price + offset
                };
                secondaryLine.point2 = {
                    time: primaryLine.point2.time,
                    price: primaryLine.point2.price + offset
                };
                secondaryLine.center = {
                    time: primaryLine.center.time,
                    price: primaryLine.center.price + offset
                };
                secondaryLine.priceOffset = offset;
            }

        } else if ((pointKey === 'point1' || pointKey === 'point2') && lineIndex === 0) {
            // PRIMARY ENDPOINT DRAG: Pivot primary line, keep secondary parallel
            if (pointKey === 'point1') {
                primaryLine.point1 = { time: coords.time, price: coords.price };
            } else {
                primaryLine.point2 = { time: coords.time, price: coords.price };
            }

            // Recalculate center as midpoint
            primaryLine.center = {
                time: Math.floor((primaryLine.point1.time + primaryLine.point2.time) / 2),
                price: (primaryLine.point1.price + primaryLine.point2.price) / 2
            };

            // Update secondary line to stay parallel if channel
            if (isChannel && secondaryLine && initialSecondary) {
                const offset = secondaryLine.priceOffset || (initialSecondary.center.price - initialPrimary.center.price);
                secondaryLine.point1 = {
                    time: primaryLine.point1.time,
                    price: primaryLine.point1.price + offset
                };
                secondaryLine.point2 = {
                    time: primaryLine.point2.time,
                    price: primaryLine.point2.price + offset
                };
                secondaryLine.center = {
                    time: primaryLine.center.time,
                    price: primaryLine.center.price + offset
                };
            }
        }

        // Update formation data
        this.dragFormation.lines_json = JSON.stringify(linesData);

        // Update positions in place (avoids flashing from remove/recreate)
        this._updateFormationPositionsInPlace(this.dragFormation);
    }

    /**
     * End dragging.
     */
    _endDrag() {
        if (this.dragAnchor) {
            const isCenter = this.dragAnchorType === 'center';
            const isChannelOffset = this.dragAnchorType === 'channel_offset';
            this.dragAnchor.setAttribute('r', isChannelOffset ? 7 : (isCenter ? 8 : 6));
            if (isCenter) {
                this.dragAnchor.style.cursor = 'grab';
            }
        }

        // Update the stored formation data
        if (this.dragFormation && this.dragFormation.tbl_key) {
            const data = this.renderedFormations.get(this.dragFormation.tbl_key);
            if (data) {
                data.formation = this.dragFormation;
            }

            // If dragging the draft formation, sync changes back to draftFormation
            // so that completeDrawing() uses the modified positions
            if (this.dragFormation.tbl_key === this.draftTblKey && this.draftFormation) {
                this.draftFormation.lines_json = this.dragFormation.lines_json;
            }
        }

        this.isDragging = false;
        this.dragAnchor = null;
        this.dragFormation = null;
        this.dragAnchorType = null;
        this._dragInitialLine = null;
        this._dragInitialLines = null;
        this._dragStartCoords = null;

        document.body.style.userSelect = '';
    }

    // ================ Cleanup ================

    /**
     * Destroy the overlay and clean up resources.
     */
    destroy() {
        // Stop RAF loop
        this._loopRunning = false;
        if (this._animationFrameId) {
            cancelAnimationFrame(this._animationFrameId);
            this._animationFrameId = null;
        }

        // Remove SVG element
        if (this.svg && this.svg.parentNode) {
            this.svg.parentNode.removeChild(this.svg);
        }

        // Clear references
        this.svg = null;
        this.container = null;
        this.renderedFormations.clear();

        console.log('FormationOverlay: Destroyed');
    }
}
