/**
 * This file is part of the NocoBase (R) project.
 * Copyright (c) 2020-2024 NocoBase Co., Ltd.
 * Authors: NocoBase Team.
 *
 * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
 * For more information, please refer to: https://www.nocobase.com/agreement.
 */

import React, { createContext, useCallback, useContext, useMemo, useState } from 'react';
import { createForm } from '@formily/core';
import { observer, useForm } from '@formily/react';

import {
  ActionContextProvider,
  css,
  SchemaComponent,
  useActionContext,
  useAPIClient,
  useCancelAction,
  useCompile,
  usePlugin,
} from '@nocobase/client';

import WorkflowPlugin, { Instruction, useStyles } from '.';
import { useFlowContext } from './FlowContext';
import { lang, NAMESPACE } from './locale';
import { RadioWithTooltip } from './components';
import { uid } from '@nocobase/utils/client';
import { Button, Dropdown } from 'antd';
import { PlusOutlined } from '@ant-design/icons';

interface AddButtonProps {
  upstream;
  branchIndex?: number | null;
  [key: string]: any;
}

export function AddButton(props: AddButtonProps) {
  const { upstream, branchIndex = null } = props;
  const engine = usePlugin(WorkflowPlugin);
  const compile = useCompile();
  const { workflow } = useFlowContext() ?? {};
  const instructionList = Array.from(engine.instructions.getValues()) as Instruction[];
  const { styles } = useStyles();
  const { onCreate, creating } = useAddNodeContext();

  const groups = useMemo(() => {
    const result = [
      { key: 'control', label: `{{t("Control", { ns: "${NAMESPACE}" })}}` },
      { key: 'calculation', label: `{{t("Calculation", { ns: "${NAMESPACE}" })}}` },
      { key: 'collection', label: `{{t("Collection operations", { ns: "${NAMESPACE}" })}}` },
      { key: 'manual', label: `{{t("Manual", { ns: "${NAMESPACE}" })}}` },
      { key: 'extended', label: `{{t("Extended types", { ns: "${NAMESPACE}" })}}` },
    ]
      .map((group) => {
        const groupInstructions = instructionList.filter(
          (item) =>
            item.group === group.key &&
            (item.isAvailable ? item.isAvailable({ engine, workflow, upstream, branchIndex }) : true),
        );

        return {
          ...group,
          type: 'group',
          children: groupInstructions.map((item) => ({
            role: 'button',
            'aria-label': item.type,
            key: item.type,
            label: item.title,
          })),
        };
      })
      .filter((group) => group.children.length);

    return compile(result);
  }, [branchIndex, compile, engine, instructionList, upstream, workflow]);

  const onClick = useCallback(
    async ({ keyPath }) => {
      const [type] = keyPath;
      onCreate({ type, upstream, branchIndex });
    },
    [branchIndex, onCreate, upstream],
  );

  if (!workflow) {
    return null;
  }

  return (
    <div className={styles.addButtonClass}>
      <Dropdown
        menu={{
          items: groups,
          onClick,
        }}
        disabled={workflow.executed}
        overlayClassName={css`
          .ant-dropdown-menu-root {
            max-height: 30em;
            overflow-y: auto;
          }
        `}
      >
        <Button
          aria-label={props['aria-label'] || 'add-button'}
          shape="circle"
          icon={<PlusOutlined />}
          loading={creating?.upstreamId == upstream?.id && creating?.branchIndex === branchIndex}
          size="small"
        />
      </Dropdown>
    </div>
  );
}

function useAddNodeSubmitAction() {
  const form = useForm();
  const api = useAPIClient();
  const { workflow, refresh } = useFlowContext();
  const { presetting, setPresetting, setCreating } = useAddNodeContext();
  const ctx = useActionContext();
  return {
    async run() {
      if (!presetting) {
        return;
      }
      await form.submit();
      setCreating(presetting.data);
      try {
        const {
          data: { data: newNode },
        } = await api.resource('workflows.nodes', workflow.id).create({
          values: {
            ...presetting.data,
            config: {
              ...presetting.data.config,
              ...form.values.config,
            },
          },
        });
        if (typeof form.values.downstreamBranchIndex === 'number' && newNode.downstreamId) {
          await api.resource('flow_nodes').update({
            filterByTk: newNode.downstreamId,
            values: {
              branchIndex: form.values.downstreamBranchIndex,
              upstream: {
                id: newNode.id,
                downstreamId: null,
              },
            },
            updateAssociationValues: ['upstream'],
          });
        }
        ctx.setVisible(false);
        setPresetting(null);
        refresh();
      } catch (err) {
        console.error(err);
      } finally {
        setCreating(null);
      }
    },
  };
}

const AddNodeContext = createContext(null);

export function useAddNodeContext() {
  return useContext(AddNodeContext);
}

const defaultBranchingOptions = [
  {
    label: `{{t('After end of branches', { ns: "${NAMESPACE}" })}}`,
    value: false,
  },
  {
    label: `{{t('Inside of branch', { ns: "${NAMESPACE}" })}}`,
    value: 0,
  },
];

