// Copyright 2015-2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only

import * as log from '../logging/log';
import { openLinkInWebBrowser } from '../util/openLinkInWebBrowser';
import { HTTPError } from '../textsecure/Errors';

window.Whisper = window.Whisper || {};
const { Whisper } = window;

enum Steps {
  INSTALL_SIGNAL = 2,
  SCAN_QR_CODE = 3,
  ENTER_NAME = 4,
  PROGRESS_BAR = 5,
  TOO_MANY_DEVICES = 'TooManyDevices',
  NETWORK_ERROR = 'NetworkError',
}

const DEVICE_NAME_SELECTOR = 'input.device-name';
const CONNECTION_ERROR = -1;
const TOO_MANY_DEVICES = 411;
const TOO_OLD = 409;

Whisper.InstallView = Whisper.View.extend({
  template: () => $('#link-flow-template').html(),
  className: 'main full-screen-flow',
  events: {
    'click .try-again': 'connect',
    'click .second': 'shutdown',
    // the actual next step happens in confirmNumber() on submit form #link-phone
  },
  initialize(options: { hasExistingData?: boolean } = {}) {
    window.readyForUpdates();

    this.selectStep(Steps.SCAN_QR_CODE);
    this.connect();
    this.on('disconnected', this.reconnect);

    // Keep data around if it's a re-link, or the middle of a light import
    this.shouldRetainData =
      window.Signal.Util.Registration.everDone() || options.hasExistingData;
  },
  render_attributes() {
    let errorMessage;
    let errorButton = window.i18n('installTryAgain');
    let errorSecondButton = null;

    if (this.error) {
      if (
        this.error instanceof HTTPError &&
        this.error.code === TOO_MANY_DEVICES
      ) {
        errorMessage = window.i18n('installTooManyDevices');
      } else if (
        this.error instanceof HTTPError &&
        this.error.code === TOO_OLD
      ) {
        errorMessage = window.i18n('installTooOld');
        errorButton = window.i18n('upgrade');
        errorSecondButton = window.i18n('quit');
      } else if (
        this.error instanceof HTTPError &&
        this.error.code === CONNECTION_ERROR
      ) {
        errorMessage = window.i18n('installConnectionFailed');
      } else if (this.error.message === 'websocket closed') {
        // AccountManager.registerSecondDevice uses this specific
        //   'websocket closed' error message
        errorMessage = window.i18n('installConnectionFailed');
      }

      return {
        isError: true,
        errorHeader: window.i18n('installErrorHeader'),
        errorMessage,
        errorButton,
        errorSecondButton,
      };
    }

    return {
      isStep3: this.step === Steps.SCAN_QR_CODE,
      linkYourPhone: window.i18n('linkYourPhone'),
      signalSettings: window.i18n('signalSettings'),
      linkedDevices: window.i18n('linkedDevices'),
      androidFinalStep: window.i18n('plusButton'),
      appleFinalStep: window.i18n('linkNewDevice'),

      isStep4: this.step === Steps.ENTER_NAME,
      chooseName: window.i18n('chooseDeviceName'),
      finishLinkingPhoneButton: window.i18n('finishLinkingPhone'),

      isStep5: this.step === Steps.PROGRESS_BAR,
      syncing: window.i18n('initialSync'),
    };
  },
  selectStep(step: Steps) {
    this.step = step;
    this.render();
  },
  shutdown() {
    window.shutdown();
  },
  async connect() {
    if (this.error instanceof HTTPError && this.error.code === TOO_OLD) {
      openLinkInWebBrowser('https://signal.org/download');
      return;
    }

    this.error = null;
    this.selectStep(Steps.SCAN_QR_CODE);
    this.clearQR();
    if (this.timeout) {
      clearTimeout(this.timeout);
      this.timeout = null;
    }

    const accountManager = window.getAccountManager();

    try {
      await accountManager.registerSecondDevice(
        this.setProvisioningUrl.bind(this),
        this.confirmNumber.bind(this)
      );
    } catch (err) {
      this.handleDisconnect(err);
    }
  },
  handleDisconnect(error: Error) {
    log.error(
      'provisioning failed',
      error && error.stack ? error.stack : error
    );

    this.error = error;
    this.render();

    if (error.message === 'websocket closed') {
      this.trigger('disconnected');
    } else if (
      !(error instanceof HTTPError) ||
      (error.code !== CONNECTION_ERROR && error.code !== TOO_MANY_DEVICES)
    ) {
      throw error;
    }
  },
  reconnect() {
    if (this.timeout) {
      clearTimeout(this.timeout);
      this.timeout = null;
    }
    this.timeout = setTimeout(this.connect.bind(this), 10000);
  },
  clearQR() {
    this.$('#qr img').remove();
    this.$('#qr canvas').remove();
    this.$('#qr .container').show();
    this.$('#qr').removeClass('ready');
  },
  setProvisioningUrl(url: string) {
    if ($('#qr').length === 0) {
      log.error('Did not find #qr element in the DOM!');
      return;
    }

    this.clearQR();
    this.$('#qr .container').hide();
    this.qr = new window.QRCode(this.$('#qr')[0]).makeCode(url);
    this.$('#qr').removeAttr('title');
    this.$('#qr').addClass('ready');
    this.$('#qr img').attr('alt', window.i18n('LinkScreen__scan-this-code'));
  },
  setDeviceNameDefault() {
    const deviceName = window.textsecure.storage.user.getDeviceName();

    this.$(DEVICE_NAME_SELECTOR).val(deviceName || window.getHostName());
    this.$(DEVICE_NAME_SELECTOR).focus();
  },
  confirmNumber() {
    window.removeSetupMenuItems();
    this.selectStep(Steps.ENTER_NAME);
    this.setDeviceNameDefault();

    return new Promise(resolve => {
      const onDeviceName = async (name: string) => {
        this.selectStep(Steps.PROGRESS_BAR);

        const finish = () => {
          window.Signal.Util.postLinkExperience.start();
          return resolve(name);
        };

        // Delete all data from database unless we're in the middle
        //   of a re-link, or we are finishing a light import. Without this,
        //   app restarts at certain times can cause weird things to happen,
        //   like data from a previous incomplete light import showing up
        //   after a new install.
        if (this.shouldRetainData) {
          return finish();
        }

        try {
          await window.textsecure.storage.protocol.removeAllData();
        } catch (error) {
          log.error(
            'confirmNumber: error clearing database',
            error && error.stack ? error.stack : error
          );
        } finally {
          finish();
        }
      };

      if (window.CI) {
        onDeviceName(window.CI.deviceName);
        return;
      }

      // eslint-disable-next-line consistent-return
      this.$('#link-phone').submit((e: SubmitEvent) => {
        e.stopPropagation();
        e.preventDefault();

        let name = this.$(DEVICE_NAME_SELECTOR).val();
        name = name.replace(/\0/g, ''); // strip unicode null
        if (name.trim().length === 0) {
          this.$(DEVICE_NAME_SELECTOR).focus();
          return;
        }

        onDeviceName(name);
      });
    });
  },
});