
// use client
'use client';

// imports
import clsx from 'clsx';
import dotProp from 'dot-prop';
import copyToClipboard from 'copy-to-clipboard';
import { Handle, Position, NodeResizer } from '@xyflow/react';
import React, { useEffect, useRef, useState, useContext, useMemo } from 'react';

// icons
import { IconSettings, IconEye, IconTick, IconWarning, IconEyeClosed, IconMaximise, IconMinimise } from '@chirper/react/src/icons';

// locals
import Input from '../Input';
import Timer from '../Timer';
import Config from './Config';
import Loading from '../Loading';
import Tooltip from '../Tooltip';
import ApiSignal from '../../signals/Api';
import ButtonIcon from '../Button/Icon';
import { useComputed, useSignal } from '../../signal';
import { output as OutputSignal, selected as SelectedSignal } from '../Workflow';

// timeout
let timeout;
const debounce = (fn, wait) => {
  clearTimeout(timeout);
  timeout = setTimeout(() => {
    fn();
  }, wait);
};

/**
* component studio line
*
* @param props 
* @returns 
*/
export const ComponentNodeLine = (props = {}) => {

  // return jsx
  return (
    <div className="flex flex-1 items-center mb-1 w-full min-h-12">
      {props.children}
    </div>
  );
};

/**
 * workflow node output
 *
 * @param props 
 */
export const ComponentNodeOutput = (props = {}) => {

  // output
  return (
    <div className={`workflow-output workflow-output-${props.output?.type?.[0]?.id}`}>
      <div className="flex relative items-center px-3 py-1 w-full">
        <p className="w-full text-xs text-right text-gray-600 dark:text-gray-50">
          {props.output?.name || 'N/A'}
        </p>
        <Handle
          id={`output-${props.id}-${props.output?.id}`}
          type="source"
          position={Position.Right}
          className="w-2 h-2 border-0 ring-0 -translate-x-1 -translate-y-1 bg-primary-500"
        />
      </div>
    </div>
  );
};

/**
 * workflow node output
 *
 * @param props 
 */
export const ComponentNodeInput = (props = {}) => {
  // output
  return (
    <div className={`workflow-input workflow-input-${props.input?.type?.[0]?.id}`}>
      <div className="flex relative items-center px-3 py-1 w-full">
        <p className="w-full text-xs text-left text-gray-600 dark:text-gray-50">
          {props.input?.name || 'N/A'}
        </p>
        <Handle
          id={`input-${props.id}-${props.input?.id}`}
          type="target"
          position={Position.Left}
          className="w-2 h-2 border-0 ring-0 -translate-x-1 -translate-y-1 bg-primary-500"
        />
      </div>
    </div>
  );
};

/**
 * workflow node input
 *
 * @param param0 
 * @returns 
 */
