"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const eventManager_1 = require("../eventManager");
const shim_1 = require("../shim");
const Setting_1 = require("../models/Setting");
const notifier = require('node-notifier');
const shouldUseElectronNotifications = () => {
    return shim_1.default.isElectron();
};
class AlarmServiceDriverNode {
    constructor(options) {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
        this.notifications_ = {};
        // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
        this.service_ = null;
        // Note: appName is required to get the notification to work. It must be the same as the appId defined in package.json
        // https://github.com/mikaelbr/node-notifier/issues/144#issuecomment-319324058
        this.appName_ = options.appName;
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
    setService(s) {
        this.service_ = s;
    }
    logger() {
        return this.service_.logger();
    }
    hasPersistentNotifications() {
        return false;
    }
    notificationIsSet(id) {
        return id in this.notifications_;
    }
    clearNotification(id) {
        if (!this.notificationIsSet(id))
            return;
        shim_1.default.clearTimeout(this.notifications_[id].timeoutId);
        delete this.notifications_[id];
    }
    displayDefaultNotification(notification) {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
        const o = {
            appID: this.appName_,
            title: notification.title,
            icon: `${shim_1.default.electronBridge().electronApp().buildDir()}/icons/512x512.png`,
        };
        if ('body' in notification)
            o.message = notification.body;
        // Message is required on Windows 7 however we don't want to repeat the title so
        // make it an empty string.
        // https://github.com/laurent22/joplin/issues/2144
        if (!o.message)
            o.message = '-';
        this.logger().info('AlarmServiceDriverNode::scheduleNotification: Triggering notification (default):', o);
        // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
        notifier.notify(o, (error, response) => {
            this.logger().info('AlarmServiceDriverNode::scheduleNotification: node-notifier response:', error, response);
        });
    }
    displayElectronNotification(notification) {
        // On Electron, node-notifier is broken:
        //
        // https://github.com/mikaelbr/node-notifier/issues/352
        //
        // However we can use the native browser notification as described
        // there:
        //
        // https://www.electronjs.org/docs/tutorial/notifications
        //
        try {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
            const options = {
                body: notification.body ? notification.body : '-',
                // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
                onerror: (error) => {
                    this.logger().error('AlarmServiceDriverNode::displayMacNotification', error);
                },
            };
            this.logger().info('AlarmServiceDriverNode::displayMacNotification: Triggering notification (electron):', notification.title, options);
            new Notification(notification.title, options);
        }
        catch (error) {
            this.logger().error('AlarmServiceDriverNode::displayMacNotification', error);
        }
    }
    async checkPermission() {
        if (shouldUseElectronNotifications()) {
            this.logger().info(`AlarmServiceDriverNode::checkPermission: Permission in settings is "${Setting_1.default.value('notificationPermission')}"`);
            if (Setting_1.default.value('notificationPermission') !== '')
                return Setting_1.default.value('notificationPermission');
            // In theory `Notification.requestPermission()` should be used to
            // ask for permission but in practice this API is unreliable. In
            // particular, it returns "granted" immediately even when
            // notifications definitely aren't allowed (and creating a new
            // notification would fail).
            //
            // Because of that, our approach is to trigger a notification, which
            // should prompt macOS to ask for permission. Once this is done we
            // manually save the result in the settings. Of course it means that
            // if permission is changed afterwards, for example from the
            // notification center, we won't know it and notifications will
            // fail.
            //
            // All this means that for now this checkPermission function always
            // returns "granted" and the setting has only two values: "granted"
            // or "" (which means we need to do the check permission trick).
            //
            // The lack of "denied" value is acceptable in our context because
            // if a user doesn't want notifications, they can simply not set
            // alarms.
            // Notification permissions are only needed on MacOS:
            if (shim_1.default.isMac()) {
                new Notification('Checking permissions...', {
                    body: 'Permission has been granted',
                });
            }
            Setting_1.default.setValue('notificationPermission', 'granted');
        }
        return 'granted';
    }
    async scheduleNotification(notification) {
        const now = Date.now();
        const interval = notification.date.getTime() - now;
        if (interval < 0)
            return;
        if (isNaN(interval)) {
            throw new Error(`Trying to create a notification from an invalid object: ${JSON.stringify(notification)}`);
        }
        const permission = await this.checkPermission();
        if (permission !== 'granted') {
            this.logger().info(`AlarmServiceDriverNode::scheduleNotification: Notification ${notification.id}: Cancelled because permission was not granted.`);
            return;
        }
        this.logger().info(`AlarmServiceDriverNode::scheduleNotification: Notification ${notification.id} with interval: ${interval}ms`);
        if (this.notifications_[notification.id])
            shim_1.default.clearTimeout(this.notifications_[notification.id].timeoutId);
        let timeoutId = null;
        // Note: setTimeout will break for values larger than Number.MAX_VALUE - in which case the timer
        // will fire immediately. So instead, if the interval is greater than a set max, reschedule after
        // that max interval.
        // https://stackoverflow.com/questions/3468607/why-does-settimeout-break-for-large-millisecond-delay-values/3468699
        const maxInterval = 60 * 60 * 1000;
        if (interval >= maxInterval) {
            this.logger().info(`AlarmServiceDriverNode::scheduleNotification: Notification interval is greater than ${maxInterval}ms - will reschedule in ${maxInterval}ms`);
            timeoutId = shim_1.default.setTimeout(() => {
                if (!this.notifications_[notification.id]) {
                    this.logger().info(`AlarmServiceDriverNode::scheduleNotification: Notification ${notification.id} has been deleted - not rescheduling it`);
                    return;
                }
                void this.scheduleNotification(this.notifications_[notification.id]);
            }, maxInterval);
        }
        else {
            timeoutId = shim_1.default.setTimeout(() => {
                if (shouldUseElectronNotifications()) {
                    this.displayElectronNotification(notification);
                }
                else {
                    this.displayDefaultNotification(notification);
                }
                this.clearNotification(notification.id);
                eventManager_1.default.emit(eventManager_1.EventName.NoteAlarmTrigger, { noteId: notification.noteId });
            }, interval);
        }
        this.notifications_[notification.id] = Object.assign({}, notification);
        this.notifications_[notification.id].timeoutId = timeoutId;
    }
}
exports.default = AlarmServiceDriverNode;
//# sourceMappingURL=AlarmServiceDriverNode.js.map