// @flow
import * as ACTIONS from 'constants/action_types';
import * as ABANDON_STATES from 'constants/abandon_states';
import Lbry from 'lbry';
import { normalizeURI } from 'util/lbryURI';
import { doToast } from 'redux/actions/notifications';
import {
  selectMyClaimsRaw,
  selectResolvingUris,
  selectClaimsByUri,
  selectMyChannelClaims,
  selectPendingClaimsById,
  selectClaimIsMine,
} from 'redux/selectors/claims';

import { doFetchTxoPage } from 'redux/actions/wallet';
import { selectSupportsByOutpoint } from 'redux/selectors/wallet';
import { creditsToString } from 'util/format-credits';
import { batchActions } from 'util/batch-actions';
import { createNormalizedClaimSearchKey } from 'util/claim';
import { PAGE_SIZE } from 'constants/claim';
import { makeSelectClaimIdsForCollectionId } from 'redux/selectors/collections';
import { doFetchItemsInCollections } from 'redux/actions/collections';

let onChannelConfirmCallback;
let checkPendingInterval;

export function doResolveUris(
  uris: Array<string>,
  returnCachedClaims: boolean = false,
  resolveReposts: boolean = true
) {
  return (dispatch: Dispatch, getState: GetState) => {
    const normalizedUris = uris.map(normalizeURI);
    const state = getState();

    const resolvingUris = selectResolvingUris(state);
    const claimsByUri = selectClaimsByUri(state);
    const urisToResolve = normalizedUris.filter((uri) => {
      if (resolvingUris.includes(uri)) {
        return false;
      }

      return returnCachedClaims ? !claimsByUri[uri] : true;
    });

    if (urisToResolve.length === 0) {
      return;
    }

    const options: { include_is_my_output?: boolean, include_purchase_receipt: boolean } = {
      include_purchase_receipt: true,
    };

    if (urisToResolve.length === 1) {
      options.include_is_my_output = true;
    }
    dispatch({
      type: ACTIONS.RESOLVE_URIS_STARTED,
      data: { uris: normalizedUris },
    });

    const resolveInfo: {
      [string]: {
        stream: ?StreamClaim,
        channel: ?ChannelClaim,
        claimsInChannel: ?number,
        collection: ?CollectionClaim,
      },
    } = {};

    const collectionIds: Array<string> = [];

    return Lbry.resolve({ urls: urisToResolve, ...options }).then(async (result: ResolveResponse) => {
      let repostedResults = {};
      const repostsToResolve = [];
      const fallbackResolveInfo = {
        stream: null,
        claimsInChannel: null,
        channel: null,
      };

      function processResult(result, resolveInfo = {}, checkReposts = false) {
        Object.entries(result).forEach(([uri, uriResolveInfo]) => {
          // Flow has terrible Object.entries support
          // https://github.com/facebook/flow/issues/2221
          if (uriResolveInfo) {
            if (uriResolveInfo.error) {
              // $FlowFixMe
              resolveInfo[uri] = { ...fallbackResolveInfo };
            } else {
              if (checkReposts) {
                if (uriResolveInfo.reposted_claim) {
                  // $FlowFixMe
                  const repostUrl = uriResolveInfo.reposted_claim.permanent_url;
                  if (!resolvingUris.includes(repostUrl)) {
                    repostsToResolve.push(repostUrl);
                  }
                }
              }
              let result = {};
              if (uriResolveInfo.value_type === 'channel') {
                result.channel = uriResolveInfo;
                // $FlowFixMe
                result.claimsInChannel = uriResolveInfo.meta.claims_in_channel;
              } else if (uriResolveInfo.value_type === 'collection') {
                result.collection = uriResolveInfo;
                // $FlowFixMe
                collectionIds.push(uriResolveInfo.claim_id);
              } else {
                result.stream = uriResolveInfo;
                if (uriResolveInfo.signing_channel) {
                  result.channel = uriResolveInfo.signing_channel;
                  result.claimsInChannel =
                    (uriResolveInfo.signing_channel.meta && uriResolveInfo.signing_channel.meta.claims_in_channel) || 0;
                }
              }
              // $FlowFixMe
              resolveInfo[uri] = result;
            }
          }
        });
      }
      processResult(result, resolveInfo, resolveReposts);

      if (repostsToResolve.length) {
        dispatch({
          type: ACTIONS.RESOLVE_URIS_STARTED,
          data: { uris: repostsToResolve, debug: 'reposts' },
        });
        repostedResults = await Lbry.resolve({ urls: repostsToResolve, ...options });
      }
      processResult(repostedResults, resolveInfo);

      dispatch({
        type: ACTIONS.RESOLVE_URIS_COMPLETED,
        data: { resolveInfo },
      });

      if (collectionIds.length) {
        dispatch(doFetchItemsInCollections({ collectionIds: collectionIds, pageSize: 5 }));
      }

      return result;
    });
  };
}

