/**
 * @provides javelin-behavior-conpherence-menu
 * @requires javelin-behavior
 *           javelin-dom
 *           javelin-util
 *           javelin-stratcom
 *           javelin-workflow
 *           javelin-behavior-device
 *           javelin-history
 *           javelin-vector
 *           javelin-scrollbar
 *           phabricator-title
 *           phabricator-shaped-request
 *           conpherence-thread-manager
 */

JX.behavior('conpherence-menu', function(config) {
  /**
   * State for displayed thread.
   */
  var _thread = {
    selected: null,
    visible: null,
    node: null
  };

  var scrollbar = null;

  // TODO - move more logic into the ThreadManager
  var threadManager = new JX.ConpherenceThreadManager();
  threadManager.setMessagesRootCallback(function() {
    return scrollbar.getContentNode();
  });
  threadManager.setWillLoadThreadCallback(function() {
    markThreadLoading(true);
  });
  threadManager.setDidLoadThreadCallback(function(r) {
    var header = JX.$H(r.header);
    var messages = JX.$H(r.transactions);
    var form = JX.$H(r.form);
    var root = JX.DOM.find(document, 'div', 'conpherence-layout');
    var header_root = JX.DOM.find(root, 'div', 'conpherence-header-pane');
    var form_root = JX.DOM.find(root, 'div', 'conpherence-form');
    JX.DOM.setContent(header_root, header);
    JX.DOM.setContent(scrollbar.getContentNode(), messages);
    JX.DOM.setContent(form_root, form);

    markThreadLoading(false);

    didRedrawThread(true);
  });

  threadManager.setDidUpdateThreadCallback(function(r) {
    _scrollMessageWindow();
  });

  threadManager.setWillSendMessageCallback(function () {
    var root = JX.DOM.find(document, 'div', 'conpherence-layout');
    var form_root = JX.DOM.find(root, 'div', 'conpherence-form');
    markThreadLoading(true);
    JX.DOM.alterClass(form_root, 'loading', true);
  });

  threadManager.setDidSendMessageCallback(function (r, non_update) {
    var root = JX.DOM.find(document, 'div', 'conpherence-layout');
    var form_root = JX.DOM.find(root, 'div', 'conpherence-form');
    var textarea = JX.DOM.find(form_root, 'textarea');
    if (!non_update) {
      _scrollMessageWindow();
      textarea.value = '';
    }
    markThreadLoading(false);

    setTimeout(function() { JX.DOM.focus(textarea); }, 100);
  });
  threadManager.start();

  /**
   * Current role of this behavior. The two possible roles are to show a 'list'
   * of threads or a specific 'thread'. On devices, this behavior stays in the
   * 'list' role indefinitely, treating clicks normally and the next page
   * loads the behavior with role = 'thread'. On desktop, this behavior
   * auto-loads a thread as part of the 'list' role. As the thread loads the
   * role is changed to 'thread'.
   */
  var _currentRole = null;

  /**
   * When _oldDevice is null the code is executing for the first time.
   */
  var _oldDevice = null;

  /**
   * Initializes this behavior based on all the configuraton jonx and the
   * result of JX.Device.getDevice();
   */
  function init() {
    _currentRole = config.role;

    if (_currentRole == 'thread') {
      markThreadsLoading(true);
    } else {
      markThreadLoading(true);
    }
    markWidgetLoading(true);
    onDeviceChange();
    var root = JX.DOM.find(document, 'div', 'conpherence-layout');
    var messages_root = JX.DOM.find(root, 'div', 'conpherence-message-pane');
    var messages = JX.DOM.find(messages_root, 'div', 'conpherence-messages');
    scrollbar = new JX.Scrollbar(messages);
    scrollbar.setAsScrollFrame();
  }
  init();

  /**
   * Selecting threads
   */
  function selectThreadByID(id, update_page_data) {
    var thread = JX.$(id);
    selectThread(thread, update_page_data);
  }

  function selectThread(node, update_page_data) {
    if (_thread.node) {
      JX.DOM.alterClass(_thread.node, 'conpherence-selected', false);
    }

    JX.DOM.alterClass(node, 'conpherence-selected', true);
    JX.DOM.alterClass(node, 'hide-unread-count', true);

    _thread.node = node;

    var data = JX.Stratcom.getData(node);
    _thread.selected = data.threadID;

    if (update_page_data) {
      updatePageData(data);
    }

    redrawThread();
  }

  function updatePageData(data) {
    var uri = '/Z' + _thread.selected;
    JX.History.replace(uri);
    if (data.title) {
      JX.Title.setTitle(data.title);
    } else if (_thread.node) {
      var threadData = JX.Stratcom.getData(_thread.node);
      JX.Title.setTitle(threadData.title);
    }
  }

  JX.Stratcom.listen(
    'conpherence-update-page-data',
    null,
    function (e) {
      updatePageData(e.getData());
    }
  );

  function redrawThread() {
    if (!_thread.node) {
      return;
    }

    if (_thread.visible == _thread.selected) {
      return;
    }

    var data = JX.Stratcom.getData(_thread.node);

    if (_thread.visible !== null || !config.hasThread) {
      threadManager.setLoadThreadURI('/conpherence/' + data.threadID + '/');
      threadManager.loadThreadByID(data.threadID);
    } else if (config.hasThread) {
      // we loaded the thread via the server so let the thread manager know
      threadManager.setLoadedThreadID(config.selectedThreadID);
      threadManager.setLoadedThreadPHID(config.selectedThreadPHID);
      threadManager.setLatestTransactionID(config.latestTransactionID);
      threadManager.setCanEditLoadedThread(config.canEditSelectedThread);
      threadManager.cacheCurrentTransactions();
      _scrollMessageWindow();
      _focusTextarea();
    } else {
      didRedrawThread();
    }

    if (_thread.visible !== null || !config.hasWidgets) {
      reloadWidget(data);
    } else {
     JX.Stratcom.invoke(
      'conpherence-update-widgets',
      null,
      {
        widget : getDefaultWidget(),
        buildSelectors : false,
        toggleWidget : true,
        threadID : _thread.selected
      });
    }

    _thread.visible = _thread.selected;
  }

  function markThreadsLoading(loading) {
    var root = JX.DOM.find(document, 'div', 'conpherence-layout');
    var menu = JX.DOM.find(root, 'div', 'conpherence-menu-pane');
    JX.DOM.alterClass(menu, 'loading', loading);
  }

  function markThreadLoading(loading) {
    var root = JX.DOM.find(document, 'div', 'conpherence-layout');
    var header_root = JX.DOM.find(root, 'div', 'conpherence-header-pane');
    var messages_root = JX.DOM.find(root, 'div', 'conpherence-message-pane');
    var form_root = JX.DOM.find(root, 'div', 'conpherence-form');

    JX.DOM.alterClass(header_root, 'loading', loading);
    JX.DOM.alterClass(messages_root, 'loading', loading);
    JX.DOM.alterClass(form_root, 'loading', loading);

    try {
      var textarea = JX.DOM.find(form, 'textarea');
      textarea.disabled = loading;
      var button = JX.DOM.find(form, 'button');
      button.disabled = loading;
    } catch (ex) {
      // haven't loaded it yet!
    }
  }

  function markWidgetLoading(loading) {
    var root = JX.DOM.find(document, 'div', 'conpherence-layout');
    var widgets_root = JX.DOM.find(root, 'div', 'conpherence-widget-pane');

    JX.DOM.alterClass(widgets_root, 'loading', loading);
  }

  function reloadWidget(data) {
    markWidgetLoading(true);
    if (!data.widget) {
      data.widget = getDefaultWidget();
    }
    var widget_uri = config.baseURI + 'widget/' + data.threadID + '/';
    new JX.Workflow(widget_uri, {})
      .setHandler(JX.bind(null, onWidgetResponse, data.threadID, data.widget))
      .start();
  }
  JX.Stratcom.listen(
    'conpherence-reload-widget',
    null,
    function (e) {
      var data = e.getData();
      if (data.threadID != _thread.selected) {
        return;
      }
      reloadWidget(data);
    }
  );

  function onWidgetResponse(thread_id, widget, response) {
    // we got impatient and this is no longer the right answer :/
    if (_thread.selected != thread_id) {
      return;
    }
    var root = JX.DOM.find(document, 'div', 'conpherence-layout');
    var widgets_root = JX.DOM.find(root, 'div', 'conpherence-widgets-holder');
    JX.DOM.setContent(widgets_root, JX.$H(response.widgets));

    JX.Stratcom.invoke(
      'conpherence-update-widgets',
      null,
      {
        widget : widget,
        buildSelectors : true,
        toggleWidget : true,
        threadID : _thread.selected
      });

    markWidgetLoading(false);
  }

  function getDefaultWidget() {
    var device = JX.Device.getDevice();
    var widget = 'conpherence-message-pane';
    if (device == 'desktop') {
      widget = 'widgets-people';
      var uri = JX.$U(location.href);
      var params = uri.getQueryParams();
      if ('settings' in params) {
        widget = 'widgets-settings';
      }
    }
    return widget;
  }

  /**
   * This function is a wee bit tricky. Internally, we want to scroll the
   * message window and let other stuff - notably widgets - redraw / build if
   * necessary. Externally, we want a hook to scroll the message window
   * - notably when the widget selector is used to invoke the message pane.
   * The following three functions get 'er done.
   */
  function didRedrawThread(build_device_widget_selector) {
    _scrollMessageWindow();
    _focusTextarea();
    JX.Stratcom.invoke(
      'conpherence-did-redraw-thread',
      null,
      {
        widget : getDefaultWidget(),
        threadID : _thread.selected,
        buildDeviceWidgetSelector : build_device_widget_selector
      });
  }

  var _firstScroll = true;
  function _scrollMessageWindow() {
    if (_firstScroll) {
      _firstScroll = false;

      // We want to let the standard #anchor tech take over after we make sure
      // we don't have to present the user with a "load older message?" dialog
      if (window.location.hash) {
        var hash = window.location.hash.replace(/^#/, '');
        try {
          JX.$('anchor-' + hash);
        } catch (ex) {
          var uri = '/conpherence/' +
            _thread.selected + '/' + hash + '/';
          threadManager.setLoadThreadURI(uri);
          threadManager.loadThreadByID(_thread.selected, true);
          _firstScroll = true;
          return;
        }
        return;
      }
    }
    scrollbar.scrollTo(scrollbar.getViewportNode().scrollHeight);
  }
  function _focusTextarea() {
    var root = JX.DOM.find(document, 'div', 'conpherence-layout');
    var form_root = JX.DOM.find(root, 'div', 'conpherence-form');
    try {
      var textarea = JX.DOM.find(form_root, 'textarea');
      // We may have a draft so do this JS trick so we end up focused at the
      // end of the draft.
      var textarea_value = textarea.value;
      textarea.value = '';
      JX.DOM.focus(textarea);
      textarea.value = textarea_value;
    } catch (ex) {
      // no textarea? no problem
    }
  }
  JX.Stratcom.listen(
    'conpherence-redraw-thread',
    null,
    function () {
      _scrollMessageWindow();
    }
  );

  JX.Stratcom.listen(
    'click',
    'conpherence-menu-click',
    function(e) {
      if (!e.isNormalClick()) {
        return;
      }

      // On devices, just follow the link normally.
      if (JX.Device.getDevice() != 'desktop') {
        return;
      }

      e.kill();
      selectThread(e.getNode('conpherence-menu-click'), true);
    });

  JX.Stratcom.listen('click', 'conpherence-edit-metadata', function (e) {
    e.kill();
    var root = e.getNode('conpherence-layout');
    var form = JX.DOM.find(root, 'form', 'conpherence-pontificate');
    var data = e.getNodeData('conpherence-edit-metadata');
    var header = JX.DOM.find(root, 'div', 'conpherence-header-pane');
    var messages = scrollbar.getContentNode();

    new JX.Workflow.newFromForm(form, data)
      .setHandler(JX.bind(this, function(r) {
        JX.DOM.appendContent(messages, JX.$H(r.transactions));
        _scrollMessageWindow();

        JX.DOM.setContent(
          header,
          JX.$H(r.header)
        );

        try {
          // update the menu entry
          JX.DOM.replace(
            JX.$(r.conpherence_phid + '-nav-item'),
            JX.$H(r.nav_item)
          );
          selectThreadByID(r.conpherence_phid + '-nav-item');
        } catch (ex) {
          // Ignore; this view may not have a menu.
        }
      }))
      .start();
  });

  /**
   * On devices, we just show a thread list, so we don't want to automatically
   * select or load any threads. On desktop, we automatically select the first
   * thread, changing the _currentRole from list to thread.
   */
  function onDeviceChange() {
    var new_device = JX.Device.getDevice();
    if (new_device === _oldDevice) {
      return;
    }

    if (_oldDevice === null) {
      _oldDevice = new_device;
      if (_currentRole == 'list') {
        if (new_device != 'desktop') {
          return;
        }
      } else {
        loadThreads();
        return;
      }
    }
    var update_toggled_widget =
      new_device == 'desktop' || _oldDevice == 'desktop';
    _oldDevice = new_device;

    if (_thread.visible !== null && update_toggled_widget) {
      JX.Stratcom.invoke(
        'conpherence-did-redraw-thread',
        null,
        {
          widget : getDefaultWidget(),
          threadID : _thread.selected
        });
    }

    if (_currentRole == 'list' && new_device == 'desktop') {
      // this selects a thread and loads it
      didLoadThreads();
      _currentRole = 'thread';
      var root = JX.DOM.find(document, 'div', 'conpherence-layout');
      JX.DOM.alterClass(root, 'conpherence-role-list', false);
      JX.DOM.alterClass(root, 'conpherence-role-thread', true);
    }
  }
  JX.Stratcom.listen('phabricator-device-change', null, onDeviceChange);

  function loadThreads() {
    markThreadsLoading(true);
    var uri = config.baseURI + 'thread/' + config.selectedThreadID + '/';
    new JX.Workflow(uri)
      .setHandler(onLoadThreadsResponse)
      .start();
  }

  function onLoadThreadsResponse(r) {
    var layout = JX.$(config.layoutID);
    var menu = JX.DOM.find(layout, 'div', 'conpherence-menu-pane');
    JX.DOM.setContent(menu, JX.$H(r));

    config.selectedID && selectThreadByID(config.selectedID);

    markThreadsLoading(false);
  }

  function didLoadThreads() {
    // If there's no thread selected yet, select the current thread or the
    // first thread.
    if (!_thread.selected) {
      if (config.selectedID) {
        selectThreadByID(config.selectedID, true);
      } else {
        var layout = JX.$(config.layoutID);
        var threads = JX.DOM.scry(layout, 'a', 'conpherence-menu-click');
        if (threads.length) {
          selectThread(threads[0]);
        } else {
          var nothreads = JX.DOM.find(layout, 'div', 'conpherence-no-threads');
          nothreads.style.display = 'block';
          markThreadLoading(false);
          markWidgetLoading(false);
        }
      }
    }
  }

  JX.Stratcom.listen(
    ['click'],
    'conpherence-menu-see-more',
    function (e) {
      e.kill();
      var sigil = e.getNodeData('conpherence-menu-see-more').moreSigil;
      var root = JX.$('conpherence-menu-pane');
      var more = JX.DOM.scry(root, 'li', sigil);
      for (var i = 0; i < more.length; i++) {
        JX.DOM.alterClass(more[i], 'hidden', false);
      }
      JX.DOM.hide(e.getNode('conpherence-menu-see-more'));
    });

  JX.Stratcom.listen(
    ['keydown'],
    'conpherence-pontificate',
    function (e) {
      threadManager.handleDraftKeydown(e);
    });

});
