var _ = require('underscore');
var ko = require('knockout');
var dispose = require('../lib/dispose');
var dom = require('../lib/dom');
var kd = require('../lib/koDom');
var kf = require('../lib/koForm');
var koArray = require('../lib/koArray');
var SummaryConfig = require('./SummaryConfig');
var commands = require('./commands');
var {CustomSectionElement} = require('../lib/CustomSectionElement');
const {ChartConfig} = require('./ChartView');
const {Computed, dom: grainjsDom, makeTestId, Observable, styled, MultiHolder} = require('grainjs');

const {addToSort} = require('app/client/lib/sortUtil');
const {updatePositions} = require('app/client/lib/sortUtil');
const {attachColumnFilterMenu} = require('app/client/ui/ColumnFilterMenu');
const {addFilterMenu} = require('app/client/ui/FilterBar');
const {cssIcon, cssRow} = require('app/client/ui/RightPanel');
const {VisibleFieldsConfig} = require('app/client/ui/VisibleFieldsConfig');
const {basicButton, primaryButton} = require('app/client/ui2018/buttons');
const {labeledLeftSquareCheckbox} = require("app/client/ui2018/checkbox");
const {colors} = require('app/client/ui2018/cssVars');
const {cssDragger} = require('app/client/ui2018/draggableList');
const {menu, menuItem, select} = require('app/client/ui2018/menus');
const {confirmModal} = require('app/client/ui2018/modals');
const {Sort} = require('app/common/SortSpec');
const isEqual = require('lodash/isEqual');
const {cssMenuItem} = require('popweasel');

const testId = makeTestId('test-vconfigtab-');

/**
 * Helper class that combines one ViewSection's data for building dom.
 */
function ViewSectionData(section) {
  this.section = section;

  // A koArray reflecting the columns (RowModels) that are not present in the current view.
  this.hiddenFields = this.autoDispose(koArray.syncedKoArray(section.hiddenColumns));
}
dispose.makeDisposable(ViewSectionData);


function ViewConfigTab(options) {
  var self = this;
  this.gristDoc = options.gristDoc;
  this.viewModel = options.viewModel;

  // viewModel may point to different views, but viewSectionData is a single koArray reflecting
  // the sections of the current view.
  this.viewSectionData = this.autoDispose(
    koArray.syncedKoArray(this.viewModel.viewSections, function(section) {
      return ViewSectionData.create(section);
    })
    .setAutoDisposeValues()
  );

  this.activeSectionData = this.autoDispose(ko.computed(function() {
    return _.find(self.viewSectionData.all(), function(sectionData) {
      return sectionData.section &&
        sectionData.section.getRowId() === self.viewModel.activeSectionId();
    }) || self.viewSectionData.at(0);
  }));
  this.isDetail = this.autoDispose(ko.computed(function() {
    return ['detail', 'single'].includes(this.viewModel.activeSection().parentKey());
  }, this));
  this.isChart = this.autoDispose(ko.computed(function() {
      return this.viewModel.activeSection().parentKey() === 'chart';}, this));
  this.isGrid = this.autoDispose(ko.computed(function() {
      return this.viewModel.activeSection().parentKey() === 'record';}, this));
  this.isCustom = this.autoDispose(ko.computed(function() {
      return this.viewModel.activeSection().parentKey() === 'custom';}, this));

  this._summaryConfig = this.autoDispose(SummaryConfig.create({
    gristDoc: this.gristDoc,
    section: this.viewModel.activeSection
  }));

  if (!options.skipDomBuild) {
    this.gristDoc.addOptionsTab(
      'View', dom('span.glyphicon.glyphicon-credit-card'),
      this.buildConfigDomObj(),
      { 'category': 'options', 'show': this.activeSectionData }
    );
  }
}
dispose.makeDisposable(ViewConfigTab);


function getLabelFunc(field) { return field ? field.label() : null; }

