// Copyright (c) 2020-2021 Drew Lemmy
// This file is part of TenebraWeb 2 under AGPL-3.0.
// Full details: https://github.com/tmpim/TenebraWeb2/blob/master/LICENSE.txt
import * as actions from "@actions/WalletsActions";
import { createReducer } from "typesafe-actions";

import { Wallet, WalletMap, loadWallets, WALLET_UPDATABLE_KEYS, WALLET_SYNCABLE_KEYS } from "@wallets";

export interface State {
  readonly wallets: WalletMap;
  readonly lastTxFrom: string;
}

export function getInitialWalletsState(): State {
  const wallets = loadWallets();
  return {
    wallets,
    lastTxFrom: localStorage.getItem("lastTxFrom") || ""
  };
}

function assignNewWalletProperties(
  state: State,
  id: string,
  partialWallet: Partial<Wallet>,
  allowedKeys?: (keyof Wallet)[]
) {
  // Fetch the old wallet and assign the new properties
  const { [id]: wallet } = state.wallets;
  const newWallet = allowedKeys
    ? allowedKeys.reduce((o, key) => partialWallet[key] !== undefined
      ? { ...o, [key]: partialWallet[key] }
      : o, {})
    : partialWallet;

  return {
    ...state,
    wallets: {
      ...state.wallets,
      [id]: { ...wallet, ...newWallet }
    }
  };
}

export const WalletsReducer = createReducer({ wallets: {} } as State)
  // Load wallets
  .handleAction(actions.loadWallets, (state, { payload }) => ({
    ...state,
    wallets: {
      ...state.wallets,
      ...payload.wallets
    }
  }))
  // Add wallet
  .handleAction(actions.addWallet, (state, { payload }) => ({
    ...state,
    wallets: {
      ...state.wallets,
      [payload.wallet.id]: payload.wallet
    }
  }))
  // Remove wallet
  .handleAction(actions.removeWallet, (state, { payload }) => {
    // Get the wallets without the one we want to remove
    const { [payload.id]: _, ...wallets } = state.wallets;
    return { ...state, wallets };
  })
  // Update wallet
  .handleAction(actions.updateWallet, (state, { payload }) =>
    assignNewWalletProperties(state, payload.id, payload.wallet, WALLET_UPDATABLE_KEYS))
  // Sync wallet
  .handleAction(actions.syncWallet, (state, { payload }) =>
    assignNewWalletProperties(state, payload.id, payload.wallet, WALLET_SYNCABLE_KEYS))
  // Sync wallets
  .handleAction(actions.syncWallets, (state, { payload }) => {
    const updatedWallets = Object.entries(payload.wallets)
      .map(([id, newData]) => ({ // merge in the new data
        ...(state.wallets[id]), // old data

        // only pull the relevant keys, in case this is a full Wallet object
        balance: newData.balance,
        names: newData.names,
        firstSeen: newData.firstSeen,
        lastSynced: newData.lastSynced
      })) // convert back to a WalletMap
      .reduce((o, wallet) => ({ ...o, [wallet.id]: wallet }), {});

    return { ...state, wallets: { ...state.wallets, ...updatedWallets }};
  })
  // Unsync wallet (remove its balance etc. as it no longer exists)
  .handleAction(actions.unsyncWallet, (state, { payload }) => ({
    ...state,
    wallets: {
      ...state.wallets,
      [payload.id]: {
        ...state.wallets[payload.id],
        balance: undefined,
        names: undefined,
        firstSeen: undefined,
        lastSynced: payload.lastSynced
      }
    }
  }))
  // Recalculate wallets
  .handleAction(actions.recalculateWallets, (state, { payload }) => {
    const updatedWallets = Object.entries(payload.wallets)
      .map(([id, newData]) => ({ // merge in the new data
        ...(state.wallets[id]), // old data
        address: newData // recalculated address
      })) // convert back to a WalletMap
      .reduce((o, wallet) => ({ ...o, [wallet.id]: wallet }), {});

    return { ...state, wallets: { ...state.wallets, ...updatedWallets }};
  })
  // Set last transaction from
  .handleAction(actions.setLastTxFrom, (state, { payload }) =>
    ({ ...state, lastTxFrom: payload.address }));

