// @flow
import type { Node } from 'react';
import * as MODALS from 'constants/modal_types';
import * as ICONS from 'constants/icons';
import React from 'react';
import { Lbry, SETTINGS } from 'lbry-redux';
import Button from 'component/button';
import ModalWalletUnlock from 'modal/modalWalletUnlock';
import ModalIncompatibleDaemon from 'modal/modalIncompatibleDaemon';
import ModalUpgrade from 'modal/modalUpgrade';
import ModalDownloading from 'modal/modalDownloading';
import Card from 'component/common/card';
import I18nMessage from 'component/i18nMessage';
import 'css-doodle';

const FORTY_FIVE_SECONDS = 45 * 1000;
const UPDATE_INTERVAL = 1000; // 1 second
const MAX_WALLET_WAIT = 20; // 20 seconds for wallet to be started, but servers to be unavailable
const MAX_SYNC_WAIT = 45; // 45 seconds to sync wallet, show message if taking long

type Props = {
  checkDaemonVersion: () => Promise<any>,
  notifyUnlockWallet: (?boolean) => Promise<any>,
  daemonVersionMatched: boolean,
  onReadyToLaunch: () => void,
  hideModal: () => void,
  modal: ?{
    id: string,
  },
  animationHidden: boolean,
  setClientSetting: (string, boolean) => void,
  clearWalletServers: () => void,
  doShowSnackBar: string => void,
};

type State = {
  details: string | Node,
  message: string,
  launchedModal: boolean,
  error: boolean,
  isRunning: boolean,
  launchWithIncompatibleDaemon: boolean,
  waitingForWallet: number,
  waitingForSync: number,
};

export default class SplashScreen extends React.PureComponent<Props, State> {
  constructor(props: Props) {
    super(props);

    this.state = {
      details: __('Starting...'),
      message: __('Connecting'),
      launchedModal: false,
      error: false,
      launchWithIncompatibleDaemon: !process.env.NODE_ENV === 'production',
      isRunning: false,
      waitingForWallet: 0,
      waitingForSync: 0,
    };

    (this: any).renderModals = this.renderModals.bind(this);
    (this: any).runWithIncompatibleDaemon = this.runWithIncompatibleDaemon.bind(this);
    this.timeout = undefined;
  }

  componentDidMount() {
    const { checkDaemonVersion } = this.props;
    this.adjustErrorTimeout();

    Lbry.connect()
      .then(checkDaemonVersion)
      .then(() => {
        this.updateStatus();
      })
      .catch(() => {
        this.setState({
          message: __('Connection Failure'),
          details: __(
            'Try closing all LBRY processes and starting again. If this still happens, your anti-virus software or firewall may be preventing LBRY from connecting. Contact hello@lbry.com if you think this is a software bug.'
          ),
        });
      });
  }

  componentDidUpdate() {
    this.adjustErrorTimeout();
  }

  componentWillUnmount() {
    if (this.timeout) {
      clearTimeout(this.timeout);
    }
  }

  adjustErrorTimeout() {
    if (this.timeout) {
      clearTimeout(this.timeout);
    }

    // Every time we make it to a new step in the daemon startup process, reset the timer
    // If nothing changes after 1 minute, show the error message.
    this.timeout = setTimeout(() => {
      this.setState({ error: true });
    }, FORTY_FIVE_SECONDS);
  }

  updateStatus() {
    const { modal, notifyUnlockWallet, clearWalletServers, doShowSnackBar } = this.props;
    const { launchedModal } = this.state;

    Lbry.status().then(status => {
      const sdkStatus = status;
      const { wallet } = status;
      Lbry.wallet_status().then(status => {
        if (sdkStatus.is_running && wallet && wallet.available_servers) {
          if (status.is_locked) {
            // Clear the error timeout, it might sit on this step for a while until someone enters their password
            if (this.timeout) {
              clearTimeout(this.timeout);
            }
            // Make sure there isn't another active modal (like INCOMPATIBLE_DAEMON)
            this.updateStatusCallback(sdkStatus, status, true);
            if (launchedModal === false && !modal) {
              this.setState({ launchedModal: true }, () => notifyUnlockWallet());
            }
          } else {
            this.updateStatusCallback(sdkStatus, status);
          }
        } else if (!sdkStatus.is_running && status.is_syncing) {
          // Clear the timeout if wallet is still syncing
          if (this.timeout) {
            clearTimeout(this.timeout);
          }
          this.updateStatusCallback(sdkStatus, status);
        } else if (this.state.waitingForWallet > MAX_WALLET_WAIT && launchedModal === false && !modal) {
          clearWalletServers();
          doShowSnackBar(
            __(
              'The wallet server took a bit too long. Resetting defaults just in case. Shutdown (Cmd/Ctrl+Q) LBRY and restart if this continues.'
            )
          );
          this.setState({ waitingForWallet: 0 });
          this.updateStatusCallback(sdkStatus, status);
        } else {
          this.updateStatusCallback(sdkStatus, status);
        }
      });
    });
  }

