/*
 * Copyright (c) 2010, 2025 BSI Business Systems Integration AG
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 */
import {Event, EventHandler, EventListener, EventSupport, InitModelOf, ObjectModel, ObjectWithType, scout, systems, UiNotificationDo, UiNotificationHandler, UiNotificationPoller} from '../index';

export class UiNotificationSystem implements UiNotificationSystemModel, ObjectWithType {
  declare model: UiNotificationSystemModel;
  declare initModel: UiNotificationSystemModel;

  poller: UiNotificationPoller;
  name: string;
  events: UiNotificationEventSupport;
  objectType: string;

  constructor() {
    this.events = new UiNotificationEventSupport(this);
  }

  init(model: InitModelOf<this>) {
    this.name = model?.name;
  }

  subscribe(topic: string, handler: UiNotificationHandler): JQuery.Promise<string> {
    scout.assertParameter('topic', topic);
    scout.assertParameter('handler', handler);
    this.events.on(topic, handler);
    return this.whenSubscriptionStart(topic).catch(error => {
      this.unsubscribe(topic, handler);
      throw error;
    });
  }

  subscribeOne(topic: string, handler: UiNotificationHandler): JQuery.Promise<string> {
    scout.assertParameter('topic', topic);
    scout.assertParameter('handler', handler);
    this.events.one(topic, handler);
    return this.whenSubscriptionStart(topic).catch(error => {
      this.unsubscribe(topic, handler);
      throw error;
    });
  }

  unsubscribe(topic: string, handler?: UiNotificationHandler) {
    scout.assertParameter('topic', topic);
    this.events.off(topic, handler);
  }

  whenSubscriptionStart(topic: string): JQuery.Promise<string> {
    if (this.poller.notifications.get(topic)?.size) {
      // If there is at least one notification, the subscription has been started successfully.
      // The notification may be the subscriptionStart notification or another one if subscriptionStart notification has been removed because history size exceeded HISTORY_COUNT.
      return $.resolvedPromise(topic);
    }
    let deferred = $.Deferred();
    let subscriptionStartHandler = event => {
      if (event.notification.topic === topic) {
        deferred.resolve(topic);
        this.poller.off('subscriptionStart', subscriptionStartHandler);
        this.poller.off('error', errorHandler);
      }
    };
    let errorHandler = event => {
      deferred.reject(event.error);
      this.poller?.off('subscriptionStart', subscriptionStartHandler);
      this.poller?.off('error', errorHandler);
    };
    this.poller.on('subscriptionStart', subscriptionStartHandler);
    this.poller.on('error', errorHandler);
    return deferred.promise();
  }

  protected _dispatch(notifications: UiNotificationDo[]) {
    for (const notification of notifications) {
      if (!this.events.count(notification.topic)) {
        $.log.isInfoEnabled() && $.log.info('Notification received but no handler registered');
        return;
      }
      this._trigger(notification.topic, notification);
    }
  }

  protected _trigger(type: string, notification: UiNotificationDo) {
    let event = new Event({
      id: notification.id,
      topic: notification.topic,
      creationTime: notification.creationTime,
      message: notification.message
    });
    event.source = this;
    this.events.trigger(type, event);
  }

  updatePoller() {
    let topics = this.events.types();
    let poller = this.poller;
    if (topics.length > 0 && !poller) {
      // First topic added -> create poller
      let endpointUrl = systems.getOrCreate(this.name).getEndpointUrl('ui-notifications', 'ui-notifications');
      poller = scout.create(UiNotificationPoller, {
        system: this,
        url: endpointUrl
      });
      poller.on('notifications', event => this._dispatch(event.notifications));
      this.poller = poller;
    }

    if (!poller) {
      // No topics registered and no poller started -> do nothing
      return;
    }

    // Update poller with new topics
    poller.setTopics(topics);
    if (poller.topics.length === 0) {
      // Last topic removed -> stop poller
      poller.stop();
      this.poller = null;
    } else {
      poller.restart();
    }
  }

  destroy() {
    this.poller?.stop();
    this.poller = null;
  }
}

export class UiNotificationEventSupport extends EventSupport {
  system: UiNotificationSystem;

  constructor(system: UiNotificationSystem) {
    super();
    this.system = system;
  }

  override on(type: string, func: EventHandler, origFunc?: EventHandler): EventListener {
    let listener = super.on(type, func, origFunc);
    this.system.updatePoller();
    return listener;
  }

  override off(type: string, func?: EventHandler) {
    super.off(type, func);
    this.system.updatePoller();
  }
}

export interface UiNotificationSystemModel extends ObjectModel<UiNotificationSystem> {
  name?: string;
}
