import React from 'react';
import PropTypes from 'prop-types';

import InitDialog from '../views/init-dialog';
import CloneDialog from '../views/clone-dialog';
import CredentialDialog from '../views/credential-dialog';
import OpenIssueishDialog from '../views/open-issueish-dialog';
import OpenCommitDialog from '../views/open-commit-dialog';
import CreateDialog from '../views/create-dialog';
import {GithubLoginModelPropType} from '../prop-types';

const DIALOG_COMPONENTS = {
  null: NullDialog,
  init: InitDialog,
  clone: CloneDialog,
  credential: CredentialDialog,
  issueish: OpenIssueishDialog,
  commit: OpenCommitDialog,
  create: CreateDialog,
  publish: CreateDialog,
};

export default class DialogsController extends React.Component {
  static propTypes = {
    // Model
    loginModel: GithubLoginModelPropType.isRequired,
    request: PropTypes.shape({
      identifier: PropTypes.string.isRequired,
      isProgressing: PropTypes.bool.isRequired,
    }).isRequired,

    // Atom environment
    currentWindow: PropTypes.object.isRequired,
    workspace: PropTypes.object.isRequired,
    commands: PropTypes.object.isRequired,
    config: PropTypes.object.isRequired,
  };

  state = {
    requestInProgress: null,
    requestError: [null, null],
  }

  render() {
    const DialogComponent = DIALOG_COMPONENTS[this.props.request.identifier];
    return <DialogComponent {...this.getCommonProps()} />;
  }

  getCommonProps() {
    const {request} = this.props;
    const accept = request.isProgressing
      ? async (...args) => {
        this.setState({requestError: [null, null], requestInProgress: request});
        try {
          const result = await request.accept(...args);
          this.setState({requestInProgress: null});
          return result;
        } catch (error) {
          this.setState({requestError: [request, error], requestInProgress: null});
          return undefined;
        }
      } : (...args) => {
        this.setState({requestError: [null, null]});
        try {
          return request.accept(...args);
        } catch (error) {
          this.setState({requestError: [request, error]});
          return undefined;
        }
      };
    const wrapped = wrapDialogRequest(request, {accept});

    return {
      loginModel: this.props.loginModel,
      request: wrapped,
      inProgress: this.state.requestInProgress === request,
      currentWindow: this.props.currentWindow,
      workspace: this.props.workspace,
      commands: this.props.commands,
      config: this.props.config,
      error: this.state.requestError[0] === request ? this.state.requestError[1] : null,
    };
  }
}

function NullDialog() {
  return null;
}

class DialogRequest {
  constructor(identifier, params = {}) {
    this.identifier = identifier;
    this.params = params;
    this.isProgressing = false;
    this.accept = () => {};
    this.cancel = () => {};
  }

  onAccept(cb) {
    this.accept = cb;
  }

  onProgressingAccept(cb) {
    this.isProgressing = true;
    this.onAccept(cb);
  }

  onCancel(cb) {
    this.cancel = cb;
  }

  getParams() {
    return this.params;
  }
}

function wrapDialogRequest(original, {accept}) {
  const dup = new DialogRequest(original.identifier, original.params);
  dup.isProgressing = original.isProgressing;
  dup.onAccept(accept);
  dup.onCancel(original.cancel);
  return dup;
}

export const dialogRequests = {
  null: {
    identifier: 'null',
    isProgressing: false,
    params: {},
    accept: () => {},
    cancel: () => {},
  },

  init({dirPath}) {
    return new DialogRequest('init', {dirPath});
  },

  clone(opts) {
    return new DialogRequest('clone', {
      sourceURL: '',
      destPath: '',
      ...opts,
    });
  },

  credential(opts) {
    return new DialogRequest('credential', {
      includeUsername: false,
      includeRemember: false,
      prompt: 'Please authenticate',
      ...opts,
    });
  },

  issueish() {
    return new DialogRequest('issueish');
  },

  commit() {
    return new DialogRequest('commit');
  },

  create() {
    return new DialogRequest('create');
  },

  publish({localDir}) {
    return new DialogRequest('publish', {localDir});
  },
};