// @flow
import { Trans } from '@lingui/macro';
import { I18n } from '@lingui/react';
import { type I18n as I18nType } from '@lingui/core';
import { t } from '@lingui/macro';

import * as React from 'react';
import { AutoSizer } from 'react-virtualized';
import SortableVirtualizedItemList from '../UI/SortableVirtualizedItemList';
import SearchBar from '../UI/SearchBar';
import { showWarningBox } from '../UI/Messages/MessageBox';
import Background from '../UI/Background';
import newNameGenerator from '../Utils/NewNameGenerator';
import {
  enumerateEventsBasedBehaviors,
  filterEventsBasedBehaviorsList,
} from './EnumerateEventsBasedBehaviors';
import Clipboard, { SafeExtractor } from '../Utils/Clipboard';
import Window from '../Utils/Window';
import {
  serializeToJSObject,
  unserializeFromJSObject,
} from '../Utils/Serializer';
import { type UnsavedChanges } from '../MainFrame/UnsavedChangesContext';

const EVENTS_BASED_BEHAVIOR_CLIPBOARD_KIND = 'Events Based Behavior';

const styles = {
  listContainer: {
    flex: 1,
  },
};

type State = {|
  renamedEventsBasedBehavior: ?gdEventsBasedBehavior,
  searchText: string,
|};

const getEventsBasedBehaviorName = (
  eventsBasedBehavior: gdEventsBasedBehavior
) => eventsBasedBehavior.getName();

type Props = {|
  project: gdProject,
  eventsBasedBehaviorsList: gdEventsBasedBehaviorsList,
  selectedEventsBasedBehavior: ?gdEventsBasedBehavior,
  onSelectEventsBasedBehavior: (
    eventsBasedBehavior: ?gdEventsBasedBehavior
  ) => void,
  onDeleteEventsBasedBehavior: (
    eventsBasedBehavior: gdEventsBasedBehavior,
    cb: (boolean) => void
  ) => void,
  onRenameEventsBasedBehavior: (
    eventsBasedBehavior: gdEventsBasedBehavior,
    newName: string,
    cb: (boolean) => void
  ) => void,
  onEventsBasedBehaviorRenamed: (
    eventsBasedBehavior: gdEventsBasedBehavior
  ) => void,
  onEditProperties: (eventsBasedBehavior: gdEventsBasedBehavior) => void,
  unsavedChanges?: ?UnsavedChanges,
|};

export default class EventsBasedBehaviorsList extends React.Component<
  Props,
  State
