var AiWavesIndicator_1;
import { __decorate } from "tslib";
import { avoidTreeShaking } from '@genesislcap/foundation-utils';
import { attr, css, customElement, GenesisElement, html } from '@genesislcap/web-core';
import { AI_COLOUR_AMBER, AI_COLOUR_CYAN, AI_COLOUR_PINK, AI_COLOUR_VIOLET, } from '../styles/ai-colours';
import { AiHaloOverlay } from './halo-overlay';
const WAVES_DEFAULT_SIZE = 56;
/** CSS-ready form of `WAVES_DEFAULT_SIZE` (the `css` tag rejects raw numbers). */
const WAVES_DEFAULT_SIZE_CSS = `${WAVES_DEFAULT_SIZE}px`;
/** SVG coordinate space the waves are drawn in (square; the circle fills it). */
const VIEWBOX = 120;
const CENTRE = VIEWBOX / 2;
/** Horizontal sampling step when tracing each wave path. Lower = smoother. */
const SAMPLE_STEP = 6;
/** Milliseconds in one second. */
const MS_PER_SECOND = 1000;
/** Baseline frame rate (fps) the wave speeds are tuned against. */
const BASELINE_FPS = 60;
/**
 * Frame duration (ms) at the baseline rate. The paths are driven by elapsed
 * wall-clock time divided by this — not by the rAF callback count — so they
 * advance at a constant real-world rate regardless of the display's refresh
 * rate or dropped frames. At the baseline rate the resulting frame index
 * matches the old per-callback counter, preserving the current look.
 */
const FRAME_MS = MS_PER_SECOND / BASELINE_FPS;
const WAVES = [
    {
        colour: AI_COLOUR_AMBER,
        amplitude: 11,
        frequency: 0.085,
        phaseSpeed: 0.05,
        verticalOffset: -6,
        slosh: 6,
        sloshFrequency: 0.018,
        sloshSpeed: 0.021,
    },
    {
        colour: AI_COLOUR_PINK,
        amplitude: 14,
        frequency: 0.07,
        phaseSpeed: -0.043,
        verticalOffset: -2,
        slosh: 7,
        sloshFrequency: 0.022,
        sloshSpeed: -0.017,
    },
    {
        colour: AI_COLOUR_CYAN,
        amplitude: 13,
        frequency: 0.095,
        phaseSpeed: 0.037,
        verticalOffset: 2,
        slosh: 5,
        sloshFrequency: 0.015,
        sloshSpeed: 0.025,
    },
    {
        colour: AI_COLOUR_VIOLET,
        amplitude: 10,
        frequency: 0.06,
        phaseSpeed: -0.055,
        verticalOffset: 6,
        slosh: 8,
        sloshFrequency: 0.025,
        sloshSpeed: -0.013,
    },
];
const wavePathsMarkup = WAVES.map((w, i) => `<path class="wave" data-wave="${i}" stroke="${w.colour}" />`).join('');
/**
 * Animated "waves inside a circle" loading indicator — coloured sine waves that
 * slosh like glowing liquid inside a circular window, ringed by a rotating
 * gradient halo (the same effect as `<ai-halo-overlay>`).
 *
 * Visual sibling of the dots loading indicator; the two are interchangeable
 * styles of the same "assistant is working" state.
 *
 * @example
 * ```html
 * <ai-waves-indicator size="56"></ai-waves-indicator>
 * ```
 *
 * @beta
 */