export function doResolveUri(uri: string) {
  return doResolveUris([uri]);
}

export function doFetchClaimListMine(
  page: number = 1,
  pageSize: number = 99999,
  resolve: boolean = true,
  filterBy: Array<string> = []
) {
  return (dispatch: Dispatch) => {
    dispatch({
      type: ACTIONS.FETCH_CLAIM_LIST_MINE_STARTED,
    });

    let claimTypes = ['stream', 'repost'];
    if (filterBy && filterBy.length !== 0) {
      claimTypes = claimTypes.filter((t) => filterBy.includes(t));
    }

    // $FlowFixMe
    Lbry.claim_list({
      page: page,
      page_size: pageSize,
      claim_type: claimTypes,
      resolve,
    }).then((result: StreamListResponse) => {
      dispatch({
        type: ACTIONS.FETCH_CLAIM_LIST_MINE_COMPLETED,
        data: {
          result,
          resolve,
        },
      });
    });
  };
}

export function doFetchAllClaimListMine() {
  return (dispatch: Dispatch) => {
    dispatch({
      type: ACTIONS.FETCH_ALL_CLAIM_LIST_MINE_STARTED,
    });

    // $FlowFixMe
    Lbry.claim_list({
      page: 1,
      page_size: 99999,
      claim_type: ['stream', 'repost'],
      resolve: true,
    }).then((result: StreamListResponse) => {
      dispatch({
        type: ACTIONS.FETCH_ALL_CLAIM_LIST_MINE_COMPLETED,
        data: {
          result,
        },
      });
    });
  };
}

export function doAbandonTxo(txo: Txo, cb: (string) => void) {
  return (dispatch: Dispatch) => {
    if (cb) cb(ABANDON_STATES.PENDING);
    const isClaim = txo.type === 'claim';
    const isSupport = txo.type === 'support' && txo.is_my_input === true;
    const isTip = txo.type === 'support' && txo.is_my_input === false;

    const data = isClaim ? { claimId: txo.claim_id } : { outpoint: `${txo.txid}:${txo.nout}` };

    const startedActionType = isClaim ? ACTIONS.ABANDON_CLAIM_STARTED : ACTIONS.ABANDON_SUPPORT_STARTED;
    const completedActionType = isClaim ? ACTIONS.ABANDON_CLAIM_SUCCEEDED : ACTIONS.ABANDON_SUPPORT_COMPLETED;

    dispatch({
      type: startedActionType,
      data,
    });

    const errorCallback = () => {
      if (cb) cb(ABANDON_STATES.ERROR);
      dispatch(
        doToast({
          message: isClaim ? 'Error abandoning your claim/support' : 'Error unlocking your tip',
          isError: true,
        })
      );
    };

    const successCallback = () => {
      dispatch({
        type: completedActionType,
        data,
      });

      let abandonMessage;
      if (isClaim) {
        abandonMessage = __('Successfully abandoned your claim.');
      } else if (isSupport) {
        abandonMessage = __('Successfully abandoned your support.');
      } else {
        abandonMessage = __('Successfully unlocked your tip!');
      }
      if (cb) cb(ABANDON_STATES.DONE);

      dispatch(
        doToast({
          message: abandonMessage,
        })
      );
    };

    const abandonParams: {
      claim_id?: string,
      txid?: string,
      nout?: number,
    } = {
      blocking: true,
    };
    if (isClaim) {
      abandonParams['claim_id'] = txo.claim_id;
    } else {
      abandonParams['txid'] = txo.txid;
      abandonParams['nout'] = txo.nout;
    }

    let method;
    if (isSupport || isTip) {
      method = 'support_abandon';
    } else if (isClaim) {
      const { normalized_name: claimName } = txo;
      method = claimName.startsWith('@') ? 'channel_abandon' : 'stream_abandon';
    }

    if (!method) {
      console.error('No "method" chosen for claim or support abandon');
      return;
    }

    Lbry[method](abandonParams).then(successCallback, errorCallback);
  };
}

