"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FeedbackManager = void 0;
const tslib_1 = require("tslib");
const base_js_1 = require("./base.js");
// eslint-disable-next-line n/no-missing-import
const index_js_1 = tslib_1.__importDefault(require("../../lib/debounce-fn/index.js"));
const util_js_1 = require("../util.js");
function convertFeedbackInstanceToEvent(type, feedback) {
    return {
        type: type,
        id: feedback.id,
        feedbackId: feedback.feedbackId,
        controlId: feedback.controlId,
        options: feedback.options,
    };
}
class FeedbackManager {
    #parseVariablesInString;
    #updateFeedbackValues;
    #setFeedbackDefinitions;
    #log;
    #feedbackDefinitions = new Map();
    #feedbackInstances = new Map();
    // Feedback values waiting to be sent
    #pendingFeedbackValues = new Map();
    // Feedbacks currently being checked
    #feedbacksBeingChecked = new Map();
    // while in a context which provides an alternate parseVariablesInString, we should log when the original is called
    #parseVariablesContext;
    get parseVariablesContext() {
        return this.#parseVariablesContext;
    }
    constructor(parseVariablesInString, updateFeedbackValues, setFeedbackDefinitions, log) {
        this.#parseVariablesInString = parseVariablesInString;
        this.#updateFeedbackValues = updateFeedbackValues;
        this.#setFeedbackDefinitions = setFeedbackDefinitions;
        this.#log = log;
    }
    getDefinitionIds() {
        return Array.from(this.#feedbackDefinitions.keys());
    }
    getInstanceIds() {
        return Array.from(this.#feedbackInstances.keys());
    }
    handleUpdateFeedbacks(feedbacks) {
        for (const [id, feedback] of Object.entries(feedbacks)) {
            const existing = this.#feedbackInstances.get(id);
            if (existing && !feedback) {
                // Call unsubscribe
                const definition = this.#feedbackDefinitions.get(existing.feedbackId);
                if (definition?.unsubscribe) {
                    const context = {
                        parseVariablesInString: async (text) => {
                            // No-op, any values parsed here will not be stable
                            return text;
                        },
                    };
                    Promise.resolve(definition.unsubscribe(convertFeedbackInstanceToEvent(definition.type, existing), context)).catch((e) => {
                        this.#log('error', `Feedback unsubscribe failed: ${JSON.stringify(existing)} - ${e?.message ?? e} ${e?.stack}`);
                    });
                }
            }
            if (!feedback || feedback.disabled) {
                // Deleted
                this.#feedbackInstances.delete(id);
            }
            else {
                // TODO module-lib - deep freeze the feedback to avoid mutation?
                this.#feedbackInstances.set(id, { ...feedback });
                // Inserted
                if (!existing) {
                    const definition = this.#feedbackDefinitions.get(feedback.feedbackId);
                    if (definition?.subscribe) {
                        const context = {
                            parseVariablesInString: async (text) => {
                                // No-op, any values parsed here will not be stable
                                return text;
                            },
                        };
                        Promise.resolve(definition.subscribe(convertFeedbackInstanceToEvent(definition.type, feedback), context)).catch((e) => {
                            this.#log('error', `Feedback subscribe failed: ${JSON.stringify(feedback)} - ${e?.message ?? e} ${e?.stack}`);
                        });
                    }
                }
                // update the feedback value
                this.#triggerCheckFeedback(id);
            }
        }
    }
    async handleLearnFeedback(msg) {
        const definition = this.#feedbackDefinitions.get(msg.feedback.feedbackId);
        if (definition && definition.learn) {
            const context = {
                parseVariablesInString: async (text) => {
                    const res = await this.#parseVariablesInString({
                        text: text,
                        controlId: msg.feedback.controlId,
                        actionInstanceId: undefined,
                        feedbackInstanceId: msg.feedback.id,
                    });
                    return res.text;
                },
            };
            const newOptions = await definition.learn({
                id: msg.feedback.id,
                feedbackId: msg.feedback.feedbackId,
                controlId: msg.feedback.controlId,
                options: msg.feedback.options,
                type: definition.type,
            }, context);
            return {
                options: newOptions,
            };
        }
        else {
            // Not supported
            return {
                options: undefined,
            };
        }
    }
    #triggerCheckFeedback(id) {
        const existingRecheck = this.#feedbacksBeingChecked.get(id);
        if (existingRecheck) {
            // Already being checked
            existingRecheck.needsRecheck = true;
            return;
        }
        const feedback0 = this.#feedbackInstances.get(id);
        if (!feedback0)
            return;
        const feedback = feedback0;
        const feedbackCheckStatus = {
            needsRecheck: false,
        };
        // mark it as being checked
        this.#feedbacksBeingChecked.set(id, feedbackCheckStatus);
        Promise.resolve()
            .then(async () => {
            const definition = this.#feedbackDefinitions.get(feedback.feedbackId);
            let value;
            // Calculate the new value for the feedback
            if (definition) {
                // Set this while the promise starts executing
                this.#parseVariablesContext = `Feedback ${feedback.feedbackId} (${id})`;
                const context = {
                    parseVariablesInString: async (text) => {
                        const res = await this.#parseVariablesInString({
                            text: text,
                            controlId: feedback.controlId,
                            actionInstanceId: undefined,
                            feedbackInstanceId: id,
                        });
                        return res.text;
                    },
                };
                switch (definition.type) {
                    case 'boolean':
                        value = definition.callback({
                            ...convertFeedbackInstanceToEvent('boolean', feedback),
                            type: 'boolean',
                        }, context);
                        break;
                    case 'value':
                        value = definition.callback({
                            ...convertFeedbackInstanceToEvent('value', feedback),
                            type: 'value',
                        }, context);
                        break;
                    case 'advanced':
                        value = definition.callback({
                            ...convertFeedbackInstanceToEvent('advanced', feedback),
                            type: 'advanced',
                            image: feedback.image,
                        }, context);
                        break;
                    default:
                        (0, util_js_1.assertNever)(definition);
                        break;
                }
                this.#parseVariablesContext = undefined;
            }
            // Await the value before looking at this.#pendingFeedbackValues, to avoid race conditions
            const resolvedValue = await value;
            this.#pendingFeedbackValues.set(id, {
                id: id,
                controlId: feedback.controlId,
                value: resolvedValue,
            });
            this.#sendFeedbackValues();
        })
            .catch((e) => {
            console.error(`Feedback check failed: ${JSON.stringify(feedback)} - ${e?.message ?? e} ${e?.stack}`);
        })
            .finally(() => {
            // ensure this.#parseVariablesContext is cleared
            this.#parseVariablesContext = undefined;
            // it is no longer being checked
            this.#feedbacksBeingChecked.delete(id);
            // If queued, trigger a check
            if (feedbackCheckStatus.needsRecheck) {
                setImmediate(() => {
                    this.#triggerCheckFeedback(id);
                });
            }
        });
    }
    /**
     * Send pending feedback values (from this.#pendingFeedbackValues) to companion, with a debounce
     */
    #sendFeedbackValues = (0, index_js_1.default)(() => {
        const newValues = this.#pendingFeedbackValues;
        this.#pendingFeedbackValues = new Map();
        // Send the new values back
        if (newValues.size > 0) {
            this.#updateFeedbackValues({
                values: Array.from(newValues.values()),
            });
        }
    }, {
        wait: 5,
        maxWait: 25,
    });
    setFeedbackDefinitions(feedbacks) {
        const hostFeedbacks = [];
        this.#feedbackDefinitions.clear();
        for (const [feedbackId, feedback] of Object.entries(feedbacks)) {
            if (feedback) {
                hostFeedbacks.push({
                    id: feedbackId,
                    name: feedback.name,
                    description: feedback.description,
                    options: (0, base_js_1.serializeIsVisibleFn)(feedback.options),
                    type: feedback.type,
                    defaultStyle: feedback.type === 'boolean' ? feedback.defaultStyle : undefined,
                    hasLearn: !!feedback.learn,
                    learnTimeout: feedback.learnTimeout,
                    showInvert: feedback.type === 'boolean' ? feedback.showInvert : false,
                });
                // Remember the definition locally
                this.#feedbackDefinitions.set(feedbackId, feedback);
            }
        }
        this.#setFeedbackDefinitions({ feedbacks: hostFeedbacks });
    }
    checkFeedbacks(feedbackTypes) {
        const types = new Set(feedbackTypes);
        for (const [id, feedback] of this.#feedbackInstances.entries()) {
            const definition = this.#feedbackDefinitions.get(feedback.feedbackId);
            if (definition) {
                if (types.size === 0 || types.has(feedback.feedbackId)) {
                    // update the feedback value
                    this.#triggerCheckFeedback(id);
                }
            }
        }
    }
    checkFeedbacksById(feedbackIds) {
        for (const id of feedbackIds) {
            // update the feedback value
            this.#triggerCheckFeedback(id);
        }
    }
    subscribeFeedbacks(feedbackIds) {
        let feedbacks = Array.from(this.#feedbackInstances.values());
        const feedbackIdSet = new Set(feedbackIds);
        if (feedbackIdSet.size)
            feedbacks = feedbacks.filter((fb) => feedbackIdSet.has(fb.feedbackId));
        for (const fb of feedbacks) {
            const def = this.#feedbackDefinitions.get(fb.feedbackId);
            if (def?.subscribe) {
                const context = {
                    parseVariablesInString: async (text) => {
                        // No-op, any values parsed here will not be stable
                        return text;
                    },
                };
                Promise.resolve(def.subscribe(convertFeedbackInstanceToEvent(def.type, fb), context)).catch((e) => {
                    this.#log('error', `Feedback subscribe failed: ${JSON.stringify(fb)} - ${e?.message ?? e} ${e?.stack}`);
                });
            }
        }
    }
    unsubscribeFeedbacks(feedbackIds) {
        let feedbacks = Array.from(this.#feedbackInstances.values());
        const feedbackIdSet = new Set(feedbackIds);
        if (feedbackIdSet.size)
            feedbacks = feedbacks.filter((fb) => feedbackIdSet.has(fb.feedbackId));
        for (const fb of feedbacks) {
            const def = this.#feedbackDefinitions.get(fb.feedbackId);
            if (def && def.unsubscribe) {
                const context = {
                    parseVariablesInString: async (text) => {
                        // No-op, any values parsed here will not be stable
                        return text;
                    },
                };
                Promise.resolve(def.unsubscribe(convertFeedbackInstanceToEvent(def.type, fb), context)).catch((e) => {
                    this.#log('error', `Feedback unsubscribe failed: ${JSON.stringify(fb)} - ${e?.message ?? e} ${e?.stack}`);
                });
            }
        }
    }
}
exports.FeedbackManager = FeedbackManager;
//# sourceMappingURL=feedback.js.map