> {
  static defaultProps = {
    onDeleteEventsBasedBehavior: (
      eventsBasedBehavior: gdEventsBasedBehavior,
      cb: boolean => void
    ) => cb(true),
    onRenameEventsBasedBehavior: (
      eventsBasedBehavior: gdEventsBasedBehavior,
      newName: string,
      cb: boolean => void
    ) => cb(true),
  };

  sortableList: ?SortableVirtualizedItemList<gdEventsBasedBehavior>;
  state: State = {
    renamedEventsBasedBehavior: null,
    searchText: '',
  };

  _deleteEventsBasedBehavior = (
    eventsBasedBehavior: gdEventsBasedBehavior,
    { askForConfirmation }: {| askForConfirmation: boolean |}
  ) => {
    const { eventsBasedBehaviorsList } = this.props;

    if (askForConfirmation) {
      const answer = Window.showConfirmDialog(
        "Are you sure you want to remove this behavior? This can't be undone."
      );
      if (!answer) return;
    }

    this.props.onDeleteEventsBasedBehavior(eventsBasedBehavior, doRemove => {
      if (!doRemove) return;

      eventsBasedBehaviorsList.remove(eventsBasedBehavior.getName());
      this._onEventsBasedBehaviorModified();
    });
  };

  _editName = (renamedEventsBasedBehavior: ?gdEventsBasedBehavior) => {
    this.setState(
      {
        renamedEventsBasedBehavior,
      },
      () => {
        if (this.sortableList) this.sortableList.forceUpdateGrid();
      }
    );
  };

  _rename = (eventsBasedBehavior: gdEventsBasedBehavior, newName: string) => {
    const { eventsBasedBehaviorsList } = this.props;
    this.setState({
      renamedEventsBasedBehavior: null,
    });

    if (eventsBasedBehavior.getName() === newName) return;

    if (eventsBasedBehaviorsList.has(newName)) {
      showWarningBox('Another behavior with this name already exists.', {
        delayToNextTick: true,
      });
      return;
    }

    this.props.onRenameEventsBasedBehavior(
      eventsBasedBehavior,
      newName,
      doRename => {
        if (!doRename) return;
        eventsBasedBehavior.setName(newName);
        this._onEventsBasedBehaviorModified();
        this.props.onEventsBasedBehaviorRenamed(eventsBasedBehavior);
      }
    );
  };

  _moveSelectionTo = (
    destinationEventsBasedBehavior: gdEventsBasedBehavior
  ) => {
    const {
      eventsBasedBehaviorsList,
      selectedEventsBasedBehavior,
    } = this.props;
    if (!selectedEventsBasedBehavior) return;

    eventsBasedBehaviorsList.move(
      eventsBasedBehaviorsList.getPosition(selectedEventsBasedBehavior),
      eventsBasedBehaviorsList.getPosition(destinationEventsBasedBehavior)
    );

    this.forceUpdateList();
  };

  forceUpdateList = () => {
    this._onEventsBasedBehaviorModified();
    if (this.sortableList) this.sortableList.forceUpdateGrid();
  };

  _copyEventsBasedBehavior = (eventsBasedBehavior: gdEventsBasedBehavior) => {
    Clipboard.set(EVENTS_BASED_BEHAVIOR_CLIPBOARD_KIND, {
      eventsBasedBehavior: serializeToJSObject(eventsBasedBehavior),
      name: eventsBasedBehavior.getName(),
    });
  };

  _cutEventsBasedBehavior = (eventsBasedBehavior: gdEventsBasedBehavior) => {
    this._copyEventsBasedBehavior(eventsBasedBehavior);
    this._deleteEventsBasedBehavior(eventsBasedBehavior, {
      askForConfirmation: false,
    });
  };

  _pasteEventsBasedBehavior = (index: number) => {
    if (!Clipboard.has(EVENTS_BASED_BEHAVIOR_CLIPBOARD_KIND)) return;

    const clipboardContent = Clipboard.get(
      EVENTS_BASED_BEHAVIOR_CLIPBOARD_KIND
    );
    const copiedEventsBasedBehavior = SafeExtractor.extractObjectProperty(
      clipboardContent,
      'eventsBasedBehavior'
    );
    const name = SafeExtractor.extractStringProperty(clipboardContent, 'name');
    if (!name || !copiedEventsBasedBehavior) return;

    const { project, eventsBasedBehaviorsList } = this.props;

    const newName = newNameGenerator(name, name =>
      eventsBasedBehaviorsList.has(name)
    );

    const newEventsBasedBehavior = eventsBasedBehaviorsList.insertNew(
      newName,
      index
    );

    unserializeFromJSObject(
      newEventsBasedBehavior,
      copiedEventsBasedBehavior,
      'unserializeFrom',
      project
    );
    newEventsBasedBehavior.setName(newName);

    this._onEventsBasedBehaviorModified();
  };

  _renderEventsBasedBehaviorMenuTemplate = (i18n: I18nType) => (
    eventsBasedBehavior: gdEventsBasedBehavior,
    index: number
  ) => {
    return [
      {
        label: i18n._(t`Properties`),
        click: () => this.props.onEditProperties(eventsBasedBehavior),
      },
      {
        type: 'separator',
      },
      {
        label: i18n._(t`Rename`),
        click: () => this._editName(eventsBasedBehavior),
      },
      {
        label: i18n._(t`Remove`),
        click: () =>
          this._deleteEventsBasedBehavior(eventsBasedBehavior, {
            askForConfirmation: true,
          }),
      },
      {
        type: 'separator',
      },
      {
        label: i18n._(t`Copy`),
        click: () => this._copyEventsBasedBehavior(eventsBasedBehavior),
      },
      {
        label: i18n._(t`Cut`),
        click: () => this._cutEventsBasedBehavior(eventsBasedBehavior),
      },
      {
        label: i18n._(t`Paste`),
        enabled: Clipboard.has(EVENTS_BASED_BEHAVIOR_CLIPBOARD_KIND),
        click: () => this._pasteEventsBasedBehavior(index),
      },
    ];
  };

  _addNewEventsBasedBehavior = () => {
    const { eventsBasedBehaviorsList } = this.props;

    const name = newNameGenerator('MyBehavior', name =>
      eventsBasedBehaviorsList.has(name)
    );
    eventsBasedBehaviorsList.insertNew(
      name,
      eventsBasedBehaviorsList.getCount()
    );
    this._onEventsBasedBehaviorModified();
  };

  _onEventsBasedBehaviorModified() {
    if (this.props.unsavedChanges)
      this.props.unsavedChanges.triggerUnsavedChanges();
    this.forceUpdate();
  }

  render() {
    const {
      project,
      eventsBasedBehaviorsList,
      selectedEventsBasedBehavior,
      onSelectEventsBasedBehavior,
    } = this.props;
    const { searchText } = this.state;

    const list = filterEventsBasedBehaviorsList(
      enumerateEventsBasedBehaviors(eventsBasedBehaviorsList),
      searchText
    );

    // Force List component to be mounted again if project or eventsBasedBehaviorsList
    // has been changed. Avoid accessing to invalid objects that could
    // crash the app.
    const listKey = project.ptr + ';' + eventsBasedBehaviorsList.ptr;

    return (
      <Background>
        <div style={styles.listContainer}>
          <AutoSizer>
            {({ height, width }) => (
              <I18n>
                {({ i18n }) => (
                  <SortableVirtualizedItemList
                    key={listKey}
                    ref={sortableList => (this.sortableList = sortableList)}
                    fullList={list}
                    width={width}
                    height={height}
                    onAddNewItem={this._addNewEventsBasedBehavior}
                    addNewItemLabel={<Trans>Add a new behavior</Trans>}
                    getItemName={getEventsBasedBehaviorName}
                    selectedItems={
                      selectedEventsBasedBehavior
                        ? [selectedEventsBasedBehavior]
                        : []
                    }
                    onItemSelected={onSelectEventsBasedBehavior}
                    renamedItem={this.state.renamedEventsBasedBehavior}
                    onRename={this._rename}
                    onMoveSelectionToItem={this._moveSelectionTo}
                    buildMenuTemplate={this._renderEventsBasedBehaviorMenuTemplate(
                      i18n
                    )}
                    reactDndType="GD_EVENTS_BASED_BEHAVIOR"
                  />
                )}
              </I18n>
            )}
          </AutoSizer>
        </div>
        <SearchBar
          value={searchText}
          onRequestSearch={() => {}}
          onChange={text =>
            this.setState({
              searchText: text,
            })
          }
        />
      </Background>
    );
  }
}
