// @ts-nocheck
import PropTypes from 'prop-types';
import React from 'react';
import CheckboxTree from 'react-checkbox-tree';
import slugid from 'slugid';

import { tileProxy } from './services';

import '../styles/TilesetFinder.css';

import withPubSub from './hocs/with-pub-sub';

// Configs
import { TRACKS_INFO } from './configs';

export class TilesetFinder extends React.Component {
  constructor(props) {
    super(props);

    // this.localTracks = TRACKS_INFO.filter

    // local tracks are ones that don't have a filetype associated with them
    this.localTracks = TRACKS_INFO.filter((x) => x.local && !x.hidden).map(
      (x) => {
        const y = { ...x };
        y.datatype = x.datatype[0];
        return y;
      },
    );

    this.augmentedTracksInfo = TRACKS_INFO;
    if (window.higlassTracksByType) {
      Object.keys(window.higlassTracksByType).forEach((pluginTrackType) => {
        this.augmentedTracksInfo.push(
          window.higlassTracksByType[pluginTrackType].config,
        );
      });
    }

    if (props.datatype) {
      this.localTracks = this.localTracks.filter(
        (x) => x.datatype[0] === props.datatype,
      );
    } else {
      this.localTracks = this.localTracks.filter(
        (x) => x.orientation === this.props.orientation,
      );
    }

    this.localTracks.forEach((x) => {
      x.uuid = slugid.nice();
    });

    const newOptions = this.prepareNewEntries('', this.localTracks, {});
    const availableTilesetKeys = Object.keys(newOptions);
    const selectedUuid = availableTilesetKeys.length
      ? [availableTilesetKeys[0]]
      : null;
    this.mounted = false;

    this.state = {
      selectedUuid,
      options: newOptions,
      filter: '',
      checked: [],
      expanded: [],
    };

    this.requestTilesetLists();
  }

  componentDidMount() {
    // we want to query for a list of tracks that are compatible with this
    // track orientation

    this.mounted = true;

    this.requestTilesetLists();
    this.searchBox.focus();
  }

  componentWillUnmount() {
    this.mounted = false;
  }

  prepareNewEntries(sourceServer, newEntries, existingOptions) {
    /**
     * Add meta data to new tileset entries before adding
     * them to the list of available options.
     */
    const newOptions = existingOptions;

    const entries = newEntries.map((ne) => {
      const ane = {
        ...ne,
        server: sourceServer,
        tilesetUid: ne.uuid,
        serverUidKey: this.serverUidKey(sourceServer, ne.uuid),
        datatype: ne.datatype,
        name: ne.name,
        uid: slugid.nice(),
      };

      return ane;
    });

    entries.forEach((ne) => {
      newOptions[ne.serverUidKey] = ne;
    });

    return newOptions;
  }

  serverUidKey(server, uid) {
    /**
     * Create a key for a server and uid
     */
    return `${server}/${uid}`;
  }

  requestTilesetLists() {
    let datatypesQuery = null;

    if (this.props.datatype) {
      datatypesQuery = `dt=${this.props.datatype}`;
    } else {
      const datatypes = new Set(
        [].concat(
          ...this.augmentedTracksInfo
            .filter((x) => x.datatype)

            .filter((x) => {
              return (
                x.orientation === this.props.orientation ||
                (this.props.orientation === '1d-vertical' &&
                  x.orientation === '1d-horizontal')
              );
            })
            .map((x) => x.datatype),
        ),
      );

      datatypesQuery = [...datatypes].map((x) => `dt=${x}`).join('&');
    }

    if (!this.props.trackSourceServers) {
      console.warn('No track source servers specified in the viewconf');
      return;
    }

    this.props.trackSourceServers.forEach((sourceServer) => {
      tileProxy.json(
        `${sourceServer}/tilesets/?limit=10000&${datatypesQuery}`,
        (error, data) => {
          if (error) {
            console.error('ERROR:', error);
          } else {
            const newOptions = this.prepareNewEntries(
              sourceServer,
              data.results,
              this.state.options,
            );
            const availableTilesetKeys = Object.keys(newOptions);
            let { selectedUuid } = this.state;

            // if there isn't a selected tileset, select the first received one
            if (!selectedUuid) {
              selectedUuid = availableTilesetKeys.length
                ? [availableTilesetKeys[0]]
                : null;
              const selectedTileset = this.state.options[selectedUuid[0]];
              this.props.selectedTilesetChanged([selectedTileset]);
            }

            if (this.mounted) {
              this.setState({
                selectedUuid,
                options: newOptions,
              });
            }
          }
        },
        this.props.pubSub,
      );
    });
  }

  /**
   * Double clicked on an element. Should be selected
   * and this window will be closed.
   */
  handleOptionDoubleClick(x /* , y */) {
    const value = this.state.options[x.target.value];
    this.props.onDoubleClick(value);
  }

  handleSelectedOptions(selectedOptions) {
    const selectedValues = [];
    const selectedTilesets = [];

    // I don't know why selectedOptions.map doesn't work
    for (let i = 0; i < selectedOptions.length; i++) {
      selectedValues.push(selectedOptions[i]);
      selectedTilesets.push(this.state.options[selectedOptions[i]]);
    }

    this.props.selectedTilesetChanged(selectedTilesets);

    this.setState({ selectedUuid: selectedValues });
  }