const ComponentNode = (props = {}) => {
  // signals
  const [api] = useSignal(ApiSignal);
  const [sOutput] = useSignal(OutputSignal);
  const [sSelected, setSelected] = useSignal(SelectedSignal);

  // use computed
  const simpleOutput = useMemo(() => {
    return {
      node    : sOutput?.node,
      state   : sOutput?.state,
      message : sOutput?.message,
      running : sOutput?.running,
    };
  }, [sOutput, props.output, props.variant]); 
  const actualOutput = useMemo(() => {
    // actual output
    return props.variant === 'message' ? props.output : sOutput?.output;
  }, [sOutput, props.output, props.variant]); 

  // output value
  const outputValue = useMemo(() => {
    // output
    return (props.outputs || []).reduce((accum, item) => {
      // if item
      if (actualOutput?.[props.id]?.[item.id]) {
        accum[item.name] = actualOutput?.[props.id]?.[item.id];
      }
  
      // return accum
      return accum;
    }, {});
  }, [props.id, actualOutput, props.outputs]);
  const inputValue = useMemo(() => {
    return (props.inputs || []).reduce((accum, item) => {
      // get input
      const nodeInput = props.item?.input?.[item.id];
  
      // check input
      if (!nodeInput) return accum;
  
      // get output
      const inputValue = dotProp.get(actualOutput || {}, nodeInput.split('-').join('.'));
      
      // if input value
      if (inputValue) {
        // set to accum
        accum[item.name] = inputValue;
      }
  
      // return accum
      return accum;
    }, {})
  }, [props.item, props.inputs]);

  // ref
  const baseRef = useRef(null);

  // state
  const [name, setName] = useState(props.item?.name || props.node?.name || '');
  const [config, setConfig] = useState(false);
  const [endedAt, setEndedAt] = useState(null);
  const [startedAt, setStartedAt] = useState(null);

  // is selected
  const isSelected = useMemo(() => {
    // return
    return sSelected === props.id;
  }, [sSelected === props.id]);

  // icon component
  const Icon = useMemo(() => {
    // icon component
    return props.iconComponent || IconTick;
  }, [props.iconComponent]);
  const Element = useMemo(() => {
    return props.Element;
  }, [props.Element]);

  /**
   * handle copy
   *
   * @param e 
   */
  const handleCopy = (e) => {
    // check if within `input` element
    if (['TEXTAREA', 'INPUT'].includes(e.target.nodeName)) return;
    
    // prevent default
    e.preventDefault();
    e.stopPropagation();
    
    // json string
    const jsonString = JSON.stringify(props.data, null, 2);

    // copy
    copyToClipboard(jsonString);
  };
   
  // use effect
  useEffect(() => {
    // check selected value
    if (!isSelected) return;
    
    // add listener
    document.addEventListener('copy', handleCopy);

    // remove listener
    return () => document.removeEventListener('copy', handleCopy);
  }, [isSelected]);
  useEffect(() => {
    // variant
    if (props.variant === 'message') return;

    // debounce
    debounce(() => {
      // on change
      props.onChange('name', name);
    }, 500)
  }, [name]);
  useEffect(() => {
    // check output value
    if ((simpleOutput?.node !== props.id) && !Object.keys(outputValue).length) {
      // reset
      setEndedAt(null);
      setStartedAt(null);

      // return
      return;
    }
    
    // check state
    if (!startedAt && (Object.keys(outputValue).length || simpleOutput?.node === props.id || simpleOutput?.running?.includes(props.id))) {
      // set started at
      setStartedAt(new Date().getTime());
    }

    // check ended at
    if (startedAt && Object.keys(outputValue).length) {
      // ended at
      setEndedAt(new Date().getTime());
    }
  }, [startedAt, Object.keys(outputValue).length, simpleOutput?.node, simpleOutput?.running?.length]);
  useEffect(() => {
    // check variant
    if (!props.data.onUpdate) return;
    if (props.variant === 'message') return;
    
    // update node internals
    props.data.onUpdate(props.id);

    // timeout
    const timeout = setTimeout(() => {
      // update node internals
      props.data.onUpdate(props.id);
    }, 500);

    // return
    return () => clearTimeout(timeout);
  }, [props.data?.position?.minimised, Object.keys(outputValue).length, Object.keys(inputValue).length]);

  // return jsx
  if (props.variant === 'message') return (
    !!(Object.keys(outputValue).length || props.running?.includes(props.id)) && (
      <div className="relative">
        {!props.isLast && (
          <div className="absolute top-0 left-0 w-12 h-full -z-0">
            <div className="absolute -bottom-4 top-12 left-1/2 py-2 transform -translate-x-1/2">
              <div className="w-[1px] h-full bg-gray-50 dark:bg-gray-300 rounded" />
            </div>
          </div>
        )}
        <div className="flex flex-row justify-center space-x-3" id={props.id}>
          <div>
            <ButtonIcon
              color="primary"
              className="flex relative justify-center items-center w-12 h-12 z-1"
            >
              {(props.state !== 'completed' && !Object.keys(outputValue).length) ? (
                (props.state === 'failed') ? (
                  <IconWarning className="w-5 h-5 text-red-500" />
                ) : (
                  <Loading className="w-5 h-5 fill-primary-500" />
                )
              ) : (props.icon || (
                <Icon className="w-5 h-5" />
              ))}
            </ButtonIcon>
          </div>
          <div className="flex-1 truncate">
            {((props.current === props.id) && (props.state === 'failed')) ? (
              <div className="p-4 mb-3 truncate bg-red-100 rounded-md">
                <p className="text-sm font-medium text-red-800 truncate">
                  {props.message}
                </p>
              </div>
            ) : (
              <>
                <ComponentNodeLine>
                  {name || props.title}
                </ComponentNodeLine>
                <div className="flex flex-col flex-1 w-full max-w-full">
                  {props.children}
                  {!!Element && (
                    <Element
                      data={props.data}
                      input={inputValue}
                      output={outputValue}
                      inputs={props.inputs || []}
                      variant="message"
                      outputs={props.outputs || []}
                      selected={isSelected}
                    />
                  )}
                </div>
              </>
            )}
          </div>
        </div>
      </div>
    )
  );

  // return jsx
  return (
    <>
      <div key="element" className={clsx(
        'flex relative w-full min-w-[280px]',
        !props.data?.position?.minimised && 'min-h-full',
      )} ref={baseRef} style={{
        minWidth  : props.width || undefined,
        maxWidth  : props.width || undefined,
        minHeight : props.data?.position?.minimised ? undefined : (props.height || undefined),
      }}>
        <NodeResizer
          nodeId={props.id}
          isVisible={!props.data?.position?.minimised && isSelected}
          minWidth={280}
          minHeight={100}
        />
        <div
          onClick={() => isSelected ? setSelected(null) : setSelected(props.id)}
          className={clsx(
            'flex flex-1 flex-col py-3 space-y-3 w-full min-h-full card !bg-primary-LIGHT dark:!bg-gray-DARK',
            endedAt ? (
              (simpleOutput?.state === 'failed') && (simpleOutput?.node === props.id) ? '!ring-red-500' :
              '!ring-green-500'
            ) : (
              isSelected ? '!ring-primary-500' : '!ring-gray-200/20 dark:!ring-gray-400'
            ),
          )}
        >
          <div className="flex flex-row items-center px-3 space-x-2 w-full truncate">
            <div className="flex flex-row flex-1 items-center space-x-2 truncate w-min-0">
              {!!(simpleOutput?.running?.includes(props.id) && (simpleOutput?.state !== 'failed')) && (
                <Loading className="w-4 h-4 fill-primary-500" />
              )}
              <div className="truncate">
                <div className={clsx(
                  'font-semibold drag-handle',
                  endedAt && startedAt && 'text-green-500 truncate'
                )}>
                  {name || props.title}
                </div>
                {!!props.description && (
                  <p className="text-xs text-gray-600 truncate dark:text-gray-50">
                    {props.description}
                  </p>
                )}
              </div>
            </div>
            {!!(startedAt && (simpleOutput?.state !== 'failed')) && (
              <ButtonIcon
                color={endedAt ? 'success' : 'primary'}
              >
                <Timer
                  end={endedAt}
                  start={startedAt}
                />
              </ButtonIcon>
            )}
            <ButtonIcon
              onClick={() => props.data.onChange('position.minimised', !props.data?.position?.minimised)}
              color="primary"
            >
              {props.data?.position?.minimised ? (
                <IconMaximise className="w-5 h-5" />
              ) : (
                <IconMinimise className="w-5 h-5" />
              )}
            </ButtonIcon>
            <ButtonIcon
              onClick={() => setConfig(true)}
              color="primary"
            >
              <IconSettings className="w-5 h-5" />
            </ButtonIcon>
          </div>
          {!!((simpleOutput?.node === props.id) && (simpleOutput?.state === 'failed')) && (
            <div className="px-3">
              <p className="font-semibold text-red-500 break-words">
                {simpleOutput?.message}
              </p>
            </div>
          )}
          {!props.data?.position?.minimised && (
            <div className="flex flex-1">
              {props.children && (
                <div className="flex flex-col flex-1 px-3 max-w-full empty:hidden">{props.children}</div>
              )}
              <div className="flex flex-col flex-1 px-3 max-w-full empty:hidden">
                {!!Element && (
                  <Element
                    data={props.data}
                    input={inputValue}
                    output={outputValue}
                    inputs={props.inputs || []}
                    variant="node"
                    outputs={props.outputs || []}
                    selected={isSelected}
                  />
                )}
              </div>
            </div>
          )}
          {!!props.inputs?.length && (
            <div>
              {(props.inputs || []).map((input) => (
                <ComponentNodeInput key={input?.id} id={props.id} input={input} />
              ))}
            </div>
          )}
          {!!props.outputs?.length && (
            <div>
              {(props.outputs || []).map((output) => (
                <ComponentNodeOutput key={output?.id} id={props.id} output={output} />
              ))}
            </div>
          )}
        </div>
      </div>

      <Config key="config" {...props} open={!!config} onClose={() => setConfig(false)}>
        <div className="flex flex-row items-center space-x-2 w-full">
          <div className="flex-1">
            <Input
              label={api.translate('node:form.name.label', 'Name')}
              name="name"
              type="text"
              value={name}
              onChange={(e) => setName(e.target.value)}
              placeholder={api.translate('node:form.name.placeholder', 'Node Name')}
            />
          </div>
          <Tooltip title={props.item.spec?.hidden ? api.translate('node:tooltip.hidden', 'Hidden') : api.translate('node:tooltip.visible', 'Visible')}>
            <ButtonIcon
              color="primary"
              onClick={() => props.item.onChange('spec.hidden', !props.item.spec?.hidden)}
            >
              {props.item.spec?.hidden ? (
                <IconEyeClosed className="w-5 h-5" />
              ) : (
                <IconEye className="w-5 h-5" />
              )}
            </ButtonIcon>
          </Tooltip>
        </div>
      </Config>
    </>
  );
};

/**
 * @description - Workflow Node Input component
 */
export default ComponentNode;