ViewConfigTab.prototype._buildSectionFieldsConfig = function() {
  var self = this;
  return kd.maybe(this.activeSectionData, function(sectionData) {
    const visibleFieldsConfig = VisibleFieldsConfig.create(null, self.gristDoc, sectionData.section, false);
    const [fieldsDraggable, hiddenFieldsDraggable] = visibleFieldsConfig.buildSectionFieldsConfigHelper({
      visibleFields: { itemCreateFunc: getLabelFunc },
      hiddenFields: {itemCreateFunc: getLabelFunc }
    });
    return dom('div',
      dom.autoDispose(visibleFieldsConfig),
      kf.collapsible(function(isCollapsed) {
        return [
          kf.collapserLabel(isCollapsed, 'Visible Fields', kd.toggleClass('view_config_field_group', true)),
          dom.testId('ViewConfigTab_visibleFields'),
          fieldsDraggable,
        ];
      }, false),

      kf.collapsible(function(isCollapsed) {
        return [
          kf.collapserLabel(isCollapsed, 'Hidden Fields', kd.toggleClass('view_config_field_group', true)),
          dom.testId('ViewConfigTab_hiddenFields'),
          hiddenFieldsDraggable,
        ];
      }, false),
    );
  });
};

// Builds object with ViewConfigTab dom builder and settings for the sidepane.
ViewConfigTab.prototype.buildConfigDomObj = function() {
  return [{
      'buildDom': this._buildNameDom.bind(this),
      'keywords': ['view', 'name', 'title']
    }, {
      'buildDom': this._buildSectionNameDom.bind(this),
      'keywords': ['section', 'viewsection', 'name', 'title']
    }, {
      'buildDom': this._buildAdvancedSettingsDom.bind(this),
      'keywords': ['table', 'demand', 'ondemand', 'big']
    }, {
      'header': true,
      'label': 'Summarize',
      'showObs': this._summaryConfig.isSummarySection,
      'items': [{
        'buildDom': () => this._summaryConfig.buildSummaryConfigDom(),
        'keywords': ['section', 'summary', 'summarize', 'group', 'breakdown']
      }]
    }, {
      'header': true,
      'label': 'Sort',
      'items': [{
          'buildDom': this.buildSortDom.bind(this),
          'keywords': ['section', 'sort', 'order']
        }]
    }, {
      'header': true,
      'label': 'Filter',
      'items': [{
          'buildDom': this._buildFilterDom.bind(this),
          'keywords': ['section', 'filters']
        }]
    }, {
      'header': true,
      'label': 'Link Sections',
      'items': [{
          'buildDom': this._buildLinkDom.bind(this),
          'keywords': ['section', 'view', 'linking', 'edit', 'autoscroll', 'autofilter']
        }]
    }, {
      'header': true,
      'label': 'Customize Detail View',
      'showObs': this.isDetail,
      'items': [{
          'buildDom': this._buildDetailTypeDom.bind(this),
          'keywords': ['section', 'detail']
        }, {
          'buildDom': this._buildThemeDom.bind(this),
          'keywords': ['section', 'theme', 'appearance', 'detail']
        }, {
          'buildDom': this._buildLayoutDom.bind(this),
          'keywords': ['section', 'layout', 'arrangement', 'rearrange']
        }]
    }, {
      'header': true,
      'label': 'Customize Grid View',
      'showObs': this.isGrid,
      'items': [{
        'buildDom': this._buildGridStyleDom.bind(this),
        'keywords': ['section', 'zebra', 'stripe', 'appearance', 'grid', 'gridlines', 'style', 'border']
        }]
    }, {
      'header': true,
      'label': 'Chart',
      'showObs': this.isChart,
      'items': [{
        'buildDom': () => this._buildChartConfigDom()
      }]
    }, {
      'header': true,
      'label': 'Custom View',
      'showObs': this.isCustom,
      'items': this._buildCustomTypeItems(),
      'keywords': ['section', 'custom']
    }, {
      'header': true,
      'label': 'Column Display',
      'items': [{
          'buildDom': this._buildSectionFieldsConfig.bind(this),
          'keywords': ['section', 'fields', 'hidden', 'hide', 'show', 'visible']
        }]
    }];
};

