/**
 * Instrumentation for diagnosing editor interaction latency
 * (currently: component selection).
 *
 * Opt-in. Enable with the `parhelia_perf=1` query param or
 * `localStorage.setItem("parhelia:perf", "1")` (flag family as
 * `parhelia:bridge:debug`), then reload. `window.__parheliaPerf.enable()` /
 * `.disable()` set the localStorage flag for you.
 *
 * A trace begins at a trigger (e.g. a canvas click), collects named steps from
 * anywhere in the editor, and finalizes automatically once no step has arrived
 * for QUIET_MS (or at HARD_CAP_MS). Each step logs one console line as it
 * happens; the finalized trace prints a waterfall `console.table` and a
 * `performance.measure` span so the same steps line up in the DevTools
 * Performance panel (marks/measures are prefixed `parhelia:perf:`).
 *
 * Steps recorded while no trace is active (e.g. a fetch that resolves after
 * the trace settled) still log standalone lines, so slow stragglers stay
 * visible.
 *
 * Everything is a no-op when the flag is off.
 */
const LOG_PREFIX = "[Parhelia perf]";
const MARK_PREFIX = "parhelia:perf:";
const QUIET_MS = 2000;
const HARD_CAP_MS = 15000;
let cachedEnabled;
let activeTrace;
let traceCounter = 0;
export function isPerfTraceEnabled() {
    if (typeof window === "undefined")
        return false;
    if (cachedEnabled !== undefined)
        return cachedEnabled;
    cachedEnabled = readPerfFlag();
    // Helpers install even while tracing is off so `.enable()` is reachable.
    installConsoleHelpers();
    if (cachedEnabled) {
        console.info(`${LOG_PREFIX} tracing enabled - click a component to record a selection trace ` +
            `(disable via window.__parheliaPerf.disable() + reload)`);
    }
    return cachedEnabled;
}
export function isPerfTraceActive() {
    return isPerfTraceEnabled() && activeTrace !== undefined;
}
/**
 * Start a new trace. An unfinished previous trace is finalized first so
 * rapid successive clicks each get their own waterfall.
 *
 * `startedAtEpochMs` backdates the trace start to an epoch timestamp captured
 * elsewhere (e.g. `Date.now()` stamped by the editing host at the original DOM
 * click), so host-side latency shows up inside the trace instead of before it.
 */
export function beginPerfTrace(name, detail, startedAtEpochMs) {
    if (!isPerfTraceEnabled())
        return;
    if (activeTrace)
        finalizeTrace("superseded");
    const now = performance.now();
    const backdateMs = startedAtEpochMs !== undefined
        ? Math.max(0, Date.now() - startedAtEpochMs)
        : 0;
    activeTrace = {
        name,
        id: ++traceCounter,
        startedAt: now - backdateMs,
        steps: [],
        quietTimer: undefined,
        capTimer: window.setTimeout(() => finalizeTrace("cap"), HARD_CAP_MS),
    };
    safeMark(`${MARK_PREFIX}${name}#${activeTrace.id}:begin`);
    console.info(`${LOG_PREFIX} ── trace #${activeTrace.id} "${name}" started`, withCompactDetail(detail, backdateMs ? { backdatedMs: round(backdateMs) } : undefined));
    recordStep("trace:begin", detail);
}
/**
 * Record a step in the active trace (one console line + performance.mark).
 * Without an active trace, logs a standalone line so late async completions
 * (fetches resolving after the trace settled) remain visible.
 */
export function perfTraceStep(step, detail) {
    if (!isPerfTraceEnabled())
        return;
    if (!activeTrace) {
        safeMark(`${MARK_PREFIX}${step}`);
        console.info(`${LOG_PREFIX} (no active trace) ${step}`, detail ?? "");
        return;
    }
    recordStep(step, detail);
}
/**
 * Record `step` after the next frame has painted (double rAF), measuring how
 * long the browser needed to actually put the pending update on screen.
 */
