// @ts-nocheck
import clsx from 'clsx';
import React from 'react';

import ContextMenuContainer from './ContextMenuContainer';
import ContextMenuItem from './ContextMenuItem';
import NestedContextMenu from './NestedContextMenu';

// Configs
import { THEME_DARK, TRACKS_INFO_BY_TYPE } from './configs';
import OPTIONS_INFO from './options-info';

// Styles
import classes from '../styles/ContextMenu.module.scss';

/**
 *  We're going to get the track object to see if it has a
 *  context menu handler that will give use context menu items
 *  to display
 *
 * @param (Dict) track The config for the track we're getting context menu
 *  items for
 *
 * @param (TrackRenderer) trackRenderer The track renderer for the view
 *  containing this track. We'll use it to get the track's object
 *
 * @param (Dict) position The position of the track. Relevant are the canvasLeft,
 *  canvasRight positions which mark where the track starts relative to the
 *  canvas. This is important because all coordinates within a track are relative
 *  to left and top coordinates.
 */

function findTrackContextMenuItems(track, trackRenderer, position) {
  let trackObj = trackRenderer.getTrackObject(track.uid);

  // The track may be a LeftTrackModifier track
  trackObj = trackObj.originalTrack || trackObj;

  // See if the track will provide us with context menu items
  if (trackObj.contextMenuItems) {
    let trackLeft = position.canvasLeft - trackObj.position[0];
    let trackTop = position.canvasTop - trackObj.position[1];

    if (trackObj.flipText) {
      // This is a left track modifier track so we need to swap the
      // left and right values
      const temp = trackLeft;
      trackLeft = trackTop;
      trackTop = temp;
    }

    const items = trackObj.contextMenuItems(trackLeft, trackTop);

    return items || [];
  }

  // The track doesn't have a contextMenuItems function so we it's
  // obviously not providing any items.
  return [];
}

export default class SeriesListMenu extends ContextMenuContainer {
  getConfigureSeriesMenu(position, bbox, track) {
    const menuItems = {};

    // plugin tracks can offer their own options
    // if they clash with the default higlass options
    // they will override them
    const pluginOptionsInfo =
      window.higlassTracksByType?.[track.type] &&
      window.higlassTracksByType[track.type].config &&
      window.higlassTracksByType[track.type].config.optionsInfo;

    if (pluginOptionsInfo) {
      for (const key of Object.keys(pluginOptionsInfo)) {
        OPTIONS_INFO[key] = pluginOptionsInfo[key];
      }
    }

    if (
      !TRACKS_INFO_BY_TYPE[track.type] ||
      !TRACKS_INFO_BY_TYPE[track.type].availableOptions
    ) {
      return null;
    }

    for (const optionType of TRACKS_INFO_BY_TYPE[track.type].availableOptions) {
      if (optionType in OPTIONS_INFO) {
        menuItems[optionType] = { name: OPTIONS_INFO[optionType].name };

        // can we dynamically generate some options?
        // should be used if the options depend on tileset info or other current state
        if (OPTIONS_INFO[optionType].generateOptions) {
          const generatedOptions =
            OPTIONS_INFO[optionType].generateOptions(track);

          if (!menuItems[optionType].children) {
            menuItems[optionType].children = {};
          }

          for (const generatedOption of generatedOptions) {
            const optionSelectorSettings = {
              name: generatedOption.name,
              value: generatedOption.value,
              handler: () => {
                track.options[optionType] = generatedOption.value;
                this.props.onTrackOptionsChanged(track.uid, track.options);
                this.props.closeMenu();
              },
            };

            menuItems[optionType].children[generatedOption.value] =
              optionSelectorSettings;
          }
        }

        if (OPTIONS_INFO[optionType].inlineOptions) {
          // we can simply select this option from the menu
          for (const inlineOptionKey in OPTIONS_INFO[optionType]
            .inlineOptions) {
            const inlineOption =
              OPTIONS_INFO[optionType].inlineOptions[inlineOptionKey];

            // check if there's already available options (e.g.
            // "Top right") for this option type (e.g. "Label
            // position")
            if (!menuItems[optionType].children) {
              menuItems[optionType].children = {};
            }

            const optionSelectorSettings = {
              name: inlineOption.name,
              value: inlineOption.value,
              // missing handler to be filled in below
            };

            // is there a custom component available for picking this
            // option type value (e.g. 'custom' color scale)
            if (inlineOption.componentPickers?.[track.type]) {
              optionSelectorSettings.handler = () => {
                this.props.onConfigureTrack(
                  track,
                  inlineOption.componentPickers[track.type],
                );
                this.props.closeMenu();
              };
            } else {
              // the menu option defines a potential value for this option
              // type (e.g. "top right")
              optionSelectorSettings.handler = () => {
                track.options[optionType] = inlineOption.value;
                this.props.onTrackOptionsChanged(track.uid, track.options);
                this.props.closeMenu();
              };
            }

            menuItems[optionType].children[inlineOptionKey] =
              optionSelectorSettings;
          }
        } else if (OPTIONS_INFO[optionType].componentPickers?.[track.type]) {
          // there's an option picker registered
          menuItems[optionType].handler = () => {
            this.props.onConfigureTrack(
              track,
              OPTIONS_INFO[optionType].componentPickers[track.type],
            );
            this.props.closeMenu();
          };
        }
      }
    }

    return (
      <NestedContextMenu
        key="config-series-menu"
        closeMenu={this.props.closeMenu}
        menuItems={menuItems}
        orientation={this.state.orientation}
        parentBbox={bbox}
        position={position}
        theme={this.props.theme}
      />
    );
  }

