import * as R from "ramda";

import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
import { localStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage";

import {
  deleteFolders,
  getNativeRootPath,
} from "@applicaster/zapp-react-native-bridge/OfflineAssets";

import { log_verbose } from "./logger";

type LayoutCacheStorageData = { id: string; files: FilesList };

type AssetCacheDataStorageData = {
  [key in string]: LayoutCacheStorageData;
};

const CACHE_DATA_KEY = "APP_ASSETS_CACHE_DATA_KEY";
const CACHE_NAMESPACE = "offline_mode";
const ASSET_FOLDERS = ["rivers", "plugins", "cellStyles"];

const getAssetsProps = (assets) => {
  return assets.map((asset) => {
    return {
      file: asset.file,
      url: asset.url,
    };
  });
};

const diff = (arr1, arr2) => {
  return arr1.filter((a1) => !arr2.some((a2) => a1.url === a2.url));
};

export class CacheManager {
  private static _instance: CacheManager;

  public static get instance() {
    if (!CacheManager._instance) {
      CacheManager._instance = new CacheManager();
    }

    return CacheManager._instance;
  }

  private _layoutId: string;
  private _files: { file: string; url: string }[];

  async getFilesToCache(
    layoutId: string,
    assetFiles: FilesList
  ): Promise<{
    removedFiles: FilesList;
    newFiles: FilesList;
    unchangedFiles: FilesList;
  }> {
    if (isTV()) return { removedFiles: [], newFiles: [], unchangedFiles: [] };
    await this.getCacheData(layoutId);

    if (layoutId !== this._layoutId) {
      log_verbose("getFilesToCache: caching new layout id", {
        previousLayout: this._layoutId,
        requestedLayout: layoutId,
        numberOfAssets: assetFiles?.length,
      });

      return {
        removedFiles: this._files || [],
        newFiles: assetFiles,
        unchangedFiles: [],
      };
    }

    const start = performance.now();

    const files = getAssetsProps(assetFiles);
    const removedFiles = diff(this._files, files);
    const newFiles = diff(files, this._files);
    const unchangedFiles = diff(diff(files, removedFiles), newFiles);
    const time = performance.now() - start;

    log_verbose(
      `getFilesToCache: getting cache diff for current layout, took ${time} ms`,
      {
        layoutId,
        removedFiles,
        newFiles,
      }
    );

    return { removedFiles, newFiles, unchangedFiles };
  }

  async clearCache(removedFiles: FilesList) {
    const folders = removedFiles.map((fileObj) => fileObj.file);

    if (folders && folders?.length > 0) {
      log_verbose("getFilesToCache: clearing asset cache folders", { folders });

      await deleteFolders(folders);

      return;
    }

    log_verbose("getFilesToCache: no assets to remove from cache");
  }

  async saveCacheData(layoutId: string, assetFiles: FilesList) {
    const files = getAssetsProps(assetFiles);

    log_verbose("getFilesToCache: saving cache data", { layoutId, files });

    await localStorage.setItem(
      CACHE_DATA_KEY,
      {
        [layoutId]: { id: layoutId, files },
      },
      CACHE_NAMESPACE
    );
  }

  async getCacheData(layoutId: string) {
    const cacheData: Maybe<AssetCacheDataStorageData> =
      await localStorage.getItem(CACHE_DATA_KEY, CACHE_NAMESPACE);

    const layoutCacheData: Maybe<LayoutCacheStorageData> =
      cacheData?.[layoutId] || {};

    this._layoutId = layoutCacheData?.id || null;
    this._files = layoutCacheData?.files || null;
  }

  async deleteAllFiles() {
    const nativeFolder = await getNativeRootPath();

    const assetfolders = R.compose(R.map(R.concat(`${nativeFolder}/`)))(
      ASSET_FOLDERS
    );

    await localStorage.removeItem(CACHE_DATA_KEY, CACHE_NAMESPACE);

    return deleteFolders(assetfolders);
  }
}