export function doAbandonClaim(claim: Claim, cb: (string) => void) {
  const { txid, nout } = claim;
  const outpoint = `${txid}:${nout}`;

  return (dispatch: Dispatch, getState: GetState) => {
    const state = getState();
    const myClaims: Array<Claim> = selectMyClaimsRaw(state);
    const mySupports: { [string]: Support } = selectSupportsByOutpoint(state);

    // A user could be trying to abandon a support or one of their claims
    const claimIsMine = selectClaimIsMine(state, claim);
    const claimToAbandon = claimIsMine ? claim : myClaims.find((claim) => claim.txid === txid && claim.nout === nout);
    const supportToAbandon = mySupports[outpoint];

    if (!claimToAbandon && !supportToAbandon) {
      console.error('No associated support or claim with txid: ', txid);
      return;
    }

    const data = claimToAbandon
      ? { claimId: claimToAbandon.claim_id }
      : { outpoint: `${supportToAbandon.txid}:${supportToAbandon.nout}` };

    const isClaim = !!claimToAbandon;
    const startedActionType = isClaim ? ACTIONS.ABANDON_CLAIM_STARTED : ACTIONS.ABANDON_SUPPORT_STARTED;
    const completedActionType = isClaim ? ACTIONS.ABANDON_CLAIM_SUCCEEDED : ACTIONS.ABANDON_SUPPORT_COMPLETED;

    dispatch({
      type: startedActionType,
      data,
    });

    const errorCallback = () => {
      dispatch(
        doToast({
          message: isClaim ? 'Error abandoning your claim/support' : 'Error unlocking your tip',
          isError: true,
        })
      );
      if (cb) cb(ABANDON_STATES.ERROR);
    };

    const successCallback = () => {
      dispatch({
        type: completedActionType,
        data,
      });
      if (cb) cb(ABANDON_STATES.DONE);

      let abandonMessage;
      if (isClaim) {
        abandonMessage = __('Successfully abandoned your claim.');
      } else if (supportToAbandon) {
        abandonMessage = __('Successfully abandoned your support.');
      } else {
        abandonMessage = __('Successfully unlocked your tip!');
      }

      dispatch(
        doToast({
          message: abandonMessage,
        })
      );
      dispatch(doFetchTxoPage());
    };

    const abandonParams = {
      txid,
      nout,
      blocking: true,
    };

    let method;
    if (supportToAbandon) {
      method = 'support_abandon';
    } else if (claimToAbandon) {
      const { name: claimName } = claimToAbandon;
      method = claimName.startsWith('@') ? 'channel_abandon' : 'stream_abandon';
    }

    if (!method) {
      console.error('No "method" chosen for claim or support abandon');
      return;
    }

    Lbry[method](abandonParams).then(successCallback, errorCallback);
  };
}

export function doFetchClaimsByChannel(uri: string, page: number = 1) {
  return (dispatch: Dispatch) => {
    dispatch({
      type: ACTIONS.FETCH_CHANNEL_CLAIMS_STARTED,
      data: { uri, page },
    });

    Lbry.claim_search({
      channel: uri,
      valid_channel_signature: true,
      page: page || 1,
      order_by: ['release_time'],
      include_is_my_output: true,
      include_purchase_receipt: true,
    }).then((result: ClaimSearchResponse) => {
      const { items: claims, total_items: claimsInChannel, page: returnedPage } = result;

      dispatch({
        type: ACTIONS.FETCH_CHANNEL_CLAIMS_COMPLETED,
        data: {
          uri,
          claimsInChannel,
          claims: claims || [],
          page: returnedPage || undefined,
        },
      });
    });
  };
}

export function doClearChannelErrors() {
  return {
    type: ACTIONS.CLEAR_CHANNEL_ERRORS,
  };
}