const DownstreamBranchIndex = observer((props) => {
  const { presetting } = useAddNodeContext();
  const { nodes } = useFlowContext();
  const { values } = useForm();
  const options = useMemo(() => {
    if (!presetting?.instruction) {
      return [];
    }
    const { instruction, data } = presetting;
    const downstream = data.upstreamId
      ? nodes.find((item) => item.upstreamId === data.upstreamId && item.branchIndex === data.branchIndex)
      : nodes.find((item) => item.upstreamId === null);
    if (!downstream) {
      return [];
    }
    const branching =
      typeof instruction.branching === 'function' ? instruction.branching(values.config ?? {}) : instruction.branching;
    if (!branching) {
      return [];
    }
    return branching === true ? defaultBranchingOptions : branching;
  }, [presetting, nodes, values.config]);

  if (!options.length) {
    return null;
  }

  const { data } = presetting;

  return (
    <SchemaComponent
      components={{
        RadioWithTooltip,
      }}
      schema={{
        name: `${data.type ?? 'unknown'}-${data.upstreamId ?? 'root'}-${data.branchIndex}`,
        type: 'void',
        properties: {
          downstreamBranchIndex: {
            type: 'number',
            title: lang('Move all downstream nodes to', { ns: NAMESPACE }),
            'x-decorator': 'FormItem',
            'x-component': 'RadioWithTooltip',
            'x-component-props': {
              options,
              direction: 'vertical',
            },
            default: false,
            required: true,
          },
        },
      }}
    />
  );

  // return (
  //   <FormItem label={lang('Move all downstream nodes to', { ns: NAMESPACE })}>
  //     <RadioWithTooltip {...props} options={options} defaultValue={-1} direction="vertical" />
  //   </FormItem>
  // );
});

function PresetFieldset() {
  const { presetting } = useAddNodeContext();
  if (!presetting?.instruction.presetFieldset) {
    return null;
  }
  return (
    <SchemaComponent
      schema={{
        type: 'void',
        properties: {
          config: {
            type: 'object',
            properties: presetting.instruction.presetFieldset,
          },
        },
      }}
    />
  );
}

export function AddNodeContextProvider(props) {
  const api = useAPIClient();
  const compile = useCompile();
  const engine = usePlugin(WorkflowPlugin);
  const [creating, setCreating] = useState(null);
  const [presetting, setPresetting] = useState(null);
  const [formValueChanged, setFormValueChanged] = useState(false);
  const { workflow, nodes, refresh } = useFlowContext() ?? {};

  const form = useMemo(() => createForm(), []);

  const onModalCancel = useCallback(
    (visible) => {
      if (!visible) {
        form.reset();
        form.clearFormGraph('*');
        setPresetting(null);
      }
    },
    [form],
  );

  const create = useCallback(
    async (data) => {
      setCreating(data);
      try {
        await api.resource('workflows.nodes', workflow.id).create({
          values: data,
        });
        refresh();
      } catch (err) {
        console.error(err);
      } finally {
        setCreating(null);
      }
    },
    [api, refresh, workflow.id],
  );

  const onCreate = useCallback(
    ({ type, upstream, branchIndex }) => {
      const instruction = engine.instructions.get(type);
      if (!instruction) {
        console.error(`Instruction "${type}" not found`);
        return;
      }
      const data = {
        key: uid(),
        type,
        upstreamId: upstream?.id ?? null,
        branchIndex,
        title: compile(instruction.title),
        config: instruction.createDefaultConfig?.() ?? {},
      };
      const downstream = upstream?.id
        ? nodes.find((item) => item.upstreamId === data.upstreamId && item.branchIndex === data.branchIndex)
        : nodes.find((item) => item.upstreamId === null);
      if (
        instruction.presetFieldset ||
        ((typeof instruction.branching === 'function' ? instruction.branching(data.config) : instruction.branching) &&
          downstream)
      ) {
        setPresetting({ data, instruction });
        return;
      }

      create(data);
    },
    [compile, create, engine.instructions],
  );

  return (
    <AddNodeContext.Provider value={{ presetting, setPresetting, onCreate, creating, setCreating }}>
      {props.children}
      <ActionContextProvider
        value={{
          visible: Boolean(presetting),
          setVisible: onModalCancel,
          formValueChanged,
          setFormValueChanged,
          openSize: 'small',
        }}
      >
        <SchemaComponent
          components={{
            DownstreamBranchIndex,
            PresetFieldset,
          }}
          scope={{
            useCancelAction,
            useAddNodeSubmitAction,
          }}
          schema={{
            name: `modal`,
            type: 'void',
            'x-decorator': 'FormV2',
            'x-decorator-props': {
              form,
            },
            'x-component': 'Action.Modal',
            title: `{{ t("Add node", { ns: "${NAMESPACE}" }) }}`,
            properties: {
              config: {
                type: 'void',
                'x-component': 'PresetFieldset',
              },
              downstreamBranchIndex: {
                type: 'void',
                'x-component': 'DownstreamBranchIndex',
              },
              footer: {
                'x-component': 'Action.Modal.Footer',
                properties: {
                  actions: {
                    type: 'void',
                    'x-component': 'ActionBar',
                    properties: {
                      cancel: {
                        type: 'void',
                        title: '{{ t("Cancel") }}',
                        'x-component': 'Action',
                        'x-component-props': {
                          useAction: '{{ useCancelAction }}',
                        },
                      },
                      submit: {
                        type: 'void',
                        title: `{{ t("Submit") }}`,
                        'x-component': 'Action',
                        'x-component-props': {
                          type: 'primary',
                          htmlType: 'submit',
                          useAction: '{{ useAddNodeSubmitAction }}',
                        },
                      },
                    },
                  },
                },
              },
            },
          }}
        />
      </ActionContextProvider>
    </AddNodeContext.Provider>
  );
}