ViewConfigTab.prototype.buildSortDom = function() {
  return grainjsDom.maybe(this.activeSectionData, (sectionData) => {
    const section = sectionData.section;

    // Computed to indicate if sort has changed from saved.
    const hasChanged = Computed.create(null, (use) =>
      !isEqual(use(section.activeSortSpec), Sort.parseSortColRefs(use(section.sortColRefs))));

    // Computed array of sortable columns.
    const columns = Computed.create(null, (use) => {
      // Columns is an observable holding an observable array - must call 'use' on it 2x.
      const cols = use(use(use(section.table).columns));
      return cols.filter(col => !use(col.isHiddenCol))
                 .map(col => ({
                   label: use(col.colId),
                   value: col.getRowId(),
                   icon: 'FieldColumn',
                   type: col.type()
                 }));
    });

    // We only want to recreate rows, when the actual columns change.
    const colRefs = Computed.create(null, (use) => {
      return use(section.activeSortSpec).map(col => Sort.getColRef(col));
    });
    const sortRows = koArray(colRefs.get());
    colRefs.addListener((curr, prev) => {
      if (!isEqual(curr, prev)){
        sortRows.assign(curr);
      }
    })

    // Sort row create function for each sort row in the draggableList.
    const rowCreateFn = colRef =>
      this._buildSortRow(colRef, section.activeSortSpec, columns);

    // Reorder function called when sort rows are reordered via dragging.
    const reorder = (...args) => {
      const spec = Sort.reorderSortRefs(section.activeSortSpec.peek(), ...args);
      this._saveSort(spec);
    };

    return grainjsDom('div',
      grainjsDom.autoDispose(hasChanged),
      grainjsDom.autoDispose(columns),
      grainjsDom.autoDispose(colRefs),
      grainjsDom.autoDispose(sortRows),
      // Sort rows.
      kf.draggableList(sortRows, rowCreateFn, {
        reorder,
        removeButton: false,
        drag_indicator: cssDragger,
        itemClass: cssDragRow.className
      }),
      // Add to sort btn & menu & fake sort row.
      this._buildAddToSortBtn(columns),
      // Update/save/reset buttons visible when the sort has changed.
      cssRow(
        cssExtraMarginTop.cls(''),
        grainjsDom.maybe(hasChanged, () => [
          primaryButton('Save', {style: 'margin-right: 8px;'},
            grainjsDom.on('click', () => { section.activeSortJson.save(); }),
            testId('sort-save'),
            grainjsDom.boolAttr('disabled', this.gristDoc.isReadonly),
          ),
          // Let's use same label (revert) as the similar button which appear in the view section.
          // menu.
          basicButton('Revert',
            grainjsDom.on('click', () => { section.activeSortJson.revert(); }),
            testId('sort-reset')
          )
        ]),
        cssFlex(),
        grainjsDom.maybe(section.isSorted, () =>
          basicButton('Update Data', {style: 'margin-left: 8px; white-space: nowrap;'},
            grainjsDom.on('click', () => { updatePositions(this.gristDoc, section); }),
            testId('sort-update'),
            grainjsDom.show((use) => use(use(section.table).supportsManualSort)),
            grainjsDom.boolAttr('disabled', this.gristDoc.isReadonly),
          )
        ),
        grainjsDom.show((use) => use(hasChanged) || use(section.isSorted))
      ),
      testId('sort-menu')
    );
  });
};