export function doCreateChannel(name: string, amount: number, optionalParams: any, onConfirm: any) {
  return (dispatch: Dispatch) => {
    dispatch({
      type: ACTIONS.CREATE_CHANNEL_STARTED,
    });

    const createParams: {
      name: string,
      bid: string,
      blocking: true,
      title?: string,
      cover_url?: string,
      thumbnail_url?: string,
      description?: string,
      website_url?: string,
      email?: string,
      tags?: Array<Tag>,
      languages?: Array<string>,
    } = {
      name,
      bid: creditsToString(amount),
      blocking: true,
    };

    if (optionalParams) {
      if (optionalParams.title) {
        createParams.title = optionalParams.title;
      }
      if (optionalParams.coverUrl) {
        createParams.cover_url = optionalParams.coverUrl;
      }
      if (optionalParams.thumbnailUrl) {
        createParams.thumbnail_url = optionalParams.thumbnailUrl;
      }
      if (optionalParams.description) {
        createParams.description = optionalParams.description;
      }
      if (optionalParams.website) {
        createParams.website_url = optionalParams.website;
      }
      if (optionalParams.email) {
        createParams.email = optionalParams.email;
      }
      if (optionalParams.tags) {
        createParams.tags = optionalParams.tags.map((tag) => tag.name);
      }
      if (optionalParams.languages) {
        createParams.languages = optionalParams.languages;
      }
    }

    return (
      Lbry.channel_create(createParams)
        // outputs[0] is the certificate
        // outputs[1] is the change from the tx, not in the app currently
        .then((result: ChannelCreateResponse) => {
          const channelClaim = result.outputs[0];
          dispatch({
            type: ACTIONS.CREATE_CHANNEL_COMPLETED,
            data: { channelClaim },
          });
          dispatch({
            type: ACTIONS.UPDATE_PENDING_CLAIMS,
            data: {
              claims: [channelClaim],
            },
          });
          dispatch(doCheckPendingClaims(onConfirm));
          return channelClaim;
        })
        .catch((error) => {
          dispatch({
            type: ACTIONS.CREATE_CHANNEL_FAILED,
            data: error.message,
          });
        })
    );
  };
}

export function doUpdateChannel(params: any, cb: any) {
  return (dispatch: Dispatch, getState: GetState) => {
    dispatch({
      type: ACTIONS.UPDATE_CHANNEL_STARTED,
    });
    const state = getState();
    const myChannels = selectMyChannelClaims(state);
    const channelClaim = myChannels.find((myChannel) => myChannel.claim_id === params.claim_id);

    const updateParams = {
      claim_id: params.claim_id,
      bid: creditsToString(params.amount),
      title: params.title,
      cover_url: params.coverUrl,
      thumbnail_url: params.thumbnailUrl,
      description: params.description,
      website_url: params.website,
      email: params.email,
      tags: [],
      replace: true,
      languages: params.languages || [],
      locations: [],
      blocking: true,
    };

    if (params.tags) {
      updateParams.tags = params.tags.map((tag) => tag.name);
    }

    // we'll need to remove these once we add locations/channels to channel page edit/create options
    if (channelClaim && channelClaim.value && channelClaim.value.locations) {
      updateParams.locations = channelClaim.value.locations;
    }

    return Lbry.channel_update(updateParams)
      .then((result: ChannelUpdateResponse) => {
        const channelClaim = result.outputs[0];
        dispatch({
          type: ACTIONS.UPDATE_CHANNEL_COMPLETED,
          data: { channelClaim },
        });
        dispatch({
          type: ACTIONS.UPDATE_PENDING_CLAIMS,
          data: {
            claims: [channelClaim],
          },
        });
        dispatch(doCheckPendingClaims(cb));
        return Boolean(result.outputs[0]);
      })
      .then()
      .catch((error) => {
        dispatch({
          type: ACTIONS.UPDATE_CHANNEL_FAILED,
          data: error,
        });
      });
  };
}

export function doImportChannel(certificate: string) {
  return (dispatch: Dispatch) => {
    dispatch({
      type: ACTIONS.IMPORT_CHANNEL_STARTED,
    });

    return Lbry.channel_import({ channel_data: certificate })
      .then(() => {
        dispatch({
          type: ACTIONS.IMPORT_CHANNEL_COMPLETED,
        });
      })
      .catch((error) => {
        dispatch({
          type: ACTIONS.IMPORT_CHANNEL_FAILED,
          data: error,
        });
      });
  };
}

