/*
File:    swRoundedRectangle.js
Date:    2026-02-28
Author:  klp + GitHub Copilot
App:     SketchWaveTNT2026-02-28-Stg6
Purpose: SWRoundedRectangle — extends SWRectangle with corner rounding.

  SWRoundedRectangle inherits all animation (breathe, transform, rotateAboutCenter),
  vertex tracking, dragging, and trail support from SWRectangle.  It overrides
  draw() and drawOnGrid() to use p5's push/translate/rotate/rect()/pop() pattern,
  which supports the `cornerRadius` property natively — something quad() cannot do.

  Key design notes:
  - cornerRadius is in user (grid) units, scaled to pixels at draw time.
  - Three internal scalars (_scaleX, _scaleY, _animRotDeg) are maintained by
    overriding breathe(), transform(), and rotateAboutCenter() so drawOnGrid()
    always knows the live dimensions and rotation without re-deriving them from
    vertex positions.
  - showVertices is silently ignored: the four inherited vertices track scaling and
    rotation correctly for trail purposes but are not drawn (corners are rounded).
*/

console.log("[swRoundedRectangle.js] SWRoundedRectangle class loaded.");

class SWRoundedRectangle extends SWRectangle {

    /**
     * @param {SWPoint}  center        — Center of the shape (user coordinates)
     * @param {number}   width         — Full width in user units
     * @param {number}   height        — Full height in user units
     * @param {SWColor}  fillColor     — Interior fill color
     * @param {number}  [cornerRadius] — Corner radius in user units (default 1.5)
     * @param {Object}  [options]      — Passed to SWRectangle:
     *                                   strokeColor, strokeWeight, showCenter, rotation
     */
    constructor(center, width, height, fillColor, cornerRadius = 1.5, options = {}) {
        super(center, width, height, fillColor, options);
        this.cornerRadius = cornerRadius;

        // ── Internal animation-state trackers ─────────────────────────────────
        this._scaleX     = 1;  // current X-axis breathing scale factor
        this._scaleY     = 1;  // current Y-axis breathing scale factor
        this._animRotDeg = 0;  // additional rotation from animation (degrees CCW)
    }//end constructor

    // ── Override breathe ──────────────────────────────────────────────────────
    /**
     * Scales the shape using independent SWSinusoid objects and tracks scale
     * factors for use in drawOnGrid().
     * @param {SWSinusoid|null} sinusoidX
     * @param {SWSinusoid|null} sinusoidY
     * @param {number}          t — time in seconds
     */
    breathe(sinusoidX, sinusoidY, t) {
        super.breathe(sinusoidX, sinusoidY, t);
        const minScale = 0.1;
        this._scaleX = Math.max(minScale, sinusoidX ? sinusoidX.getValue(t) : 1);
        this._scaleY = Math.max(minScale, sinusoidY ? sinusoidY.getValue(t) : 1);
    }//end breathe

    // ── Override rotateAboutCenter ────────────────────────────────────────────
    /**
     * Rotates the shape about its center and tracks the angle for drawOnGrid().
     * @param {number} degPerSec — angular velocity (CCW positive)
     * @param {number} t         — time in seconds
     */
    rotateAboutCenter(degPerSec, t) {
        super.rotateAboutCenter(degPerSec, t);
        this._animRotDeg = degPerSec * t;
    }//end rotateAboutCenter

    // ── Override transform ────────────────────────────────────────────────────
    /**
     * Applies simultaneous breathing and rotation, tracking scalars for drawOnGrid().
     * @param {Object} options — {sinusoidX, sinusoidY, t, degPerSec}
     */
    transform({ sinusoidX = null, sinusoidY = null, t = 0, degPerSec = null } = {}) {
        super.transform({ sinusoidX, sinusoidY, t, degPerSec });
        const minScale = 0.1;
        this._scaleX     = sinusoidX ? Math.max(minScale, sinusoidX.getValue(t)) : 1;
        this._scaleY     = sinusoidY ? Math.max(minScale, sinusoidY.getValue(t)) : 1;
        this._animRotDeg = degPerSec !== null ? degPerSec * t : 0;
    }//end transform