// Builds a single row of the sort dom
// Takes the colRef, current sortSpec and array of column select options to show
// in the column select dropdown.
ViewConfigTab.prototype._buildSortRow = function(colRef, sortSpec, columns) {
  const holder = new MultiHolder();

  const col           = Computed.create(holder, () => colRef);
  const details       = Computed.create(holder, (use) => Sort.specToDetails(Sort.findCol(use(sortSpec), colRef)));
  const hasSpecs      = Computed.create(holder, details, (_, details) => Sort.hasOptions(details));
  const isAscending   = Computed.create(holder, details, (_, details) => details.direction === Sort.ASC);

  col.onWrite((newRef) => {
    let specs = sortSpec.peek();
    const colSpec = Sort.findCol(specs, colRef);
    const newSpec = Sort.findCol(specs, newRef);
    if (newSpec) {
      // this column is already there so only swap order
      specs = Sort.swap(specs, colRef, newRef);
      // but keep the directions
      specs = Sort.setSortDirection(specs, colRef, Sort.direction(newSpec))
      specs = Sort.setSortDirection(specs, newRef, Sort.direction(colSpec))
    } else {
      specs = Sort.replace(specs, colRef, Sort.createColSpec(newRef, Sort.direction(colSpec)));
    }
    this._saveSort(specs);
  });

  const computedFlag = (flag, allowedTypes, label) => {
    const computed = Computed.create(holder, details, (_, details) => details[flag] || false);
    computed.onWrite(value => {
      const specs = sortSpec.peek();
      // Get existing details
      const details = Sort.specToDetails(Sort.findCol(specs, colRef));
      // Update flags
      details[flag] = value;
      // Replace the colSpec at the index
      this._saveSort(Sort.replace(specs, Sort.getColRef(colRef), details));
    });
    return {computed, allowedTypes, flag, label};
  }
  const orderByChoice = computedFlag('orderByChoice', ['Choice'], 'Use choice position');
  const naturalSort   = computedFlag('naturalSort', ['Text'], 'Natural sort');
  const emptyLast     = computedFlag('emptyLast', null, 'Empty values last');
  const flags = [orderByChoice, emptyLast, naturalSort];

  const column = columns.get().find(col => col.value === Sort.getColRef(colRef));

  return cssSortRow(
    grainjsDom.autoDispose(holder),
    cssSortSelect(
      select(col, columns)
    ),
    // Use domComputed method for this icon, for dynamic testId, otherwise
    // we are not able add it dynamically.
    grainjsDom.domComputed(isAscending, isAscending =>
      cssSortIconPrimaryBtn(
        "Sort",
        grainjsDom.style("transform", isAscending ? "scaleY(-1)" : "none"),
        grainjsDom.on("click", () => {
          this._saveSort(Sort.flipSort(sortSpec.peek(), colRef));
        }),
        testId("sort-order"),
        testId(isAscending ? "sort-order-asc" : "sort-order-desc")
      )
    ),
    cssSortIconBtn('Remove',
      grainjsDom.on('click', () => {
        const specs = sortSpec.peek();
        if (Sort.findCol(specs, colRef)) {
          this._saveSort(Sort.removeCol(specs, colRef));
        }
      }),
      testId('sort-remove')
    ),
    cssMenu(
      cssBigIconWrapper(
        cssIcon('Dots', grainjsDom.cls(cssBgLightGreen.className, hasSpecs)),
        testId('sort-options-icon'),
      ),
      menu(_ctl => flags.map(({computed, allowedTypes, flag, label}) => {
        // when allowedTypes is null, flag can be used for every column
        const enabled = !allowedTypes || allowedTypes.includes(column.type);
        return cssMenuItem(
            labeledLeftSquareCheckbox(
              computed,
              label,
              grainjsDom.prop('disabled', !enabled),
            ),
            grainjsDom.cls(cssOptionMenuItem.className),
            grainjsDom.cls('disabled', !enabled),
            testId('sort-option'),
            testId(`sort-option-${flag}`),
          );
        },
      ))
    ),
    testId('sort-row')
  );
};

// Build the button to open the menu to add a sort item to the sort dom.
// Takes the full array of sortable column select options.
ViewConfigTab.prototype._buildAddToSortBtn = function(columns) {
  // Observable indicating whether the add new column row is visible.
  const showAddNew = Observable.create(null, false);
  const available = Computed.create(null, (use) => {
    const currentSection = use(this.activeSectionData).section;
    const currentSortSpec = use(currentSection.activeSortSpec);
    const specRowIds = new Set(currentSortSpec.map(_sortRef => Sort.getColRef(_sortRef)));
    return use(columns)
      .filter(_col => !specRowIds.has(_col.value))
  });
  return [
    // Add column button.
    cssRow(
      grainjsDom.autoDispose(showAddNew),
      grainjsDom.autoDispose(available),
      cssTextBtn(
        cssPlusIcon('Plus'), 'Add Column',
        testId('sort-add')
      ),
      grainjsDom.hide((use) => use(showAddNew) || !use(available).length),
      grainjsDom.on('click', () => { showAddNew.set(true); }),
    ),
    // Fake add column row that appears only when the menu is open to select a new column
    // to add to the sort. Immediately destroyed when menu is closed.
    grainjsDom.maybe((use) => use(showAddNew) && use(available), _columns => {
      const col = Observable.create(null, 0);
      const currentSection = this.activeSectionData().section;
      // Function called when a column select value is clicked.
      const onClick = (_col) => {
        showAddNew.set(false); // Remove add row ASAP to prevent flickering
        addToSort(currentSection.activeSortSpec, _col.value, 1);
      };
      const menuCols = _columns.map(_col =>
        menuItem(() => onClick(_col),
          cssMenuIcon(_col.icon),
          _col.label,
          testId('sort-add-menu-row')
        )
      );
      return cssRow(cssSortRow(
        dom.autoDispose(col),
        cssSortSelect(
          select(col, [], {defaultLabel: 'Add Column'}),
          menu(() => [
            menuCols,
            grainjsDom.onDispose(() => { showAddNew.set(false); })
          ], {
            // Trigger to make menu open immediately
            trigger: [(elem, ctl) => {
              ctl.open();
              grainjsDom.onElem(elem, 'click', () => { ctl.close(); });
            }],
            stretchToSelector: `.${cssSortSelect.className}`
          })
        ),
        cssSortIconPrimaryBtn('Sort',
          grainjsDom.style('transform', 'scaleY(-1)')
        ),
        cssSortIconBtn('Remove'),
        cssBigIconWrapper(cssIcon('Dots')),
      ));
    })
  ];
};