export function doFetchChannelListMine(page: number = 1, pageSize: number = 99999, resolve: boolean = true) {
  return (dispatch: Dispatch) => {
    dispatch({
      type: ACTIONS.FETCH_CHANNEL_LIST_STARTED,
    });

    const callback = (response: ChannelListResponse) => {
      dispatch({
        type: ACTIONS.FETCH_CHANNEL_LIST_COMPLETED,
        data: { claims: response.items },
      });
    };

    const failure = (error) => {
      dispatch({
        type: ACTIONS.FETCH_CHANNEL_LIST_FAILED,
        data: error,
      });
    };

    Lbry.channel_list({ page, page_size: pageSize, resolve }).then(callback, failure);
  };
}

export function doFetchCollectionListMine(page: number = 1, pageSize: number = 99999) {
  return (dispatch: Dispatch) => {
    dispatch({
      type: ACTIONS.FETCH_COLLECTION_LIST_STARTED,
    });

    const callback = (response: CollectionListResponse) => {
      const { items } = response;
      dispatch({
        type: ACTIONS.FETCH_COLLECTION_LIST_COMPLETED,
        data: { claims: items },
      });
      dispatch(
        doFetchItemsInCollections({
          collectionIds: items.map((claim) => claim.claim_id),
          page_size: 5,
        })
      );
    };

    const failure = (error) => {
      dispatch({
        type: ACTIONS.FETCH_COLLECTION_LIST_FAILED,
        data: error,
      });
    };

    Lbry.collection_list({ page, page_size: pageSize, resolve_claims: 1, resolve: true }).then(callback, failure);
  };
}

export function doClaimSearch(
  options: {
    page_size?: number,
    page: number,
    no_totals?: boolean,
    any_tags?: Array<string>,
    claim_ids?: Array<string>,
    channel_ids?: Array<string>,
    not_channel_ids?: Array<string>,
    not_tags?: Array<string>,
    order_by?: Array<string>,
    release_time?: string,
    has_source?: boolean,
    has_no_souce?: boolean,
  } = {
    no_totals: true,
    page_size: 10,
    page: 1,
  }
) {
  const query = createNormalizedClaimSearchKey(options);
  return async (dispatch: Dispatch) => {
    dispatch({
      type: ACTIONS.CLAIM_SEARCH_STARTED,
      data: { query: query },
    });

    const success = (data: ClaimSearchResponse) => {
      const resolveInfo = {};
      const urls = [];
      data.items.forEach((stream: Claim) => {
        resolveInfo[stream.canonical_url] = { stream };
        urls.push(stream.canonical_url);
      });

      dispatch({
        type: ACTIONS.CLAIM_SEARCH_COMPLETED,
        data: {
          query,
          resolveInfo,
          urls,
          append: options.page && options.page !== 1,
          pageSize: options.page_size,
        },
      });
      return resolveInfo;
    };

    const failure = (err) => {
      dispatch({
        type: ACTIONS.CLAIM_SEARCH_FAILED,
        data: { query },
        error: err,
      });
      return false;
    };

    return await Lbry.claim_search({
      ...options,
      include_purchase_receipt: true,
    }).then(success, failure);
  };
}

export function doRepost(options: StreamRepostOptions) {
  return (dispatch: Dispatch): Promise<any> => {
    return new Promise((resolve) => {
      dispatch({
        type: ACTIONS.CLAIM_REPOST_STARTED,
      });

      function success(response) {
        const repostClaim = response.outputs[0];
        dispatch({
          type: ACTIONS.CLAIM_REPOST_COMPLETED,
          data: {
            originalClaimId: options.claim_id,
            repostClaim,
          },
        });
        dispatch({
          type: ACTIONS.UPDATE_PENDING_CLAIMS,
          data: {
            claims: [repostClaim],
          },
        });

        dispatch(doFetchClaimListMine(1, 10));
        resolve(repostClaim);
      }

      function failure(error) {
        dispatch({
          type: ACTIONS.CLAIM_REPOST_FAILED,
          data: {
            error: error.message,
          },
        });
      }

      Lbry.stream_repost(options).then(success, failure);
    });
  };
}