  updateStatusCallback(status: StatusResponse, walletStatus: WalletStatusResponse, waitingForUnlock: boolean = false) {
    if (status.connection_status.code !== 'connected') {
      this.setState({ error: true });
      return;
    }

    const { wallet, startup_status: startupStatus } = status;

    // If the wallet is locked, stop doing anything and make the user input their password
    if (startupStatus && wallet && wallet.available_servers < 1) {
      this.setState({ waitingForWallet: this.state.waitingForWallet + UPDATE_INTERVAL / 1000 });
    } else if (status.is_running && !waitingForUnlock) {
      Lbry.resolve({ urls: 'lbry://one' }).then(() => {
        this.setState({ isRunning: true }, () => this.continueAppLaunch());
      });

      return;
    } else if (wallet && !status.is_running && walletStatus.is_syncing) {
      this.setState({ waitingForSync: this.state.waitingForSync + UPDATE_INTERVAL / 1000 });
      if (this.state.waitingForSync < MAX_SYNC_WAIT) {
        this.setState({
          message: __('Loading Wallet'),
          details: __('Updating wallet data...'),
        });
      } else {
        this.setState({
          message: __('Loading Wallet'),
          details: (
            <React.Fragment>
              <div>{__('Large account history')}</div>
              <div>{__('Please wait...')}</div>
            </React.Fragment>
          ),
        });
      }
    } else if (wallet && !status.is_running && startupStatus.database) {
      this.setState({
        message: __('Finalizing'),
        details: __('Almost ready...'),
      });
    }

    setTimeout(() => {
      this.updateStatus();
    }, UPDATE_INTERVAL);
  }

  runWithIncompatibleDaemon() {
    const { hideModal } = this.props;
    hideModal();
    this.setState({ launchWithIncompatibleDaemon: true }, () => this.continueAppLaunch());
  }

  continueAppLaunch() {
    const { daemonVersionMatched, onReadyToLaunch } = this.props;
    const { isRunning, launchWithIncompatibleDaemon } = this.state;

    if (daemonVersionMatched) {
      onReadyToLaunch();
    } else if (launchWithIncompatibleDaemon && isRunning) {
      // The user may have decided to run the app with mismatched daemons
      // They could make this decision before the daemon is finished starting up
      // If it isn't running, this function will be called after the daemon is started
      onReadyToLaunch();
    }
  }

  timeout: ?TimeoutID;

  renderModals() {
    const { modal } = this.props;
    const modalId = modal && modal.id;

    if (!modalId) {
      return null;
    }

    switch (modalId) {
      case MODALS.INCOMPATIBLE_DAEMON:
        return <ModalIncompatibleDaemon onContinueAnyway={this.runWithIncompatibleDaemon} />;
      case MODALS.WALLET_UNLOCK:
        return <ModalWalletUnlock />;
      case MODALS.UPGRADE:
        return <ModalUpgrade />;
      case MODALS.DOWNLOADING:
        return <ModalDownloading />;
      default:
        return null;
    }
  }

  render() {
    const { error, details } = this.state;
    const { animationHidden, setClientSetting } = this.props;

    return (
      <div className="splash">
        <h1 className="splash__title">LBRY</h1>
        <div className="splash__details">{details}</div>

        {!animationHidden && !error && (
          <css-doodle class="doodle">
            {`
            --color: @p(var(--color-primary), var(--color-secondary), var(--color-focus), var(--color-nothing));
            :doodle {
              @grid: 30x1 / 18vmin;
              --deg: @p(-180deg, 180deg);
            }
            :container {
              perspective: 30vmin;
            }

            @place-cell: center;
            @size: 100%;

            box-shadow: @m2(0 0 50px var(--color));
            will-change: transform, opacity;
            animation: scale-up 12s linear infinite;
            animation-delay: calc(-12s / @size() * @i());

            @keyframes scale-up {
              0%, 95.01%, 100% {
                transform: translateZ(0) rotate(0);
                opacity: 0;
              }
              10% {
                opacity: 1;
              }
              95% {
                transform:
                  translateZ(35vmin) rotateZ(@var(--deg));
              }
            }
          )
          `}
          </css-doodle>
        )}
        {!error && (
          <Button
            className="splash__animation-toggle"
            label={!animationHidden ? __('I feel woosy! Stop spinning!') : __('Spin Spin Sugar')}
            onClick={() => setClientSetting(SETTINGS.HIDE_SPLASH_ANIMATION, !animationHidden)}
          />
        )}
        {error && (
          <Card
            title={__('Error Starting Up')}
            subtitle={
              <React.Fragment>
                <p>
                  {__(
                    'You can try refreshing to fix it. If you still have issues, your anti-virus software or firewall may be preventing startup.'
                  )}
                </p>
                <p>
                  <I18nMessage
                    tokens={{
                      help_link: (
                        <Button
                          button="link"
                          href="https://lbry.com/faq/startup-troubleshooting"
                          label={__('this link')}
                        />
                      ),
                    }}
                  >
                    Reach out to hello@lbry.com for help, or check out %help_link%.
                  </I18nMessage>
                </p>
              </React.Fragment>
            }
            actions={
              <Button
                button="primary"
                icon={ICONS.REFRESH}
                label={__('Refresh')}
                onClick={() => window.location.reload()}
              />
            }
          />
        )}
        {/* Temp hack: don't show any modals on splash screen daemon is running;
            daemon doesn't let you quit during startup, so the "Quit" buttons
          in the modals won't work. */}
        {this.renderModals()}
      </div>
    );
  }
}
