import { StrictEffect, select, call } from 'redux-saga/effects';
import { Account, AccountID, MainAccountID } from '../../../domain/account';
import { RootReducerState } from '../../../domain/common';
import {
  selectEsploraForNetwork,
  selectEsploraURL,
  selectNetwork,
} from '../selectors/app.selector';
import {
  selectAccount,
  selectAllAccountsIDs,
  selectUpdaterIsLoading,
  selectUtxos,
} from '../selectors/wallet.selector';
import { isBufferLike, reviver } from '../../utils/browser-storage-converters';
import { NetworkString } from 'ldk';
import { Channel, channel, buffers } from 'redux-saga';

export type SagaGenerator<ReturnType = void, YieldType = any> = Generator<
  StrictEffect,
  ReturnType,
  YieldType
>;

// customSagaParser is a recursive function that parses the result of a saga selector
// this is a trick due to the fact that the state requested by saga is parsed by JSON.parse
// which does not include our custom Buffer serialization format @see browser-storage-converters.ts file.
function customSagaParser(obj: any): any {
  if (typeof obj === 'object') {
    if (isBufferLike(obj)) {
      return reviver('', obj);
    }

    for (const key in obj) {
      // eslint-disable-next-line no-prototype-builtins
      if (obj.hasOwnProperty(key)) {
        obj[key] = customSagaParser(obj[key]);
      }
    }
  }

  return obj;
}

// create a saga "selector" (a generator) from a redux selector function
export function newSagaSelector<R>(selectorFn: (state: RootReducerState) => R) {
  return function* (): SagaGenerator<R> {
    const result = yield select(selectorFn);
    return customSagaParser(result);
  };
}

// redux-saga does not handle async generator
// this is useful to pass through this limitation
export function* processAsyncGenerator<NextType>(
  asyncGenerator: AsyncGenerator<NextType>,
  onNext: (n: NextType) => SagaGenerator,
  onDone?: () => SagaGenerator
): SagaGenerator<void, IteratorYieldResult<NextType>> {
  const next = () => asyncGenerator.next();
  let n = yield call(next);
  while (!n.done) {
    yield* onNext(n.value);
    n = yield call(next);
  }

  if (onDone && n.done) {
    yield* onDone();
  }
}

export function* createChannel<T>(): SagaGenerator<Channel<T>> {
  return yield call(channel, buffers.sliding(10));
}

export const selectNetworkSaga = newSagaSelector(selectNetwork);
export const selectAllAccountsIDsSaga = newSagaSelector(selectAllAccountsIDs);
export const selectExplorerSaga = newSagaSelector(selectEsploraURL);
export const selectUpdaterIsLoadingSaga = newSagaSelector(selectUpdaterIsLoading);
export const selectAllUnspentsSaga = newSagaSelector(selectUtxos(MainAccountID));

export function selectAccountSaga(accountID: AccountID): SagaGenerator<Account | undefined> {
  return newSagaSelector(selectAccount(accountID))();
}

export function selectExplorerSagaForNet(net: NetworkString): SagaGenerator<string> {
  return newSagaSelector(selectEsploraForNetwork(net))();
}