    // ── Override reset ────────────────────────────────────────────────────────
    /**
     * Restores original dimensions, resets animation trackers.
     */
    reset() {
        super.reset();
        this._scaleX     = 1;
        this._scaleY     = 1;
        this._animRotDeg = 0;
    }//end reset

    // ── drawOnGrid (key override) ─────────────────────────────────────────────
    /**
     * Draws the rounded rectangle using push/translate/rotate/rect()/pop().
     * This is the fundamental departure from SWRectangle's quad() approach —
     * it is what enables corner rounding and is why this subclass exists.
     * @param {SWGrid} grid
     */
    drawOnGrid(grid) {
        const s  = grid.userToScreen(this.rCenter.x, this.rCenter.y);
        const pw = this.width  * this._scaleX * grid.xScale;  // pixel width
        const ph = this.height * this._scaleY * grid.yScale;  // pixel height
        const cr = Math.min(this.cornerRadius * grid.xScale,  // pixel corner radius
                            pw / 2, ph / 2);                  // clamped to half side

        // Total rotation = static initial rotation + accumulated animation rotation
        // Negate because p5 uses CW-positive; user convention is CCW-positive.
        const totalDeg = this.rotation + this._animRotDeg;
        const rotRad   = -totalDeg * Math.PI / 180;

        push();
        translate(s.x, s.y);
        rotate(rotRad);

        if (this.fillColor && this.fillColor.col) {
            fill(this.fillColor.col);
        } else {
            noFill();
        }
        if (this.strokeColor && this.strokeColor.col) {
            stroke(this.strokeColor.col);
            strokeWeight(this.strokeWeight);
        } else {
            noStroke();
        }

        rectMode(CENTER);
        rect(0, 0, pw, ph, cr);
        pop();
        rectMode(CORNER); // restore p5 default

        noStroke();
        noFill();

        // Optionally draw center point
        if (this.showCenter) {
            this.rCenter.drawOnGrid(grid);
        }
    }//end drawOnGrid

    // ── draw (screen-pixel version) ───────────────────────────────────────────
    /**
     * Draws using raw pixel coordinates (no grid mapping).
     */
    draw() {
        const pw = this.width  * this._scaleX;
        const ph = this.height * this._scaleY;
        const cr = Math.min(this.cornerRadius, pw / 2, ph / 2);
        const rotRad = -(this.rotation + this._animRotDeg) * Math.PI / 180;

        push();
        translate(this.rCenter.x, this.rCenter.y);
        rotate(rotRad);

        if (this.fillColor && this.fillColor.col)   { fill(this.fillColor.col); }   else { noFill(); }
        if (this.strokeColor && this.strokeColor.col) {
            stroke(this.strokeColor.col);
            strokeWeight(this.strokeWeight);
        } else { noStroke(); }

        rectMode(CENTER);
        rect(0, 0, pw, ph, cr);
        pop();
        rectMode(CORNER);
    }//end draw

    // ── Convenience getters ───────────────────────────────────────────────────

    /** Effective width at the current breathing scale */
    get currentWidth()       { return this.width  * this._scaleX; }
    /** Effective height at the current breathing scale */
    get currentHeight()      { return this.height * this._scaleY; }
    /** Area of the live (scaled) bounding rectangle */
    get currentArea()        { return this.currentWidth * this.currentHeight; }
    /** Aspect ratio of the live (scaled) shape */
    get currentAspectRatio() { return this.currentWidth / this.currentHeight; }

    // ── toString ──────────────────────────────────────────────────────────────
    toString() {
        const totalDeg = this.rotation + this._animRotDeg;
        return `SWRoundedRectangle(center: (${this.rCenter.x.toFixed(2)}, ${this.rCenter.y.toFixed(2)}), ` +
               `width: ${this.width.toFixed(2)}, height: ${this.height.toFixed(2)}, ` +
               `cornerRadius: ${this.cornerRadius.toFixed(2)}, area: ${this.area.toFixed(2)}, ` +
               `rotation: ${totalDeg.toFixed(1)}\u00b0)`;
    }//end toString

}//end class SWRoundedRectangle