export function doCollectionPublish(
  options: {
    name: string,
    bid: string,
    blocking: true,
    title?: string,
    channel_id?: string,
    thumbnail_url?: string,
    description?: string,
    tags?: Array<Tag>,
    languages?: Array<string>,
    claims: Array<string>,
  },
  localId: string
) {
  return (dispatch: Dispatch): Promise<any> => {
    // $FlowFixMe

    const params: {
      name: string,
      bid: string,
      channel_id?: string,
      blocking?: true,
      title?: string,
      thumbnail_url?: string,
      description?: string,
      tags?: Array<string>,
      languages?: Array<string>,
      claims: Array<string>,
    } = {
      name: options.name,
      bid: creditsToString(options.bid),
      title: options.title,
      thumbnail_url: options.thumbnail_url,
      description: options.description,
      tags: [],
      languages: options.languages || [],
      locations: [],
      blocking: true,
      claims: options.claims,
    };

    if (options.tags) {
      params['tags'] = options.tags.map((tag) => tag.name);
    }

    if (options.channel_id) {
      params['channel_id'] = options.channel_id;
    }

    return new Promise((resolve) => {
      dispatch({
        type: ACTIONS.COLLECTION_PUBLISH_STARTED,
      });

      function success(response) {
        const collectionClaim = response.outputs[0];
        dispatch(
          batchActions(
            {
              type: ACTIONS.COLLECTION_PUBLISH_COMPLETED,
              data: { claimId: collectionClaim.claim_id },
            },
            // move unpublished collection to pending collection with new publish id
            // recent publish won't resolve this second. handle it in checkPending
            {
              type: ACTIONS.UPDATE_PENDING_CLAIMS,
              data: {
                claims: [collectionClaim],
              },
            }
          )
        );
        dispatch({
          type: ACTIONS.COLLECTION_PENDING,
          data: { localId: localId, claimId: collectionClaim.claim_id },
        });
        dispatch(doCheckPendingClaims());
        dispatch(doFetchCollectionListMine(1, 10));
        return resolve(collectionClaim);
      }

      function failure(error) {
        dispatch({
          type: ACTIONS.COLLECTION_PUBLISH_FAILED,
          data: {
            error: error.message,
          },
        });
      }

      return Lbry.collection_create(params).then(success, failure);
    });
  };
}

export function doCollectionPublishUpdate(
  options: {
    bid?: string,
    blocking?: true,
    title?: string,
    thumbnail_url?: string,
    description?: string,
    claim_id: string,
    tags?: Array<Tag>,
    languages?: Array<string>,
    claims?: Array<string>,
    channel_id?: string,
  },
  isBackgroundUpdate?: boolean
) {
  return (dispatch: Dispatch, getState: GetState): Promise<any> => {
    // TODO: implement one click update

    const updateParams: {
      bid?: string,
      blocking?: true,
      title?: string,
      thumbnail_url?: string,
      channel_id?: string,
      description?: string,
      claim_id: string,
      tags?: Array<string>,
      languages?: Array<string>,
      claims?: Array<string>,
      clear_claims: boolean,
      replace?: boolean,
    } = isBackgroundUpdate
      ? {
          blocking: true,
          claim_id: options.claim_id,
          clear_claims: true,
        }
      : {
          bid: creditsToString(options.bid),
          title: options.title,
          thumbnail_url: options.thumbnail_url,
          description: options.description,
          tags: [],
          languages: options.languages || [],
          locations: [],
          blocking: true,
          claim_id: options.claim_id,
          clear_claims: true,
          replace: true,
        };

    if (isBackgroundUpdate && updateParams.claim_id) {
      const state = getState();
      updateParams['claims'] = makeSelectClaimIdsForCollectionId(updateParams.claim_id)(state);
    } else if (options.claims) {
      updateParams['claims'] = options.claims;
    }

    if (options.tags) {
      updateParams['tags'] = options.tags.map((tag) => tag.name);
    }

    if (options.channel_id) {
      updateParams['channel_id'] = options.channel_id;
    }

    return new Promise((resolve) => {
      dispatch({
        type: ACTIONS.COLLECTION_PUBLISH_UPDATE_STARTED,
      });

      function success(response) {
        const collectionClaim = response.outputs[0];
        dispatch({
          type: ACTIONS.COLLECTION_PUBLISH_UPDATE_COMPLETED,
          data: {
            collectionClaim,
          },
        });
        dispatch({
          type: ACTIONS.COLLECTION_PENDING,
          data: { claimId: collectionClaim.claim_id },
        });
        dispatch({
          type: ACTIONS.UPDATE_PENDING_CLAIMS,
          data: {
            claims: [collectionClaim],
          },
        });
        dispatch(doCheckPendingClaims());
        return resolve(collectionClaim);
      }

      function failure(error) {
        dispatch({
          type: ACTIONS.COLLECTION_PUBLISH_UPDATE_FAILED,
          data: {
            error: error.message,
          },
        });
      }

      return Lbry.collection_update(updateParams).then(success, failure);
    });
  };
}