  /**
   * Return a list of track types that can be used
   * with the data for this track
   *
   * @param {Object} position The position where to draw ths menu (e.g. {left: 42, top: 88})
   *
   * @param (Object) bbox
   *  The bounding box of the parent menu, used to determine whether
   *  to draw the child menu on the left or the right
   *
   * @param (Object) track The track definition for this series (as in the viewconf)
   */
  getTrackTypeItems(position, bbox, track) {
    // get the datatype of the current track
    //

    // if we've loaded external track types, list them here
    if (window.higlassTracksByType) {
      // Extend `TRACKS_INFO_BY_TYPE` with the configs of plugin tracks.
      Object.keys(window.higlassTracksByType).forEach((pluginTrackType) => {
        TRACKS_INFO_BY_TYPE[pluginTrackType] =
          window.higlassTracksByType[pluginTrackType].config;
      });
    }

    let { datatype } = track;

    let orientation = null;
    // make sure that this is a valid track type before trying to
    // look up other tracks that can substitute for it
    if (track.type in TRACKS_INFO_BY_TYPE) {
      if (!datatype) {
        datatype = TRACKS_INFO_BY_TYPE[track.type].datatype[0];
      }
      ({ orientation } = TRACKS_INFO_BY_TYPE[track.type]);
    }

    // see which other tracks can display a similar datatype
    const availableTrackTypes = Object.values(TRACKS_INFO_BY_TYPE)
      .filter((x) => x.datatype)
      .filter((x) => x.orientation)
      .filter((x) => x.datatype.includes(datatype))
      .filter((x) => x.orientation === orientation)
      .map((x) => x.type);

    const menuItems = {};
    for (let i = 0; i < availableTrackTypes.length; i++) {
      menuItems[availableTrackTypes[i]] = {
        value: availableTrackTypes[i],
        name: availableTrackTypes[i],
        handler: () => {
          this.props.onChangeTrackType(track.uid, availableTrackTypes[i]);
        },
      };
    }

    return (
      <NestedContextMenu
        key="track-type-items"
        closeMenu={this.props.closeMenu}
        menuItems={menuItems}
        orientation={this.state.orientation}
        parentBbox={bbox}
        position={position}
        theme={this.props.theme}
      />
    );
  }

  getSubmenu() {
    if (this.state.submenuShown) {
      // the bounding box of the element which initiated the subMenu
      // necessary so that we can position the submenu next to the initiating
      // element
      const bbox = this.state.submenuSourceBbox;
      const position =
        this.state.orientation === 'left'
          ? {
              left: this.state.left,
              top: bbox.top,
            }
          : {
              left: this.state.left + bbox.width + 7,
              top: bbox.top,
            };

      // When a submenu is requested, the onMouseEnter handler of the
      // item that requested it provides a structure containing the option
      // picked as well as some data associated with it
      // e.g. {"option": "configure-series", data: track}
      const subMenuData = this.state.submenuShown;
      const track = subMenuData.value;

      if (subMenuData.option === 'track-type') {
        return this.getTrackTypeItems(position, bbox, track);
      }
      return this.getConfigureSeriesMenu(position, bbox, track);
    }

    return <div />;
  }

  getDivideByMenuItem() {
    if (this.props.series.data && this.props.series.data.type === 'divided') {
      const newData = {
        tilesetUid: this.props.series.data.children[0].tilesetUid,
        server: this.props.series.data.children[0].server,
      };

      // this track is already being divided
      return (
        <ContextMenuItem
          className={classes['context-menu-item']}
          onClick={() =>
            this.props.onChangeTrackData(this.props.series.uid, newData)
          }
          onMouseEnter={(e) => this.handleOtherMouseEnter(e)}
        >
          <span className={classes['context-menu-span']}>Remove divisor</span>
        </ContextMenuItem>
      );
    }

    return (
      <ContextMenuItem
        className={classes['context-menu-item']}
        onClick={() => this.props.onAddDivisor(this.props.series)}
        onMouseEnter={(e) => this.handleOtherMouseEnter(e)}
      >
        <span className={classes['context-menu-span']}>Divide by</span>
      </ContextMenuItem>
    );
  }

