/*
 * ******************************************************************
 * Copyright (c) 2024 Broadcom. All Rights Reserved.
 * Broadcom Confidential. The term "Broadcom" refers to Broadcom Inc.
 * and/or its subsidiaries.
 * ******************************************************************
 */
import { BehaviorSubject, filter, map, mergeMap, Observable, of, Subscription } from 'rxjs';
import { EntityActionExtensionMenuItemClickEvent } from "../extensibility-framework/common/extensibility/entity-action/entity-action-extension-menu.component";
import { EntityActionComponentContainer } from "../extensibility-framework/common/extensibility/extension-components.directive";
import { VM_ACTION_COMPONENT_SCOPE } from "../extensibility-framework/common/extensibility/entity-action/entity-action.extension-point";
import { ActionItem } from "../extensibility-framework/common/extensibility/vcd-common-components-actions";
import { EntityActionExtensionService } from "../extensibility-framework/common/extensibility/entity-action/entity-action-extension.service";
import { CORE_UI_CUSTOM_EVENTS } from "../extensibility-framework/common/extensibility/extension-container/core-ui-events";
import { Injectable, OnDestroy } from "@angular/core";

@Injectable()
export class VmActionExtensionConsumer implements OnDestroy {
  VM_ACTION_COMPONENT_SCOPE = VM_ACTION_COMPONENT_SCOPE;

  /**
   * List of extension actions (menus) per entity id or other unique identifier.
   */
  private extensionActionsPerEntityIdSbj: {
    [entityId: string]: BehaviorSubject<ActionItem[]>
  } = {};
  private entityIdSubs: {
    [entityId: string]: Subscription;
  } = {}

  private loadExtensionActionsSubscription: Subscription = new Subscription();
  private menuItemClickSubscription: Subscription = new Subscription();

  private localEntityActionExtensionComponents: EntityActionComponentContainer[] = [];

  constructor(
    private entityActionExtensionService: EntityActionExtensionService,
  ) {
    this.onExtensionAction();
  }

  ngOnDestroy(): void {
    if (this.loadExtensionActionsSubscription) {
      this.loadExtensionActionsSubscription.unsubscribe();
    }

    if (this.menuItemClickSubscription) {
      this.menuItemClickSubscription.unsubscribe();
    }

    const extensionActionsSbjs = Object.keys(this.extensionActionsPerEntityIdSbj);
    if (extensionActionsSbjs.length) {
      extensionActionsSbjs.forEach((entityId) => {
        this.extensionActionsPerEntityIdSbj[entityId].complete();
      });
    }

    const extensionActionsChangeSubscriptionIds = Object.keys(this.entityIdSubs);
    if (extensionActionsChangeSubscriptionIds.length) {
      extensionActionsChangeSubscriptionIds.forEach((entityId) => {
        const subscription = this.entityIdSubs[entityId];
        if (subscription) {
          subscription.unsubscribe();
        }
      });
    }
  }

  set entityActionExtensionComponents(entityActionExtensionComponents: EntityActionComponentContainer[]) {
    this.localEntityActionExtensionComponents = entityActionExtensionComponents;

    Object.keys(this.extensionActionsPerEntityIdSbj).forEach((id) => {
      this.subscribeToExtensionActionChanges(id);
    });
  }

  get entityActionExtensionComponents(): EntityActionComponentContainer[] {
    return this.localEntityActionExtensionComponents;
  }

  /**
   * This is called whenever an extension menu event is bubbled up.
   */
  performExtensionEntityAction(event: EntityActionExtensionMenuItemClickEvent): void {
    event.component
      .performAction(event.menuItemUrn, event.entityUrn)
      .toPromise()
      .then((result) => {
        if (!result) {
          return;
        }
        if (result.refreshRequested) {
          this.refresh();
        }
      })
      .catch((err) => {
        const msg = `Error performing entity action ${event.menuItemUrn} on ` + `entity ${event.entityUrn}`;
        console.error(msg, err);
        this.handleError(msg);
      });

    window.dispatchEvent(new CustomEvent<void>(CORE_UI_CUSTOM_EVENTS.TRIGGER_DETECT_CHANGES));
  }

  /**
   * This is called whenever the extension components are updated in the view.
   */
  onEntityActionExtensionComponentsChange(components: EntityActionComponentContainer[]): void {
    this.entityActionExtensionComponents = [...this.entityActionExtensionComponents, ...components];
  }

  /**
   * This is called to refresh the component.
   */
  refresh(): void {}

  /**
   * This is called to handle action error
   */
  handleError(errMsg: string): void {}

  getActionsForEntity(entityId: Observable<string>): Observable<ActionItem[]> {
    if (this.loadExtensionActionsSubscription) {
      this.loadExtensionActionsSubscription.unsubscribe();
    }

    return entityId
      .pipe(
        mergeMap((id) => {
          this.extensionActionsPerEntityIdSbj[id] = new BehaviorSubject<ActionItem[]>([]);

          this.subscribeToExtensionActionChanges(id);

          return this.extensionActionsPerEntityIdSbj[id].asObservable();
        }),
      );
  }

  private onExtensionAction() {
    this.menuItemClickSubscription = this.entityActionExtensionService.menuItemClick
      .pipe(
        filter((event) => {
          return event.scope === VM_ACTION_COMPONENT_SCOPE;
        }),
      )
      .subscribe((event: EntityActionExtensionMenuItemClickEvent) => {
        this.performExtensionEntityAction(event);
      });
  }

  /**
   * Subscribes to changes in the list of actions of the current list of available extension components.
   * 
   * Extension component is 3rd party angular application that is not related to the current angular application.
   * Extension component returns list of menu items and handlers which trigger specific actions, e.g open dialog.
   * 
   * You subscribe to the changes of those extension components in order to retrive the list of action items (menus)
   * and render them in your application (hosting application).
   * 
   * @param entityId vm entity id, urn or other unique identifier.
   */
  private subscribeToExtensionActionChanges(entityId: string) {
    const sub = this.entityActionExtensionService.getExtensionActionItems(entityId, this.entityActionExtensionComponents).pipe(
      map((actions) => {
        this.extensionActionsPerEntityIdSbj[entityId].next(actions);
      })
    ).subscribe();

    if (this.entityIdSubs[entityId]) {
      this.entityIdSubs[entityId].unsubscribe();
    }
    this.entityIdSubs[entityId] = sub;
  }
}