export function doCheckPublishNameAvailability(name: string) {
  return (dispatch: Dispatch) => {
    dispatch({
      type: ACTIONS.CHECK_PUBLISH_NAME_STARTED,
    });

    return Lbry.claim_list({ name: name }).then((result) => {
      dispatch({
        type: ACTIONS.CHECK_PUBLISH_NAME_COMPLETED,
      });
      if (result.items.length) {
        dispatch({
          type: ACTIONS.FETCH_CLAIM_LIST_MINE_COMPLETED,
          data: {
            result,
            resolve: false,
          },
        });
      }
      return !(result && result.items && result.items.length);
    });
  };
}

export function doClearRepostError() {
  return {
    type: ACTIONS.CLEAR_REPOST_ERROR,
  };
}

export function doPurchaseList(page: number = 1, pageSize: number = PAGE_SIZE) {
  return (dispatch: Dispatch) => {
    dispatch({
      type: ACTIONS.PURCHASE_LIST_STARTED,
    });

    const success = (result: PurchaseListResponse) => {
      return dispatch({
        type: ACTIONS.PURCHASE_LIST_COMPLETED,
        data: {
          result,
        },
      });
    };

    const failure = (error) => {
      dispatch({
        type: ACTIONS.PURCHASE_LIST_FAILED,
        data: {
          error: error.message,
        },
      });
    };

    Lbry.purchase_list({
      page: page,
      page_size: pageSize,
      resolve: true,
    }).then(success, failure);
  };
}

export const doCheckPendingClaims = (onChannelConfirmed: Function) => (dispatch: Dispatch, getState: GetState) => {
  if (onChannelConfirmed) {
    onChannelConfirmCallback = onChannelConfirmed;
  }
  clearInterval(checkPendingInterval);
  const checkTxoList = () => {
    const state = getState();
    const pendingById = Object.assign({}, selectPendingClaimsById(state));
    const pendingTxos = (Object.values(pendingById): any).map((p) => p.txid);
    // use collections
    if (pendingTxos.length) {
      Lbry.txo_list({ txid: pendingTxos })
        .then((result) => {
          const txos = result.items;
          const idsToConfirm = [];
          txos.forEach((txo) => {
            if (txo.claim_id && txo.confirmations > 0) {
              idsToConfirm.push(txo.claim_id);
              delete pendingById[txo.claim_id];
            }
          });
          return { idsToConfirm, pendingById };
        })
        .then((results) => {
          const { idsToConfirm, pendingById } = results;
          if (idsToConfirm.length) {
            return Lbry.claim_list({ claim_id: idsToConfirm, resolve: true }).then((results) => {
              const claims = results.items;
              const collectionIds = claims.filter((c) => c.value_type === 'collection').map((c) => c.claim_id);
              dispatch({
                type: ACTIONS.UPDATE_CONFIRMED_CLAIMS,
                data: {
                  claims: claims,
                  pending: pendingById,
                },
              });
              if (collectionIds.length) {
                dispatch(
                  doFetchItemsInCollections({
                    collectionIds,
                  })
                );
              }
              const channelClaims = claims.filter((claim) => claim.value_type === 'channel');
              if (channelClaims.length && onChannelConfirmCallback) {
                channelClaims.forEach((claim) => onChannelConfirmCallback(claim));
              }
              if (Object.keys(pendingById).length === 0) {
                clearInterval(checkPendingInterval);
              }
            });
          }
        });
    } else {
      clearInterval(checkPendingInterval);
    }
  };
  // do something with onConfirmed (typically get blocklist for channel)
  checkPendingInterval = setInterval(() => {
    checkTxoList();
  }, 30000);
};