/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 */
import React, {useState} from 'react';
import {plugin} from '../index';
import {
  Button,
  Input,
  Modal,
  Tooltip,
  Dropdown,
  Menu,
  Typography,
  TreeSelect,
  Space,
} from 'antd';
import {
  MoreOutlined,
  PauseCircleOutlined,
  PlayCircleOutlined,
} from '@ant-design/icons';
import {usePlugin, useValue, Layout} from 'flipper-plugin';
import {FrameworkEventType} from '../types';

/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 */

export const Controls: React.FC = () => {
  const instance = usePlugin(plugin);
  const searchTerm = useValue(instance.uiState.searchTerm);
  const isPaused = useValue(instance.uiState.isPaused);

  const frameworkEventMonitoring: Map<FrameworkEventType, boolean> = useValue(
    instance.uiState.frameworkEventMonitoring,
  );

  const onSetEventMonitored: (
    eventType: FrameworkEventType,
    monitored: boolean,
  ) => void = (eventType: FrameworkEventType, monitored: boolean) => {
    instance.uiState.frameworkEventMonitoring.update((draft) =>
      draft.set(eventType, monitored),
    );
  };

  return (
    <Layout.Horizontal pad="small" gap="small">
      <Input
        value={searchTerm}
        onChange={(e) => instance.uiState.searchTerm.set(e.target.value)}
      />
      <Button
        type="default"
        shape="circle"
        onClick={() => instance.setPlayPause(!instance.uiState.isPaused.get())}
        icon={
          <Tooltip
            title={isPaused ? 'Resume live updates' : 'Pause incoming updates'}>
            {isPaused ? <PlayCircleOutlined /> : <PauseCircleOutlined />}
          </Tooltip>
        }></Button>
      <MoreOptionsMenu
        onSetEventMonitored={onSetEventMonitored}
        frameworkEventTypes={[...frameworkEventMonitoring.entries()]}
      />
    </Layout.Horizontal>
  );
};

function MoreOptionsMenu({
  onSetEventMonitored,
  frameworkEventTypes,
}: {
  onSetEventMonitored: (
    eventType: FrameworkEventType,
    monitored: boolean,
  ) => void;
  frameworkEventTypes: [FrameworkEventType, boolean][];
}) {
  const [showFrameworkEventsModal, setShowFrameworkEventsModal] =
    useState(false);

  const moreOptionsMenu = (
    <Menu>
      <Menu.Item
        onClick={() => {
          setShowFrameworkEventsModal(true);
        }}>
        Framework event monitoring
      </Menu.Item>
    </Menu>
  );

  return (
    <>
      <Dropdown
        key="more"
        mouseLeaveDelay={0.7}
        overlay={moreOptionsMenu}
        placement="bottomRight">
        <Button type="text" icon={<MoreOutlined style={{fontSize: 20}} />} />
      </Dropdown>

      {/*invisible until shown*/}
      <FrameworkEventsMonitoringModal
        frameworkEventTypes={frameworkEventTypes}
        onSetEventMonitored={onSetEventMonitored}
        visible={showFrameworkEventsModal}
        onCancel={() => setShowFrameworkEventsModal(false)}
      />
    </>
  );
}

function FrameworkEventsMonitoringModal({
  visible,
  onCancel,
  onSetEventMonitored,
  frameworkEventTypes,
}: {
  visible: boolean;
  onCancel: () => void;
  onSetEventMonitored: (
    eventType: FrameworkEventType,
    monitored: boolean,
  ) => void;
  frameworkEventTypes: [FrameworkEventType, boolean][];
}) {
  const selectedFrameworkEvents = frameworkEventTypes
    .filter(([, selected]) => selected)
    .map(([eventType]) => eventType);

  const treeData = buildTreeSelectData(
    frameworkEventTypes.map(([type]) => type),
  );

  return (
    <Modal
      title="Framework event monitoring"
      visible={visible}
      footer={null}
      onCancel={onCancel}>
      <Space direction="vertical" size="large">
        <Typography.Text>
          Monitoring an event will cause the relevant node in the visualizer and
          tree to highlight briefly. Additionally a running total of the number
          of each event will be show in the tree inline
        </Typography.Text>

        <TreeSelect
          treeCheckable
          showSearch={false}
          showCheckedStrategy={TreeSelect.SHOW_PARENT}
          placeholder="Select nodes to monitor"
          virtual={false} //for scrollbar
          style={{
            width: '100%',
          }}
          treeData={treeData}
          treeDefaultExpandAll
          value={selectedFrameworkEvents}
          onSelect={(_: any, node: any) => {
            for (const leaf of getAllLeaves(node)) {
              onSetEventMonitored(leaf, true);
            }
          }}
          onDeselect={(_: any, node: any) => {
            for (const leaf of getAllLeaves(node)) {
              onSetEventMonitored(leaf, false);
            }
          }}
        />
      </Space>
    </Modal>
  );
}

type TreeSelectNode = {
  title: string;
  key: string;
  value: string;
  children: TreeSelectNode[];
};

/**
 * In tree select you can select a parent which implicitly selects all children, we find them all here as the real state
 * is in terms of the leaf nodes
 */
function getAllLeaves(treeSelectNode: TreeSelectNode) {
  const result: string[] = [];
  function getAllLeavesRec(node: TreeSelectNode) {
    console.log(node);
    if (node.children.length > 0) {
      for (const child of node.children) {
        getAllLeavesRec(child);
      }
    } else {
      result.push(node.key);
    }
  }
  getAllLeavesRec(treeSelectNode);
  return result;
}

/**
 * transformed flat event type data structure into tree
 */
function buildTreeSelectData(eventTypes: string[]): TreeSelectNode[] {
  const root: TreeSelectNode = buildTreeSelectNode('root', 'root');

  eventTypes.forEach((eventType) => {
    const eventSubtypes = eventType.split(':');
    let currentNode = root;

    // Find the parent node for the current id
    for (let i = 0; i < eventSubtypes.length - 1; i++) {
      let foundChild = false;
      for (const child of currentNode.children) {
        if (child.title === eventSubtypes[i]) {
          currentNode = child;
          foundChild = true;
          break;
        }
      }
      if (!foundChild) {
        const newNode: TreeSelectNode = buildTreeSelectNode(
          eventSubtypes[i],
          eventSubtypes.slice(0, i + 1).join(':'),
        );

        currentNode.children.push(newNode);
        currentNode = newNode;
      }
    }
    // Add the current id as a child of the parent node
    currentNode.children.push(
      buildTreeSelectNode(
        eventSubtypes[eventSubtypes.length - 1],
        eventSubtypes.slice(0, eventSubtypes.length).join(':'),
      ),
    );
  });

  return root.children;
}

function buildTreeSelectNode(title: string, fullValue: string): TreeSelectNode {
  return {
    title: title,
    key: fullValue,
    value: fullValue,
    children: [],
  };
}