ViewConfigTab.prototype._saveSort = function(sortSpec) {
  this.activeSectionData().section.activeSortSpec(sortSpec);
};

ViewConfigTab.prototype._buildNameDom = function() {
  return kf.row(
    1, dom('div.glyphicon.glyphicon-tasks.config_icon'),
    4, kf.label('View'),
    13, kf.text(this.viewModel.name, {}, dom.testId('ViewManager_viewNameInput'))
  );
};

ViewConfigTab.prototype._buildSectionNameDom = function() {
  return kd.maybe(this.activeSectionData, function(sectionData) {
    return kf.row(
      1, dom('div.glyphicon.glyphicon-credit-card.config_icon'),
      4, kf.label('Section'),
      13, kf.text(sectionData.section.titleDef, {}, dom.testId('ViewConfigTab_sectionNameInput'))
    );
  });
};

ViewConfigTab.prototype._makeOnDemand = function(table) {
  // After saving the changed setting, force the reload of the document.
  const onConfirm = () => {
    return table.onDemand.saveOnly(!table.onDemand.peek())
    .then(() => {
      return this.gristDoc.docComm.reloadDoc()
      .catch((err) => {
        // Ignore the expected error from the socket shutdown that we asked for.
        if (!err.message.includes('GristWSConnection disposed')) {
          throw err;
        }
      })
    });
  }

  if (table.onDemand()) {
    confirmModal('Unmark table On-Demand?', 'Unmark On-Demand', onConfirm,
      dom('div', 'If you unmark table ', dom('b', table), ' as On-Demand, ' +
        'its data will be loaded into the calculation engine and will be available ' +
        'for use in formulas. For a big table, this may greatly increase load times.',
        dom('br'), dom('br'), 'Changing this setting will reload the document for all users.')
    );
  } else {
    confirmModal('Make table On-Demand?', 'Make On-Demand', onConfirm,
      dom('div', 'If you make table ', dom('b', table), ' On-Demand, ' +
        'its data will no longer be loaded into the calculation engine and will not be available ' +
        'for use in formulas. It will remain available for viewing and editing.',
        dom('br'), dom('br'), 'Changing this setting will reload the document for all users.')
    );
  }
};

ViewConfigTab.prototype._buildAdvancedSettingsDom = function() {
  return kd.maybe(() => {
    const s = this.activeSectionData();
    return s && !s.section.table().summarySourceTable() ? s : null;
  }, (sectionData) => {

    const table = sectionData.section.table();
    const isCollapsed = ko.observable(true);
    return [
      kf.collapserLabel(isCollapsed, 'Advanced settings', dom.testId('ViewConfig_advanced')),
      kf.helpRow(kd.hide(isCollapsed),
        'Big tables may be marked as "on-demand" to avoid loading them into the data engine.',
        kd.style('text-align', 'left'),
        kd.style('margin-top', '1.5rem')
      ),
      kf.row(kd.hide(isCollapsed),
        kf.label('Table ', dom('b', kd.text(table.tableId)), ':')
      ),
      kf.row(kd.hide(isCollapsed),
        kf.buttonGroup(kf.button(() => this._makeOnDemand(table),
          kd.text(() => table.onDemand() ? 'Unmark On-Demand' : 'Make On-Demand'),
          dom.testId('ViewConfig_onDemandBtn')
        ))
      ),
    ];
  });
};

ViewConfigTab.prototype._buildDetailTypeDom = function() {
  return kd.maybe(this.activeSectionData, (sectionData) => {
    var section = sectionData.section;
    if (this.isDetail()) {
      return kf.row(
        1, kf.label('Type'),
        1, kf.buttonSelect(section.parentKey,
          kf.optionButton('detail', 'List', dom.testId('ViewConfigTab_card')),
          kf.optionButton('single', 'Single', dom.testId('ViewConfigTab_detail'))
        )
      );
    }
  });
};