  handleSelect() {
    const { selectedOptions } = this.multiSelect;
    const selectedOptionsList = [];

    for (let i = 0; i < selectedOptions.length; i++) {
      const selectedOption = selectedOptions[i];
      selectedOptionsList.push(selectedOption.value);
    }

    this.handleSelectedOptions(selectedOptionsList);
  }

  handleSearchChange() {
    const domElement = this.searchBox;

    this.setState({ filter: domElement.value });
  }

  /*
   * Create the nested checkbox tree by partitioning the
   * list of available datasets according to their group
   *
   * @param {object} datasetsDict A dictionary of id -> tileset
   *  def mappings
   * @param {string} filter A string to filter the results by
   * @returns {array} A list of items that define the nested checkbox
   *  structure
   *  {
   *    'name': 'blah',
   *    'value': 'blah',
   *    'children': [],
   *  }
   */
  partitionByGroup(datasetsDict, filter) {
    const itemsByGroup = {
      '': {
        name: '',
        value: '',
        children: [],
      },
    };

    for (const uuid of Object.keys(datasetsDict)) {
      const item = datasetsDict[uuid];

      if (!item.name.toLowerCase().includes(filter)) {
        continue;
      }

      if ('project_name' in item) {
        const group = item.project_name;

        if (!(group in itemsByGroup)) {
          itemsByGroup[group] = {
            value: group,
            label: group,
            children: [],
          };
        }

        itemsByGroup[group].children.push({
          label: item.name,
          value: uuid,
        });
      } else {
        itemsByGroup[''].children.push({
          label: item.name,
          value: uuid,
        });
      }
    }

    const allItems = itemsByGroup[''].children;
    // coollapse the group lists into one list of objects
    for (const group of Object.keys(itemsByGroup)) {
      if (group !== '') {
        itemsByGroup[group].children.sort((a, b) =>
          a.label.toLowerCase().localeCompare(b.label.toLowerCase(), 'en'),
        );

        allItems.push(itemsByGroup[group]);
      }
    }

    allItems.sort((a, b) =>
      a.label.toLowerCase().localeCompare(b.label.toLowerCase(), 'en'),
    );

    return allItems;
  }

  handleChecked(checked) {
    this.handleSelectedOptions(checked);

    this.setState({ checked });
  }

  handleExpanded(expanded) {
    this.setState({ expanded });
  }

  render() {
    const optionsList = [];
    for (const key in this.state.options) {
      optionsList.push(this.state.options[key]);
    }

    const nestedItems = this.partitionByGroup(
      this.state.options,
      this.state.filter,
    );
    const svgStyle = {
      width: 15,
      height: 15,
      top: 2,
      right: 2,
      position: 'relative',
    };

    const halfSvgStyle = JSON.parse(JSON.stringify(svgStyle));
    halfSvgStyle.opacity = 0.5;

    const form = (
      <form
        onSubmit={(evt) => {
          evt.preventDefault();
        }}
      >
        <div className="tileset-finder-search-bar">
          <span className="tileset-finder-label">Select tileset:</span>
          <input
            ref={(c) => {
              this.searchBox = c;
            }}
            className="tileset-finder-search-box"
            onChange={this.handleSearchChange.bind(this)}
            placeholder="Search Term"
            type="text"
          />
        </div>
        <div className="tileset-finder-checkbox-tree">
          <CheckboxTree
            checked={this.state.checked}
            expanded={this.state.expanded}
            icons={{
              uncheck: (
                <svg style={svgStyle}>
                  <title>Uncheck</title>
                  <use xlinkHref="#square_o" />
                </svg>
              ),
              check: (
                <svg style={svgStyle}>
                  <title>Check</title>
                  <use xlinkHref="#check_square_o" />
                </svg>
              ),
              halfCheck: (
                <svg style={halfSvgStyle}>
                  <title>Half Check</title>
                  <use xlinkHref="#check_square_o" />
                </svg>
              ),
              leaf: (
                <svg style={svgStyle}>
                  <title>Leaf</title>
                  <use xlinkHref="#file_o" />
                </svg>
              ),
              expandClose: (
                <svg style={svgStyle}>
                  <title>Expand Close</title>
                  <use xlinkHref="#chevron_right" />
                </svg>
              ),
              expandOpen: (
                <svg style={svgStyle}>
                  <title>Expand Open </title>
                  <use xlinkHref="#chevron_down" />
                </svg>
              ),
              parentClose: (
                <svg style={svgStyle}>
                  <title>Parent Close</title>
                  <use xlinkHref="#folder_o" />
                </svg>
              ),
              parentOpen: (
                <svg style={svgStyle}>
                  <title>Parent Open</title>
                  <use xlinkHref="#folder_open_o" />
                </svg>
              ),
            }}
            nodes={nestedItems}
            onCheck={this.handleChecked.bind(this)}
            onExpand={this.handleExpanded.bind(this)}
          />
        </div>
      </form>
    );

    return <div>{form}</div>;
  }
}

TilesetFinder.propTypes = {
  datatype: PropTypes.string,
  orientation: PropTypes.string,
  onDoubleClick: PropTypes.func,
  pubSub: PropTypes.object.isRequired,
  selectedTilesetChanged: PropTypes.func,
  trackSourceServers: PropTypes.array,
};

export default withPubSub(TilesetFinder);
