import Logger from '@joplin/utils/Logger';
import Alarm from '../models/Alarm';

import Note from '../models/Note';

export default class AlarmService {

	// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
	private static driver_: any;
	private static logger_: Logger;
	// private static inAppNotificationHandler_:any;

	// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
	public static setDriver(v: any) {
		this.driver_ = v;

		if (this.driver_.setService) this.driver_.setService(this);
	}

	public static driver() {
		if (!this.driver_) throw new Error('AlarmService driver not set!');
		return this.driver_;
	}

	public static setLogger(v: Logger) {
		this.logger_ = v;
	}

	public static logger() {
		return this.logger_;
	}

	// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
	public static setInAppNotificationHandler(v: any) {
		// this.inAppNotificationHandler_ = v;
		if (this.driver_.setInAppNotificationHandler) this.driver_.setInAppNotificationHandler(v);
	}

	public static async garbageCollect() {
		this.logger().info('Garbage collecting alarms...');

		// Delete alarms that have already been triggered
		await Alarm.deleteExpiredAlarms();

		// Delete alarms that correspond to non-existent notes
		const alarmIds = await Alarm.alarmIdsWithoutNotes();
		for (let i = 0; i < alarmIds.length; i++) {
			this.logger().info(`Clearing notification for non-existing note. Alarm ${alarmIds[i]}`);
			await this.driver().clearNotification(alarmIds[i]);
		}
		await Alarm.batchDelete(alarmIds, { sourceDescription: 'AlarmService/garbageCollect' });
	}

	// When passing a note, make sure it has all the required properties
	// (better to pass a complete note or else just the ID)
	// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
	public static async updateNoteNotification(noteOrId: any, isDeleted = false) {
		try {
			let note = null;
			let noteId = null;

			if (typeof noteOrId === 'object') {
				note = noteOrId;
				noteId = note.id;
			} else {
				note = await Note.load(noteOrId);
				noteId = note ? note.id : null;
			}

			if (!note && !isDeleted) return;

			const driver = this.driver();

			let alarm = noteId ? await Alarm.byNoteId(noteId) : null;
			let clearAlarm = false;

			if (isDeleted || !Note.needAlarm(note) || (alarm && alarm.trigger_time !== note.todo_due)) {
				clearAlarm = !!alarm;
			}

			if (!clearAlarm && alarm) {
				// Alarm already exists and set at the right time

				// For persistent notifications (those that stay active after the app has been closed, like on mobile), if we have
				// an alarm object we can be sure that the notification has already been set, so there's nothing to do.
				// For non-persistent notifications however we need to check that the notification has been set because, for example,
				// if the app has just started the notifications need to be set again. so we do this below.
				if (!driver.hasPersistentNotifications() && !driver.notificationIsSet(alarm.id)) {
					const notification = await Alarm.makeNotification(alarm, note);
					this.logger().info(`Scheduling (non-persistent) notification for note ${note.id}`, notification);
					driver.scheduleNotification(notification);
				}

				return;
			}

			if (clearAlarm) {
				this.logger().info(`Clearing notification for note ${noteId}`);
				await driver.clearNotification(alarm.id);
				await Alarm.delete(alarm.id, { sourceDescription: 'AlarmService/clearAlarm' });
			}

			if (isDeleted || !Note.needAlarm(note)) return;

			await Alarm.save({
				note_id: note.id,
				trigger_time: note.todo_due,
			});

			// Reload alarm to get its ID
			alarm = await Alarm.byNoteId(note.id);

			const notification = await Alarm.makeNotification(alarm, note);
			this.logger().info(`Scheduling notification for note ${note.id}`, notification);
			await driver.scheduleNotification(notification);
		} catch (error) {
			this.logger().error('Could not update notification', error);
		}
	}

	public static async updateAllNotifications() {
		this.logger().info('Updating all notifications...');

		await this.garbageCollect();

		const dueNotes = await Note.dueNotes();
		for (let i = 0; i < dueNotes.length; i++) {
			await this.updateNoteNotification(dueNotes[i]);
		}
	}
}