import React, { useEffect, useMemo, useState } from 'react';

const { ModuleContainer, StyleContainer, elementClassnames } = window?.divi?.module || {};
const allowedLineTypes = new Set(['text', 'custom', 'flag', 'symbol', 'image', 'space']);

const ModuleStyles = ({ elements, settings, mode, state, noStyleTag, moduleType }) => (
  <StyleContainer mode={mode} state={state} noStyleTag={noStyleTag}>
    {elements.style({
      attrName: 'module',
      styleProps: { disabledOn: { disabledModuleVisibility: settings?.disabledModuleVisibility } },
    })}
    {moduleType === 'parent' && elements.style({ attrName: 'body' })}
  </StyleContainer>
);

const ModuleScriptData = ({ elements }) => (
  <React.Fragment>{elements.scriptData({ attrName: 'module' })}</React.Fragment>
);

const moduleClassnames = ({ classnamesInstance, attrs }) => {
  classnamesInstance.add(elementClassnames({ attrs: attrs?.module?.decoration ?? {} }));
};

const getPreviewConfig = () => {
  if (window?.BRCEDivi5Preview) {
    return window.BRCEDivi5Preview;
  }

  const scriptSrc = document?.currentScript?.src;
  if (!scriptSrc) {
    return {};
  }

  const params = new URL(scriptSrc).searchParams;
  return {
    ajaxUrl: decodeURIComponent(params.get('brce_ajax_url') || ''),
    action: params.get('brce_action') || '',
    nonce: params.get('brce_nonce') || '',
  };
};

const previewConfig = getPreviewConfig();

const getAttrValue = (attrs, attrName) => {
  if (!attrs) {
    return undefined;
  }

  if (typeof attrs?.asMutable === 'function') {
    attrs = attrs.asMutable({ deep: true });
  }

  let attr = attrs[attrName];
  if (typeof attr?.asMutable === 'function') {
    attr = attr.asMutable({ deep: true });
  }

  if (attr?.innerContent?.desktop?.value !== undefined) {
    return attr.innerContent.desktop.value;
  }

  if (attr?.innerContent?.desktop !== undefined && typeof attr.innerContent.desktop !== 'object') {
    return attr.innerContent.desktop;
  }

  if (attr !== undefined && typeof attr !== 'object') {
    return attr;
  }

  return undefined;
};

const extractLineTypes = (children) => {
  const lineTypes = [];
  const collect = (value) => {
    if (!value) {
      return;
    }

    if (Array.isArray(value)) {
      value.forEach(collect);
      return;
    }

    if (typeof value !== 'object') {
      return;
    }

    if (typeof value?.asMutable === 'function') {
      value = value.asMutable({ deep: true });
    }

    const attrs = value.props?.attrs ?? value.attrs ?? value.attributes;
    const lineType = getAttrValue(attrs, 'line_type');
    if (allowedLineTypes.has(lineType)) {
      lineTypes.push(lineType);
      return;
    }

    collect(value.props?.content);
    collect(value.props?.children);
    collect(value.content);
    collect(value.children);
    collect(value.innerBlocks);
  };

  collect(children);
  return lineTypes;
};

const callStoreSelector = (selector, method, ...args) => {
  if (!selector || typeof selector[method] !== 'function') {
    return undefined;
  }

  try {
    return selector[method](...args);
  } catch (error) {
    return undefined;
  }
};

const getDiviDataStores = () => {
  const stores = [
    window?.divi?.data,
    window?.wp?.data,
    window?.top?.divi?.data,
    window?.top?.wp?.data,
  ].filter(Boolean);

  return stores.filter((store, index) => stores.indexOf(store) === index);
};

const extractLineTypesFromStore = (moduleId) => {
  if (!moduleId) {
    return [];
  }

  for (const dataStore of getDiviDataStores()) {
    if (!dataStore?.select) {
      continue;
    }

    const selector = dataStore.select('divi/edit-post');
    const moduleWithChildren = callStoreSelector(selector, 'getModuleWithChildren', moduleId);
    const nestedLineTypes = extractLineTypes(moduleWithChildren);
    if (nestedLineTypes.length) {
      return nestedLineTypes;
    }

    const structureIds = callStoreSelector(selector, 'getModuleStructureIds', moduleId);
    if (!Array.isArray(structureIds)) {
      continue;
    }

    const lineTypes = [];
    structureIds.flat(Infinity).forEach((childId) => {
      if (!childId || childId === moduleId) {
        return;
      }

      const childName = callStoreSelector(selector, 'getModuleName', childId);
      if (childName && childName !== 'brce/currency-line-text') {
        return;
      }

      const childAttrs = callStoreSelector(selector, 'getModuleAttrs', childId);
      const lineType = getAttrValue(childAttrs, 'line_type');
      if (allowedLineTypes.has(lineType)) {
        lineTypes.push(lineType);
      }
    });

    if (lineTypes.length) {
      return lineTypes;
    }
  }

  return [];
};