let AiWavesIndicator = AiWavesIndicator_1 = class AiWavesIndicator extends GenesisElement {
    constructor() {
        super(...arguments);
        /** Diameter of the circular window in px. Default: 56. */
        this.size = WAVES_DEFAULT_SIZE;
    }
    sizeChanged() {
        this.style.setProperty('--waves-size', `${this.size}px`);
    }
    connectedCallback() {
        super.connectedCallback();
        // Guard against a reconnect starting a second concurrent loop.
        if (this.animFrame === undefined) {
            this.tick();
        }
    }
    disconnectedCallback() {
        super.disconnectedCallback();
        if (this.animFrame !== undefined) {
            cancelAnimationFrame(this.animFrame);
            this.animFrame = undefined;
        }
        // Reset the clock so a later reconnect restarts the waves cleanly at frame 0.
        this.startTime = undefined;
    }
    tick(now) {
        var _a, _b;
        // Stop ticking once disconnected (belt-and-braces alongside the cancel in
        // disconnectedCallback) so no frames are scheduled while detached.
        if (!this.isConnected) {
            this.animFrame = undefined;
            return;
        }
        if (!this.wavePaths) {
            const paths = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelectorAll('.wave');
            if (paths === null || paths === void 0 ? void 0 : paths.length)
                this.wavePaths = Array.from(paths);
        }
        // Anchor to the first real rAF timestamp; the initial synchronous call (no
        // timestamp) renders frame 0. `frame` is then elapsed time in 60fps frames.
        if (now !== undefined && this.startTime === undefined)
            this.startTime = now;
        const frame = now === undefined || this.startTime === undefined ? 0 : (now - this.startTime) / FRAME_MS;
        (_b = this.wavePaths) === null || _b === void 0 ? void 0 : _b.forEach((path, i) => {
            path.setAttribute('d', AiWavesIndicator_1.buildWavePath(WAVES[i], frame));
        });
        this.animFrame = requestAnimationFrame((t) => this.tick(t));
    }
    /** Trace one wave's polyline `d` attribute for the given frame. */
    static buildWavePath(cfg, frame) {
        const segments = [];
        for (let x = 0; x <= VIEWBOX; x += SAMPLE_STEP) {
            const y = CENTRE +
                cfg.verticalOffset +
                cfg.amplitude * Math.sin(x * cfg.frequency + frame * cfg.phaseSpeed) +
                cfg.slosh * Math.sin(x * cfg.sloshFrequency + frame * cfg.sloshSpeed);
            segments.push(`${x},${y.toFixed(2)}`);
        }
        return `M ${segments.join(' L ')}`;
    }
};
__decorate([
    attr({ converter: { fromView: Number, toView: String } })
], AiWavesIndicator.prototype, "size", void 0);
AiWavesIndicator = AiWavesIndicator_1 = __decorate([
    customElement({
        name: 'ai-waves-indicator',
        template: html `
    <div class="window" role="img" aria-label="Assistant is working">
      <svg class="waves" viewBox="0 0 ${VIEWBOX} ${VIEWBOX}" preserveAspectRatio="none">
        <defs>
          <filter id="wave-glow" x="-20%" y="-20%" width="140%" height="140%">
            <feGaussianBlur stdDeviation="1.6" result="blur" />
            <feMerge>
              <feMergeNode in="blur" />
              <feMergeNode in="SourceGraphic" />
            </feMerge>
          </filter>
        </defs>
        <g filter="url(#wave-glow)">${wavePathsMarkup}</g>
      </svg>
      <ai-halo-overlay active border-size="2" glow-opacity="0.5" glow-spread="55"></ai-halo-overlay>
    </div>
  `,
        styles: css `
    :host {
      display: inline-block;
      width: var(--waves-size, ${WAVES_DEFAULT_SIZE_CSS});
      height: var(--waves-size, ${WAVES_DEFAULT_SIZE_CSS});
    }

    .window {
      position: relative;
      width: 100%;
      height: 100%;
      border-radius: 50%;
      overflow: hidden;
      background: radial-gradient(circle at 50% 32%, #2b3140 0%, #11141c 55%, #05070c 100%);
    }

    .waves {
      position: absolute;
      inset: 0;
      width: 100%;
      height: 100%;
    }

    .wave {
      fill: none;
      stroke-width: 2;
      stroke-linecap: round;
      stroke-linejoin: round;
    }
  `,
    })
], AiWavesIndicator);
export { AiWavesIndicator };
// Ensure the halo overlay used for the ring is registered alongside this component.
avoidTreeShaking(AiHaloOverlay);