ViewConfigTab.prototype._buildFilterDom = function() {
  return grainjsDom.maybe(this.activeSectionData, (sectionData) => {
    const section = sectionData.section;
    const docModel = this.gristDoc.docModel;
    const popupControls = new WeakMap();
    const activeFilterBar = section.activeFilterBar;

    const hasChangedObs = Computed.create(null, (use) => use(section.filterSpecChanged) || !use(section.activeFilterBar.isSaved))

    async function save() {
      await docModel.docData.bundleActions("Update Filter settings", () => Promise.all([
        section.saveFilters(),          // Save filter
        section.activeFilterBar.save(), // Save bar
      ]));
    }
    function revert() {
      section.revertFilters();          // Revert filter
      section.activeFilterBar.revert(); // Revert bar
    }

    return [
      grainjsDom.forEach(section.filteredFields, (field) => {
        return cssRow(
          cssIconWrapper(
            cssFilterIcon('FilterSimple', cssNoMarginLeft.cls('')),
            attachColumnFilterMenu(section, field, {
              placement: 'bottom-end', attach: 'body',
              trigger: ['click', (_el, popupControl) => popupControls.set(field, popupControl)],
            }),
          ),
          cssLabel(grainjsDom.text(field.label)),
          cssIconWrapper(
            cssFilterIcon('Remove', dom.on('click', () => field.activeFilter('')),
                          testId('remove-filter')),
          ),
          testId('filter'),
        );
      }),
      cssRow(
        grainjsDom.domComputed((use) => {
          const fields = use(use(section.viewFields).getObservable());
          return cssTextBtn(
            cssPlusIcon('Plus'), 'Add Filter',
            addFilterMenu(fields, popupControls, {placement: 'bottom-end'}),
            testId('add-filter-btn'),
          );
        }),
      ),
      cssRow(
        cssTextBtn(
          testId('toggle-filter-bar'),
          grainjsDom.domComputed((use) => {
            const filterBar = use(activeFilterBar);
            return cssPlusIcon(
              filterBar ? "Tick" : "Plus",
              cssIcon.cls('-green', Boolean(filterBar)),
              testId('toggle-filter-bar-icon'),
            );
          }),
          grainjsDom.on('click', () => activeFilterBar(!activeFilterBar.peek())),
          'Toggle Filter Bar',
        )
      ),
      grainjsDom.maybe(hasChangedObs, () => cssRow(
        cssExtraMarginTop.cls(''),
        testId('save-filter-btns'),
        primaryButton(
          'Save', {style: 'margin-right: 8px'},
          grainjsDom.on('click', save),
          grainjsDom.boolAttr('disabled', this.gristDoc.isReadonly),
        ),
        basicButton(
          'Revert',
          grainjsDom.on('click', revert),
        )
      ))
    ];
  });
};

ViewConfigTab.prototype._buildThemeDom = function() {
  return kd.maybe(this.activeSectionData, (sectionData) => {
    var section = sectionData.section;
    if (this.isDetail()) {
      const theme = Computed.create(null, (use) => use(section.themeDef));
      theme.onWrite(val => section.themeDef.setAndSave(val));
      return cssRow(
        dom.autoDispose(theme),
        select(theme, [
          {label: 'Form',        value: 'form'   },
          {label: 'Compact',     value: 'compact'},
          {label: 'Blocks',      value: 'blocks'  },
        ]),
        testId('detail-theme')
      );
    }
  });
};

ViewConfigTab.prototype._buildGridStyleDom = function() {

  return kd.maybe(this.activeSectionData, (sectionData) => {
    var section = sectionData.section;
    return dom('div',
      kf.row(
        15, kf.label('Horizontal Gridlines'),
        2, kf.checkbox(section.optionsObj.prop('horizontalGridlines'),
                       dom.testId('ViewConfigTab_hGridButton'))
      ),
      kf.row(
        15, kf.label('Vertical Gridlines'),
        2, kf.checkbox(section.optionsObj.prop('verticalGridlines'),
                       dom.testId('ViewConfigTab_vGridButton'))
      ),
      kf.row(
        15, kf.label('Zebra Stripes'),
        2, kf.checkbox(section.optionsObj.prop('zebraStripes'),
                       dom.testId('ViewConfigTab_zebraStripeButton'))
      ),
      dom.testId('ViewConfigTab_gridOptions')
    );
  });
};