const Placeholder = ({ children }) => (
  <div style={{
    padding: '2em 0',
    background: '#6c2eb9',
    color: '#fff',
    fontSize: '12px',
    fontWeight: '600',
    textAlign: 'center',
    borderRadius: '1em',
  }}>
    <h3 style={{ color: '#000', fontWeight: '900' }}>BeRocket Currency Exchange</h3>
    {children}
  </div>
);

const CurrencyPreview = ({ attrs, lineTypes, moduleId }) => {
  const [state, setState] = useState({ html: '', isLoading: true, error: '' });
  const [storeRevision, setStoreRevision] = useState(0);
  const attrsKey = useMemo(() => JSON.stringify(attrs ?? {}), [attrs]);
  const passedLinesKey = useMemo(() => JSON.stringify(lineTypes), [lineTypes]);
  const storeLineTypes = useMemo(
    () => extractLineTypesFromStore(moduleId),
    [moduleId, storeRevision, passedLinesKey],
  );
  const linesKey = useMemo(
    () => JSON.stringify(storeLineTypes.length ? storeLineTypes : lineTypes),
    [storeLineTypes, lineTypes],
  );

  useEffect(() => {
    const update = () => setStoreRevision((revision) => revision + 1);
    const unsubscribers = getDiviDataStores()
      .map((dataStore) => (typeof dataStore?.subscribe === 'function' ? dataStore.subscribe(update) : null))
      .filter(Boolean);

    document.addEventListener('brce_divi5_line_type_changed', update);
    return () => {
      unsubscribers.forEach((unsubscribe) => unsubscribe());
      document.removeEventListener('brce_divi5_line_type_changed', update);
    };
  }, []);

  useEffect(() => {
    if (!previewConfig.ajaxUrl || !previewConfig.action || !previewConfig.nonce) {
      setState({ html: '', isLoading: false, error: 'Currency Exchange not displayed in Builder' });
      return undefined;
    }

    const controller = new AbortController();
    const body = new FormData();
    body.append('action', previewConfig.action);
    body.append('nonce', previewConfig.nonce);
    body.append('attrs', attrsKey);
    body.append('line_types', linesKey);
    setState((current) => ({ ...current, isLoading: true, error: '' }));

    fetch(previewConfig.ajaxUrl, {
      body,
      method: 'POST',
      credentials: 'same-origin',
      signal: controller.signal,
    })
      .then((response) => response.json())
      .then((response) => {
        if (!response?.success) {
          throw new Error(response?.data?.message || 'Currency Exchange not displayed in Builder');
        }
        setState({ html: response?.data?.html || '', isLoading: false, error: '' });
      })
      .catch((error) => {
        if (error.name !== 'AbortError') {
          setState({ html: '', isLoading: false, error: error.message || 'Currency Exchange not displayed in Builder' });
        }
      });

    return () => controller.abort();
  }, [attrsKey, linesKey]);

  useEffect(() => {
    if (!state.isLoading && !state.error && state.html && typeof window.brjsf_ce === 'function') {
      window.brjsf_ce();
    }
  }, [state.html, state.isLoading, state.error]);

  if (state.isLoading) {
    return <div className="et-fb-loader-wrapper"><div className="et-fb-loader" /></div>;
  }
  if (state.error || !state.html) {
    return <Placeholder>{state.error || 'Currency Exchange not displayed in Builder'}</Placeholder>;
  }
  return <div dangerouslySetInnerHTML={{ __html: state.html }} />;
};

const ChildPreview = ({ attrs }) => {
  const lineType = getAttrValue(attrs, 'line_type') || 'text';
  return <Placeholder>{lineType}</Placeholder>;
};

const ChildChangeTracker = ({ attrs }) => {
  const lineType = getAttrValue(attrs, 'line_type') || 'text';

  useEffect(() => {
    document.dispatchEvent(new CustomEvent('brce_divi5_line_type_changed', { bubbles: true }));
  }, [lineType]);

  return null;
};

export const createCurrencyModule = (metadata, moduleType) => ({
  metadata,
  renderers: {
    edit: ({ attrs, content, children, id, name, elements }) => {
      const lineTypes = moduleType === 'parent' ? extractLineTypes(content ?? children) : [];
      const Styles = (props) => <ModuleStyles {...props} moduleType={moduleType} />;

      return (
        <ModuleContainer
          attrs={attrs}
          elements={elements}
          id={id}
          moduleClassName={metadata.moduleClassName}
          name={name}
          scriptDataComponent={ModuleScriptData}
          stylesComponent={Styles}
          classnamesFunction={moduleClassnames}
        >
          {elements.styleComponents({ attrName: 'module' })}
          {moduleType === 'parent' && elements.styleComponents({ attrName: 'body' })}
          {moduleType === 'child' && <ChildChangeTracker attrs={attrs} />}
          <div className="et_pb_module_inner">
            {moduleType === 'parent'
              ? <CurrencyPreview attrs={attrs} lineTypes={lineTypes} moduleId={id} />
              : <ChildPreview attrs={attrs} />}
          </div>
        </ModuleContainer>
      );
    },
  },
  placeholderContent: {
    module: { meta: { adminLabel: { desktop: { value: metadata.title } } } },
    ...metadata.defaultAttrs,
  },
});