  componentWillUnmount() {}

  render() {
    let exportDataMenuItem = null;

    const trackContextMenuItems = findTrackContextMenuItems(
      this.props.track,
      this.props.trackRenderer,
      this.props.position,
    );

    if (
      TRACKS_INFO_BY_TYPE[this.props.series.type] &&
      TRACKS_INFO_BY_TYPE[this.props.series.type].exportable
    ) {
      exportDataMenuItem = (
        <ContextMenuItem
          className={classes['context-menu-item']}
          onClick={() =>
            this.props.onExportData(
              this.props.hostTrack.uid,
              this.props.track.uid,
            )
          }
          onMouseEnter={(e) => this.handleOtherMouseEnter(e)}
        >
          <span className={classes['context-menu-span']}>Export Data</span>
        </ContextMenuItem>
      );
    }

    // if a track can't be replaced, this.props.onAddSeries
    // will be null so we don't need to display the menu item
    const replaceSeriesItem = this.props.onAddSeries ? (
      <ContextMenuItem
        className={classes['context-menu-item']}
        onClick={() => {
          this.props.onCloseTrack(this.props.series.uid);
          this.props.onAddSeries(this.props.hostTrack.uid);
        }}
        onMouseEnter={(e) => this.handleOtherMouseEnter(e)}
      >
        <span className={classes['context-menu-span']}>Replace Series</span>
      </ContextMenuItem>
    ) : null;

    return (
      <div
        ref={(c) => {
          this.div = c;
        }}
        className={clsx(classes['context-menu'], {
          [classes['context-menu-dark']]: this.props.theme === THEME_DARK,
        })}
        data-menu-type="SeriesListMenu"
        onMouseLeave={this.props.handleMouseLeave}
        style={{
          left: this.state.left,
          top: this.state.top,
        }}
      >
        {trackContextMenuItems.map((x) => (
          <ContextMenuItem
            key={x.label}
            onClick={(evt) => {
              x.onClick(evt, (newOptions) => {
                // We're going to pass in a handler to that the track
                // can use to change its options
                this.props.onTrackOptionsChanged(this.props.track.uid, {
                  ...this.props.track.options,
                  ...newOptions,
                });
              });
              this.props.closeMenu();
            }}
            onMouseEnter={(e) => this.handleOtherMouseEnter(e)}
            className={classes['context-menu-item']}
          >
            <span className={classes['context-menu-span']}>{x.label}</span>
          </ContextMenuItem>
        ))}
        {trackContextMenuItems.length > 0 && (
          <hr className={classes['context-menu-hr']} />
        )}
        <ContextMenuItem
          onClick={() => {}}
          onMouseEnter={(e) =>
            this.handleItemMouseEnter(e, {
              option: 'configure-series',
              value: this.props.track,
            })
          }
          onMouseLeave={(e) => this.handleMouseLeave(e)}
        >
          Configure Series
          <svg className={classes['play-icon']}>
            <title>Play</title>
            <use xlinkHref="#play" />
          </svg>
        </ContextMenuItem>

        <ContextMenuItem
          className={classes['context-menu-item']}
          onClick={() => {}}
          onMouseEnter={(e) =>
            this.handleItemMouseEnter(e, {
              option: 'track-type',
              value: this.props.track,
            })
          }
          onMouseLeave={(e) => this.handleMouseLeave(e)}
        >
          <span className={classes['context-menu-span']}>
            Track Type
            <svg className={classes['play-icon']}>
              <title>Play</title>
              <use xlinkHref="#play" />
            </svg>
          </span>
        </ContextMenuItem>

        {exportDataMenuItem}

        {this.getDivideByMenuItem()}

        <ContextMenuItem
          className={classes['context-menu-item']}
          onClick={this.props.onCloseTrack}
          onMouseEnter={(e) => this.handleOtherMouseEnter(e)}
        >
          <span className={classes['context-menu-span']}>Close Series</span>
        </ContextMenuItem>

        {replaceSeriesItem}

        {/*
          this.props.series.type === 'heatmap' ?
          <ContextMenuItem
            onClick={() => {
              this.props.onDivideSeries(this.props.series.uid);
              // this.props.onCloseTrack(this.props.series.uid);
              // this.props.onAddSeries(this.props.hostTrack.uid);
            }}
            onMouseEnter={e => this.handleOtherMouseEnter(e)}
            className={classes["context-menu-item"]}
          >
            <span className={classes["context-menu-span"]}>
              {'Divide Series By'}
            </span>
          </ContextMenuItem>
          : null
        */}

        {this.getSubmenu()}
      </div>
    );
  }
}
