import type { Pinia } from 'pinia';

import type { App } from 'vue';

import { createPinia } from 'pinia';
import SecureLS from 'secure-ls';

import { createSessionAuthStorage } from './session-auth';

let pinia: Pinia;
let sessionLogin: (<T>(action: (token: null | string) => Promise<T>) => Promise<T>) | undefined;

/** Serialize password login across tabs when the browser supports Web Locks. */
export async function withSessionAuthLogin<T>(action: (token: null | string) => Promise<T>): Promise<T> {
  return sessionLogin ? sessionLogin(action) : action(null);
}

export interface InitStoreOptions {
  // 子应用是否共用token
  isCommonToken?: boolean;
  /**
   * @zh_CN 应用名,由于 @vben/stores 是公用的，后续可能有多个app，为了防止多个app缓存冲突，可在这里配置应用名,应用名将被用于持久化的前缀
   */
  namespace: string;
  /** Keep authentication in the page session and share it with live tabs. */
  sessionAuth?: boolean;
}

/**
 * @zh_CN 初始化pinia
 */
export async function initStores(app: App, options: InitStoreOptions) {
  const { createPersistedState } = await import('pinia-plugin-persistedstate');
  pinia = createPinia();
  let { namespace } = options;
  const ls = new SecureLS({
    encodingType: 'aes',
    encryptionSecret: import.meta.env.VITE_APP_STORE_SECURE_KEY,
    isCompression: true,
    // @ts-ignore secure-ls does not have a type definition for this
    metaKey: `${namespace}-secure-meta`,
  });
  const authNamespace = options.isCommonToken ? namespace.split('_')[0]! : namespace;
  const authKey = `${authNamespace}-core-access`;
  let sessionAuth: ReturnType<typeof createSessionAuthStorage> | undefined;
  sessionLogin = undefined;
  if (options.sessionAuth) {
    const sessionLs = import.meta.env.DEV
      ? undefined
      : new SecureLS({
          encodingType: 'aes',
          encryptionSecret: import.meta.env.VITE_APP_STORE_SECURE_KEY,
          isCompression: true,
          // @ts-ignore secure-ls supports storage and metaKey at runtime.
          storage: sessionStorage,
          metaKey: `${authNamespace}-session-secure-meta`,
        });
    sessionAuth = createSessionAuthStorage({
      key: authKey,
      session: sessionLs
        ? {
            getItem: (key) => sessionLs.get(key),
            setItem: (key, value) => sessionLs.set(key, value),
          }
        : sessionStorage,
      persistent: localStorage,
      channel: typeof BroadcastChannel === 'undefined' ? undefined : new BroadcastChannel(`${authKey}-session`),
      events: window,
      // Rebuild user information, routes and permissions for the new identity.
      onRemoteChange: () => window.location.reload(),
    });
    await sessionAuth.start();
    const authentication = sessionAuth;
    sessionLogin = async (action) => {
      const run = async () => {
        await authentication.start();
        return action(authentication.getAccessToken());
      };
      return navigator.locks ? navigator.locks.request(`${authKey}-login`, run) : run();
    };
    app.onUnmount(() => sessionAuth?.dispose());
    import.meta.hot?.dispose(() => sessionAuth?.dispose());
  }
  pinia.use(
    createPersistedState({
      // key $appName-$store.id
      key: (storeKey) => {
        if (options.isCommonToken && storeKey == 'core-access' && namespace.indexOf('_') > 0) {
          namespace = namespace.slice(0, namespace.indexOf('_'));
        }
        return `${namespace}-${storeKey}`;
      },
      storage: {
        getItem(key) {
          if (sessionAuth && key === authKey) return sessionAuth.getItem();
          return import.meta.env.DEV ? localStorage.getItem(key) : ls.get(key);
        },
        setItem(key, value) {
          if (sessionAuth && key === authKey) {
            sessionAuth.setItem(key, value);
          } else if (import.meta.env.DEV) {
            localStorage.setItem(key, value);
          } else {
            ls.set(key, value);
          }
        },
      },
    }),
  );
  app.use(pinia);
  return pinia;
}

export function resetAllStores() {
  if (!pinia) {
    console.error('Pinia is not installed');
    return;
  }
  const allStores = (pinia as any)._s;
  for (const [_key, store] of allStores) {
    if (!_key.includes('jnpfUniver')) store?.$reset();
  }
}

export function getAllStores() {
  if (!pinia) {
    console.error('Pinia is not installed');
    return [];
  }
  const allStores = (pinia as any)._s;
  return allStores;
}