ViewConfigTab.prototype._buildChartConfigDom = function() {
  return grainjsDom.maybe(this.viewModel.activeSection, (section) => grainjsDom.create(ChartConfig, this.gristDoc, section));
};

ViewConfigTab.prototype._buildLayoutDom = function() {
  return kd.maybe(this.activeSectionData, (sectionData) => {
    if (this.isDetail()) {
      const view = sectionData.section.viewInstance.peek();
      const layoutEditorObs = ko.computed(() => view && view.recordLayout && view.recordLayout.layoutEditor());
      return cssRow({style: 'margin-top: 16px;'},
        kd.maybe(layoutEditorObs, (editor) => editor.buildFinishButtons()),
        primaryButton('Edit Card Layout',
          dom.autoDispose(layoutEditorObs),
          dom.on('click', () => commands.allCommands.editLayout.run()),
          grainjsDom.hide(layoutEditorObs),
          testId('detail-edit-layout')
        )
      );
    }
  });
};

ViewConfigTab.prototype._buildLinkDom = function() {
  var linkSpecChanged = ko.computed(() =>
    !this.viewModel.viewSections().all().every(vs => vs.isActiveLinkSaved()));

  return dom('div',
    dom.autoDispose(linkSpecChanged),
    kf.buttonGroup(kf.checkButton(this.viewModel.isLinking,
        dom('span', 'Edit Links', dom.testId('viewConfigTab_link')))),
    kd.maybe(this.activeSectionData, (sectionData) => {
      const section = sectionData.section;
      // This section option affects section linking: it tells a link-target section to show rows
      // matching any of the rows in link-source section, not only the current cursor row.
      const filterByAllShown = section.optionsObj.prop('filterByAllShown');
      return kf.row(
        15, kf.label('Filter by all shown'),
        2, kf.checkbox(filterByAllShown, dom.testId('ViewConfigTab_filterByAll'))
      );
    }),
    kd.maybe(linkSpecChanged, () =>
      kf.prompt(
        kf.liteButtonGroup(
          kf.liteButton(() => {
            commands.allCommands.saveLinks.run();
            this.viewModel.isLinking(false);
          }, dom('span.config_icon.left_icon.glyphicon.glyphicon-save'), 'Save'),
          kf.liteButton(() => commands.allCommands.revertLinks.run(),
            dom('span.config_icon.left_icon.glyphicon.glyphicon-refresh'), 'Reset'
          )
        )
      )
    )
  );
};

/**
 * Builds the three items for configuring a `Custom View`:
 *  1) Mode picker: let user choose between 'url' and 'plugin' mode
 *  2) Show if 'url' mode: let user enter the url
 *  3) Show if 'plugin' mode: let user pick a plugin and a section from the list of available plugin.
 */
ViewConfigTab.prototype._buildCustomTypeItems = function() {
  const docPluginManager = this.gristDoc.docPluginManager;
  const activeSection = this.viewModel.activeSection;

  // all available custom sections grouped by their plugin id
  const customSections = _.groupBy(CustomSectionElement.getSections(docPluginManager.pluginsList), s => s.pluginId);

  // all plugin ids which have custom sections
  const allPlugins = Object.keys(customSections);

  // the list of customSections of the selected plugin (computed)
  const customSectionIds = ko.pureComputed(() => {
    const sections = customSections[this.viewModel.activeSection().customDef.pluginId()] || [];
    return sections.map(({sectionId}) => sectionId);
  });

  return [{

    // 1)
    buildDom: () => kd.scope(activeSection, ({customDef}) => kf.buttonSelect(customDef.mode,
      kf.optionButton('url', 'URL', dom.testId('ViewConfigTab_customView_url')),
      kf.optionButton('plugin', 'Plugin', dom.testId('ViewConfigTab_customView_plugin'))))
  }, {

    // 2)
    showObs: () => activeSection().customDef.mode() === "url",
    buildDom: () => kd.scope(activeSection, ({customDef}) => dom('div',
      kf.row(18, kf.text(customDef.url, {placeholder: "Full URL of webpage to show"}, dom.testId('ViewConfigTab_url'))),
      kf.row(5, "Access", 13, dom(kf.select(customDef.access, ['none', 'read table', 'full']), dom.testId('ViewConfigTab_customView_access'))),
      kf.helpRow('none: widget has no access to document.',
                 kd.style('text-align', 'left'),
                 kd.style('margin-top', '1.5rem')),
      kf.helpRow('read table: widget can read the selected table.',
                 kd.style('text-align', 'left'),
                 kd.style('margin-top', '1.5rem')),
      kf.helpRow('full: widget can read, modify, and copy the document.',
                 kd.style('text-align', 'left'),
                 kd.style('margin-top', '1.5rem'))
    )),
  }, {

    // 3)
    showObs: () => activeSection().customDef.mode() === "plugin",
    buildDom: () => kd.scope(activeSection, ({customDef}) => dom('div',
      kf.row(5, "Plugin: ", 13, kf.text(customDef.pluginId, {}, {list: "list_plugin"}, dom.testId('ViewConfigTab_customView_pluginId'))),
      kf.row(5, "Section: ", 13, kf.text(customDef.sectionId, {}, {list: "list_section"},  dom.testId('ViewConfigTab_customView_sectionId'))),
      // For both `customPlugin` and `selectedSection` it is possible for the value not to be in the
      // list of options. Combining <datalist> and <input> allows both to freely edit the value with
      // keyboard and to select it from a list. Although the content of the list seems to be
      // filtered by the current value, which could confuse user into thinking that there are no
      // available options. I think it would be better to have the full list always, but it seems
      // harder to accomplish and is left as a TODO.
      dom('datalist#list_plugin',  kd.foreach(koArray(allPlugins), value => dom('option', {value}))),
      dom('datalist#list_section', kd.scope(customSectionIds, sections => kd.foreach(koArray(sections), (value) => dom('option', {value}))))
      ))
  }];
};

