"use strict";

// AppSignal integration helper for jso-symbolicate.
//
// AppSignal's JS client exposes `addDecorator(fn)` (and `addOverride`) — the
// decorator receives a span before it ships and can mutate it in place. Use
// it to demangle the action name, error name/message, and the backtrace
// attached to the error sample.
//
// Usage:
//
//   import Appsignal from "@appsignal/javascript";
//   const { buildLookup } = require("jso-symbolicate");
//   const { createAppSignalDecorator } = require("jso-symbolicate/appsignal");
//
//   const lookup = buildLookup(
//       window.__JSO_REPORT__.Report.GlobalIdentifierMap,
//       window.__JSO_REPORT__.Report.MemberIdentifierMap
//   );
//
//   const appsignal = new Appsignal({ key: "..." });
//   appsignal.addDecorator(createAppSignalDecorator(lookup));

const { demangleStack } = require("./index.js");

/**
 * Build an AppSignal decorator. The decorator receives a span. It mutates
 * the span in place and returns it. Errors are swallowed so a broken lookup
 * never blocks reporting.
 */
function createAppSignalDecorator(lookup) {
    if (!lookup || typeof lookup !== "object") {
        return function (span) { return span; };
    }
    return function decorator(span) {
        try {
            if (!span || typeof span !== "object") return span;
            // AppSignal spans expose serialize()/getData() depending on version.
            // We mutate whichever data bag is reachable.
            const data = (typeof span.serialize === "function" ? span.serialize() : null)
                      || (typeof span.getData === "function" ? span.getData() : null)
                      || span._data
                      || span;

            if (typeof data.action === "string") data.action = demangleStack(data.action, lookup);
            if (typeof data.namespace === "string") data.namespace = demangleStack(data.namespace, lookup);

            const err = data.error;
            if (err && typeof err === "object") {
                if (typeof err.name === "string") err.name = demangleStack(err.name, lookup);
                if (typeof err.message === "string") err.message = demangleStack(err.message, lookup);
                if (Array.isArray(err.backtrace)) {
                    err.backtrace = err.backtrace.map(s => typeof s === "string" ? demangleStack(s, lookup) : s);
                }
            }

            // Tags + params often surface function names through user code.
            if (data.tags && typeof data.tags === "object") {
                for (const k of Object.keys(data.tags)) {
                    if (typeof data.tags[k] === "string") {
                        data.tags[k] = demangleStack(data.tags[k], lookup);
                    }
                }
                data.tags.jso_symbolicated = true;
            } else {
                data.tags = { jso_symbolicated: true };
            }

            // If the span exposed a setter API, push the mutated data back.
            if (typeof span.setError === "function" && err) span.setError(err);
            if (typeof span.setAction === "function" && data.action) span.setAction(data.action);
            if (typeof span.setTags === "function" && data.tags) span.setTags(data.tags);
        } catch (e) {
            // Swallow; the span ships in whatever state we left it.
        }
        return span;
    };
}

module.exports = { createAppSignalDecorator };