export function perfTracePaintStep(step, detail) {
    if (!isPerfTraceEnabled())
        return;
    const requestedAt = performance.now();
    requestAnimationFrame(() => {
        requestAnimationFrame(() => {
            perfTraceStep(step, {
                ...detail,
                renderToPaintMs: round(performance.now() - requestedAt),
            });
        });
    });
}
function recordStep(step, detail) {
    const trace = activeTrace;
    if (!trace)
        return;
    const at = performance.now();
    const previousAt = trace.steps.length > 0
        ? trace.steps[trace.steps.length - 1].at
        : trace.startedAt;
    trace.steps.push({ step, at, detail });
    safeMark(`${MARK_PREFIX}${step}`);
    console.info(`${LOG_PREFIX} #${trace.id} +${round(at - trace.startedAt)}ms (Δ ${round(at - previousAt)}ms) ${step}`, detail ?? "");
    if (trace.quietTimer !== undefined)
        window.clearTimeout(trace.quietTimer);
    trace.quietTimer = window.setTimeout(() => finalizeTrace("settled"), QUIET_MS);
}
function finalizeTrace(reason) {
    const trace = activeTrace;
    if (!trace)
        return;
    activeTrace = undefined;
    if (trace.quietTimer !== undefined)
        window.clearTimeout(trace.quietTimer);
    if (trace.capTimer !== undefined)
        window.clearTimeout(trace.capTimer);
    const rows = trace.steps.map((entry, index) => ({
        step: entry.step,
        "t+ms": round(entry.at - trace.startedAt),
        "Δms": round(entry.at - (index > 0 ? trace.steps[index - 1].at : trace.startedAt)),
        detail: entry.detail ? compactDetailString(entry.detail) : "",
    }));
    const lastAt = trace.steps.length > 0
        ? trace.steps[trace.steps.length - 1].at
        : trace.startedAt;
    try {
        performance.measure(`${MARK_PREFIX}${trace.name}#${trace.id}`, {
            start: trace.startedAt,
            end: lastAt,
        });
    }
    catch {
        // Older browsers without options-object measure: skip the span.
    }
    console.groupCollapsed(`${LOG_PREFIX} ── trace #${trace.id} "${trace.name}" ${reason} - ` +
        `${round(lastAt - trace.startedAt)}ms across ${trace.steps.length} steps ` +
        `(sorted by Δms descending: check the biggest Δ first)`);
    console.table(rows);
    console.table([...rows].sort((a, b) => b["Δms"] - a["Δms"]).slice(0, 5), ["step", "Δms", "t+ms"]);
    console.groupEnd();
}
function readPerfFlag() {
    // Opt-in; the query param wins over storage.
    try {
        const params = new URLSearchParams(window.location.search);
        const param = params.get("parhelia_perf");
        if (param === "0")
            return false;
        if (param === "1")
            return true;
        const stored = window.localStorage.getItem("parhelia:perf");
        return stored === "1" || stored === "true";
    }
    catch {
        return false;
    }
}
function installConsoleHelpers() {
    const target = window;
    if (target.__parheliaPerf)
        return;
    target.__parheliaPerf = {
        enable: () => {
            window.localStorage.setItem("parhelia:perf", "1");
            console.info(`${LOG_PREFIX} enabled - reload to apply everywhere`);
        },
        disable: () => {
            window.localStorage.setItem("parhelia:perf", "0");
            console.info(`${LOG_PREFIX} disabled - reload to apply`);
        },
    };
}
function safeMark(name) {
    try {
        performance.mark(name);
    }
    catch {
        // performance.mark is best-effort only.
    }
}
function compactDetailString(detail) {
    try {
        const json = JSON.stringify(detail);
        return json.length > 160 ? `${json.slice(0, 157)}...` : json;
    }
    catch {
        return "[unserializable]";
    }
}
function withCompactDetail(detail, extra) {
    if (!detail && !extra)
        return "";
    return { ...detail, ...extra };
}
function round(value) {
    return Math.round(value * 10) / 10;
}
//# sourceMappingURL=perfTrace.js.map