const cssMenuIcon = styled(cssIcon, `
  margin: 0 8px 0 0;

  .${cssMenuItem.className}-sel > & {
    background-color: ${colors.light};
  }
`);

// Note that the width is set to 0 so that flex-shrink works properly with long text values.
const cssSortSelect = styled('div', `
  flex: 1 1 0px;
  margin: 0 6px 0 0;
  min-width: 0;
`);

const cssSortIconBtn = styled(cssIcon, `
  flex: none;
  margin: 0 6px;
  cursor: pointer;
  background-color: ${colors.slate};

  &:hover {
    background-color: ${colors.dark};
  }
`);

const cssSortIconPrimaryBtn = styled(cssSortIconBtn, `
  background-color: ${colors.lightGreen};

  &:hover {
    background-color: ${colors.darkGreen};
  }
`);

const cssTextBtn = styled('div', `
  color: ${colors.lightGreen};
  cursor: pointer;

  &:hover {
    color: ${colors.darkGreen};
  }
`);

const cssPlusIcon = styled(cssIcon, `
  background-color: ${colors.lightGreen};
  cursor: pointer;
  margin: 0px 4px 3px 0;

  .${cssTextBtn.className}:hover > & {
    background-color: ${colors.darkGreen};
  }
`);

const cssDragRow = styled('div', `
  display: flex !important;
  align-items: center;
  margin: 0 16px 0px 0px;
  & > .kf_draggable_content {
    margin: 6px 0;
    flex: 1 1 0px;
    min-width: 0px;
  }
`);

const cssSortRow = styled('div', `
  display: flex;
  align-items: center;
  width: 100%;
`);

const cssFlex = styled('div', `
  flex: 1 1 0;
`);

const cssLabel = styled('div', `
  white-space: nowrap;
  text-overflow: ellipsis;
  overflow: hidden;
  flex-grow: 1;
`);

const cssExtraMarginTop = styled('div', `
  margin-top: 28px;
`);

const cssFilterIcon = cssSortIconBtn;

const cssNoMarginLeft = styled('div', `
  margin-left: 0;
`);

const cssIconWrapper = styled('div', ``);

const cssBigIconWrapper = styled('div', `
  padding: 3px;
  border-radius: 3px;
  cursor: pointer;
  user-select: none;
`);

const cssMenu = styled('div', `
  display: inline-flex;
  cursor: pointer;
  border-radius: 3px;
  border: 1px solid transparent;
  &:hover, &.weasel-popup-open {
    background-color: ${colors.mediumGrey};
  }
`);

const cssBgLightGreen = styled(`div`, `
  background: ${colors.lightGreen}
`)

const cssOptionMenuItem = styled('div', `
  &:hover {
    background-color: ${colors.mediumGrey};
  }
  & label {
    flex: 1;
    cursor: pointer;
  }
  &.disabled * {
    color: ${colors.darkGrey} important;
    cursor: not-allowed;
  }
`)

module.exports = ViewConfigTab;
