import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
import { bridgeLogger } from "../logger";
import { BehaviorSubject } from "rxjs";
import { localStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage";
import { getNamespaceAndKey } from "@applicaster/zapp-react-native-utils/appUtils/contextKeysManager/utils";

export const { log_verbose, log_debug, log_warning, log_info, log_error } =
  createLogger({
    category: "StorageMultiSelectProvider",
    subsystem: "zapp-react-native-bridge",
    parent: bridgeLogger,
  });

export interface MultiSelectProvider {
  getObservable(): BehaviorSubject<string[]>;

  addItem(item: string): Promise<void>;

  addItems(items: string[]): Promise<void>;

  setSelectedItems(items: string[]): Promise<void>;

  removeItem(item: string): Promise<void>;

  removeItems(items: string[]): Promise<void>;

  removeAllItems(): Promise<void>;

  getSelectedItems(): string[];

  getSelectedAsync(): Promise<string[]>;
}

export class StorageMultiSelectProvider implements MultiSelectProvider {
  // TODO: Unsubscribe and remove when there is no listeners
  private static multiSelectProviders: Record<
    string,
    StorageMultiSelectProvider
  > = {};

  public static getProvider(keyNamespace: string): StorageMultiSelectProvider {
    if (!this.multiSelectProviders[keyNamespace]) {
      this.multiSelectProviders[keyNamespace] = new StorageMultiSelectProvider(
        keyNamespace
      );
    }

    return this.multiSelectProviders[keyNamespace];
  }

  private itemSubject: BehaviorSubject<string[] | null>;

  private readonly key: string;
  private readonly namespace: string;

  private constructor(keyNamespace: string) {
    const { namespace, key } = getNamespaceAndKey(keyNamespace);

    if (!key) {
      throw new Error("StorageMultiSelectProvider: Key is required");
    }

    this.key = key;
    this.namespace = namespace;
    localStorage.addListener?.({ key, namespace }, this.reloadItems);

    this.itemSubject = new BehaviorSubject([]);

    void this.getSelectedAsync();
    log_debug("StorageMultiSelectProvider: Initializing");
  }

  private reloadItems = async ({ value }: StorageListenerArgs) => {
    const selectedItems = value ? value.split(",") : [];

    log_debug(
      `reloadItems: request to reload items for value: ${value}`,
      selectedItems
    );

    this.itemSubject.next(selectedItems);
  };

  public getObservable = (): BehaviorSubject<string[]> => this.itemSubject;

  private async getSetOfCurrentItems() {
    const currentResult: string = await localStorage.getItem(
      this.key,
      this.namespace
    );

    return new Set(currentResult ? currentResult.split(",") : []);
  }

  private async updateItemsAndNotifyObservers(currentItems: Set<string>) {
    const arrayOfItems = Array.from(currentItems);
    const newValue = arrayOfItems.join(",");
    await localStorage.setItem(this.key, newValue, this.namespace);

    this.itemSubject.next(arrayOfItems);
  }

  public addItem = async (item: string): Promise<void> => {
    const currentItems = await this.getSetOfCurrentItems();
    currentItems.add(item);
    log_debug(`addItem: Adding new item: ${item}`);

    await this.updateItemsAndNotifyObservers(currentItems);
  };

  public addItems = async (items: string[]): Promise<void> => {
    const currentItems = await this.getSetOfCurrentItems();

    items.forEach((item) => currentItems.add(item));

    log_debug(
      `addItems: Adding new items: ${items.join(
        ","
      )}, current items: ${Array.from(currentItems).join(",")}`
    );

    await this.updateItemsAndNotifyObservers(currentItems);
  };

  public removeItem = async (item: string): Promise<void> => {
    const currentItems = await this.getSetOfCurrentItems();
    currentItems.delete(item);

    log_debug(
      `removeItem: Removing item: ${item}, current items: ${Array.from(
        currentItems
      ).join(",")}`
    );

    await this.updateItemsAndNotifyObservers(currentItems);
  };

  public removeItems = async (items: string[]): Promise<void> => {
    const currentItems = await this.getSetOfCurrentItems();

    items.forEach((item) => currentItems.delete(item));

    log_debug(
      `removeItems: Removing items: ${items.join(
        ","
      )}, current items: ${Array.from(currentItems).join(",")}`
    );

    await this.updateItemsAndNotifyObservers(currentItems);
  };

  public removeAllItems = async (): Promise<void> => {
    await localStorage.removeItem(this.key, this.namespace);
    log_debug(`removeAllItems: Removing all items, current items: ${[]}`);

    this.itemSubject.next([]);
  };

  public getSelectedAsync = async (): Promise<string[]> => {
    const value = await localStorage.getItem(this.key, this.namespace);
    const selectedItems = value ? value.split(",") : [];
    this.itemSubject.next(selectedItems);

    return selectedItems;
  };

  public getSelectedItems = (): string[] => {
    return this.itemSubject.getValue();
  };

  setSelectedItems = async (items: string[]): Promise<void> => {
    log_debug(`setSelectedItems: Setting selected items: ${items.join(",")}`);

    await this.updateItemsAndNotifyObservers(new Set(items));
  };
}
