import { __ } from '@wordpress/i18n';
import { applyFilters } from '@wordpress/hooks';
import { useBlockProps, InspectorControls, useInnerBlocksProps, store as blockEditorStore } from '@wordpress/block-editor';
import { useState, useEffect, useRef } from '@wordpress/element';
import { useSelect, useDispatch } from '@wordpress/data';
import { TabPanel, PanelBody } from '@wordpress/components';
import { cog, styles } from '@wordpress/icons';
import './editor.scss';
import { useUniqueBlockClass } from '../../components/hooks/useUniqueBlockClass';
import { SpacingControls, LayoutControls, SizingControl, BackgroundControl, BorderControl, PositionControl, EffectControl } from '../../components/style-controls';
import StyleTabs from '../../components/style/StyleTabs';
import ResponsiveControl from '../../components/controls/ResponsiveControl';
import { DeviceProvider } from '../../components/controls/DeviceContext';
import { useDeviceSettings, generateBlockStyles } from '../../components/controls/blockStyles';
import ColumnTag from '../../components/settings/ColumnTag';
import { CHILD_NAME, createColumnsFromWidths, makeColumnWidthAttrs } from './structures';
import StructurePicker from './components/StructurePicker';
import AddColumnAppender from './components/AddColumnAppender';

const BLOCK_NAME = 'axcelersblocks/columns';
const DEVICES = [ 'desktop', 'tablet', 'mobile' ];
const RESPONSIVE_EMPTY = { desktop: '', tablet: '', mobile: '' };

const getResponsiveValue = ( values = {}, device ) => {
  const index = DEVICES.indexOf( device );
  for ( let i = index; i >= 0; i-- ) {
    const value = values?.[ DEVICES[ i ] ];
    if ( value ) {
      return value;
    }
  }
  return '';
};

const parsePercentWidth = ( value ) => {
  if ( typeof value !== 'string' ) {
    return null;
  }

  const match = value.trim().match( /^(\d+(?:\.\d+)?)%$/ );
  return match ? parseFloat( match[ 1 ] ) : null;
};

const hasWrappingColumnWidths = ( columns = [], device ) => {
  const widths = columns
    .map( ( column ) => parsePercentWidth( getResponsiveValue( column.width || {}, device ) ) )
    .filter( ( width ) => width !== null );

  if ( widths.length < 2 ) {
    return false;
  }

  return widths.reduce( ( total, width ) => total + width, 0 ) > 100.01;
};

const shouldUseStackedAppender = ( flexDirection = {}, flexWrap = {}, columns = [], device ) => {
  const direction = getResponsiveValue( flexDirection, device );
  const wrap = getResponsiveValue( flexWrap, device );

  if ( direction === 'row' || direction === 'row-reverse' ) {
    return false;
  }

  if ( direction === 'column' || direction === 'column-reverse' ) {
    return true;
  }

  return (
    ( wrap === 'wrap' || wrap === 'wrap-reverse' ) &&
    hasWrappingColumnWidths( columns, device )
  );
};

const isColumnDirection = ( direction ) => direction === 'column' || direction === 'column-reverse';

const normalizeWidthMemory = ( memory = {} ) =>
  DEVICES.reduce( ( next, device ) => {
    next[ device ] = Array.isArray( memory?.[ device ] ) ? memory[ device ] : [];
    return next;
  }, {} );

const getDeviceWidths = ( columns = [], device ) =>
  columns.map( ( column ) => column.width?.[ device ] || '' );

const getEqualWidths = ( count ) => {
  const pct = Math.round( ( 100 / count ) * 100 ) / 100;
  return Array.from( { length: count }, () => `${ pct }%` );
};

const areSameArray = ( a = [], b = [] ) =>
  a.length === b.length && a.every( ( value, index ) => value === b[ index ] );

const areSameResponsiveWidth = ( a = {}, b = {} ) =>
  DEVICES.every( ( device ) => ( a?.[ device ] || '' ) === ( b?.[ device ] || '' ) );

const getDirectionClasses = ( flexDirection = {} ) =>
  DEVICES
    .map( ( device ) => {
      const direction = getResponsiveValue( flexDirection, device );
      return direction === 'column' || direction === 'column-reverse'
        ? `axcelersblocks-columns--stacked-${ device }`
        : '';
    } )
    .filter( Boolean )
    .join( ' ' );

const getAppenderLayoutCss = ( isStacked ) => isStacked
  ? {
      appender: 'position:absolute!important;inset:auto!important;top:auto!important;right:0!important;bottom:-18px!important;left:0!important;transform:none!important;align-self:stretch;width:100%;min-height:36px;height:36px;padding:0;box-sizing:border-box;overflow:visible;pointer-events:none;z-index:20;',
      line: 'top:50%;right:0;bottom:auto;left:0;width:auto;height:2px;transform:translateY(-50%);pointer-events:none;',
    }
  : {
      appender: 'position:absolute!important;inset:auto!important;top:0!important;right:-18px!important;bottom:0!important;left:auto!important;transform:none!important;align-self:stretch;width:36px;min-height:72px;height:auto;padding:0;box-sizing:border-box;overflow:visible;pointer-events:none;z-index:20;',
      line: 'top:0;right:auto;bottom:0;left:50%;width:2px;height:auto;transform:translateX(-50%);pointer-events:none;',
    };

const generateColumnsAppenderStyles = ( uniqueClass, devices = {}, flexDirection = {}, flexWrap = {}, columns = [] ) => {
  if ( ! uniqueClass ) {
    return '';
  }

  const selector = `.editor-styles-wrapper .${ uniqueClass }.axcelersblocks-columns > .axcelersblocks-columns__appender`;

  return [ 'mobile', 'tablet', 'desktop' ]
    .filter( ( device ) => devices?.[ device ] )
    .map( ( device ) => {
      const layout = getAppenderLayoutCss( shouldUseStackedAppender( flexDirection, flexWrap, columns, device ) );
      const css = `${ selector }{${ layout.appender }}${ selector }::before{${ layout.line }}`;
      const media = devices?.[ device ]?.media || '';
      return media ? `${ media } { ${ css } }` : css;
    } )
    .join( '\n' );
};

const hasCssValue = ( value ) => value !== undefined && value !== null && value !== '';

const shouldClearColumnFlexBasis = ( flexBasisValue, widthValue, previousWidthValue = '', allowCurrentWidthMatch = false ) => {
  if ( ! hasCssValue( flexBasisValue ) ) {
    return false;
  }

  if ( allowCurrentWidthMatch && flexBasisValue === widthValue ) {
    return true;
  }

  return hasCssValue( previousWidthValue ) && previousWidthValue !== widthValue && flexBasisValue === previousWidthValue;
};

export default function Edit({ attributes, setAttributes, clientId }) {
  const { tagName = 'div', queryEnabled = false, anchor, uniqueClass } = attributes;
  useUniqueBlockClass({ clientId, uniqueClass, setAttributes, blockName: 'axcelersblocks-columns' });
  const devices = useDeviceSettings();
  const [activeStyleTab, setActiveStyleTab] = useState('main');

  // Inner Column blocks are the source of truth. The structure picker shows
  // while empty (non-query); "+ Add Column" appends after that.
  const childColumns = useSelect(
    (select) => select(blockEditorStore).getBlocks(clientId).map((block) => ({
      clientId: block.clientId,
      width: block.attributes?.width || {},
      flexBasis: block.attributes?.flexBasis || {},
    })),
    [clientId]
  );
  const childrenIds = childColumns.map((column) => column.clientId);
  const columnCount = childrenIds.length;
  const { replaceInnerBlocks, updateBlockAttributes } = useDispatch(blockEditorStore);
  const showPicker = !queryEnabled && columnCount === 0;

  const prevStateRef = useRef({
    count: columnCount,
    direction: JSON.stringify(attributes.flexDirection || {}),
    widths: childColumns.map((column) => column.width || {}),
  });
  const prevFlexDirectionRef = useRef(attributes.flexDirection || {});
  const hasCleanedMirroredFlexBasisRef = useRef(false);

  // Cleanup for a previous implementation that mirrored Column width into
  // flexBasis. In Columns children, width owns the column structure; an old
  // percentage flexBasis would override a manually edited responsive width.
  useEffect(() => {
    if (queryEnabled || columnCount === 0) {
      return;
    }

    const previousWidths = prevStateRef.current.widths || [];
    const allowCurrentWidthMatch = !hasCleanedMirroredFlexBasisRef.current;

    childColumns.forEach(({ clientId: columnId, width = {}, flexBasis = {} }, index) => {
      const nextFlexBasis = DEVICES.reduce((next, device) => {
        const flexBasisValue = flexBasis?.[device] || '';
        const widthValue = width?.[device] || '';
        const previousWidthValue = previousWidths?.[index]?.[device] || '';
        next[device] = shouldClearColumnFlexBasis(flexBasisValue, widthValue, previousWidthValue, allowCurrentWidthMatch)
          ? ''
          : flexBasisValue;
        return next;
      }, {});

      if (JSON.stringify(nextFlexBasis) === JSON.stringify({
        desktop: flexBasis?.desktop || '',
        tablet: flexBasis?.tablet || '',
        mobile: flexBasis?.mobile || '',
      })) {
        return;
      }

      updateBlockAttributes(columnId, { flexBasis: nextFlexBasis });
    });

    hasCleanedMirroredFlexBasisRef.current = true;
  }, [childColumns, columnCount, queryEnabled, updateBlockAttributes]);

  // Keep visual column widths in sync with the Columns flex direction:
  // stacked directions should fill the container, while returning to row restores
  // the previous row widths instead of leaving all children at 100%.
  useEffect(() => {
    if (queryEnabled || columnCount === 0) {
      prevFlexDirectionRef.current = attributes.flexDirection || {};
      return;
    }

    const previousFlexDirection = prevFlexDirectionRef.current || {};
    const currentFlexDirection = attributes.flexDirection || {};
    const currentMemory = normalizeWidthMemory(attributes.rowColumnWidths);
    const nextMemory = normalizeWidthMemory(attributes.rowColumnWidths);
    const nextWidths = childColumns.map((column) => ({
      ...RESPONSIVE_EMPTY,
      ...(column.width || {}),
    }));
    let didChangeWidths = false;
    let didChangeMemory = false;

    DEVICES.forEach((device) => {
      const previousDirection = getResponsiveValue(previousFlexDirection, device);
      const currentDirection = getResponsiveValue(currentFlexDirection, device);
      const wasStacked = isColumnDirection(previousDirection);
      const isStacked = isColumnDirection(currentDirection);
      const deviceWidths = getDeviceWidths(childColumns, device);
      const isAlreadyFullWidth = deviceWidths.every((width) => width === '100%');

      if (isStacked) {
        if (!isAlreadyFullWidth) {
          nextMemory[device] = deviceWidths;
          if (!areSameArray(nextMemory[device], currentMemory[device])) {
            didChangeMemory = true;
          }
          nextWidths.forEach((width) => {
            if (width[device] !== '100%') {
              width[device] = '100%';
              didChangeWidths = true;
            }
          });
        }
        return;
      }

      if (!wasStacked) {
        return;
      }

      const rememberedWidths = nextMemory[device];
      const restoredWidths = rememberedWidths.length === columnCount && rememberedWidths.some(Boolean)
        ? rememberedWidths
        : getEqualWidths(columnCount);

      nextWidths.forEach((width, index) => {
        const restoredWidth = restoredWidths[index] || '';
        if (restoredWidth && width[device] !== restoredWidth) {
          width[device] = restoredWidth;
          didChangeWidths = true;
        }
      });
    });

    if (didChangeMemory) {
      setAttributes({ rowColumnWidths: nextMemory });
    }

    if (didChangeWidths) {
      childColumns.forEach((column, index) => {
        const currentWidth = {
          ...RESPONSIVE_EMPTY,
          ...(column.width || {}),
        };
        const nextWidth = nextWidths[index];

        if (!areSameResponsiveWidth(currentWidth, nextWidth)) {
          updateBlockAttributes(column.clientId, { width: nextWidth });
        }
      });
    }

    prevFlexDirectionRef.current = currentFlexDirection;
  }, [attributes.flexDirection, attributes.rowColumnWidths, childColumns, columnCount, queryEnabled, setAttributes, updateBlockAttributes]);

  // Re-equalize column widths only when the column count changes. Manual width
  // edits belong to the child Column block's responsive Sizing control and must
  // remain saved exactly per device.
  useEffect(() => {
    const prevState = prevStateRef.current;
    const directionKey = JSON.stringify(attributes.flexDirection || {});
    const currentWidths = childColumns.map((column) => column.width || {});
    const count = childrenIds.length;
    const didCountChange = count !== prevState.count;
    const didDirectionChange = directionKey !== prevState.direction;
    const didWidthChange = JSON.stringify(currentWidths) !== JSON.stringify(prevState.widths || []);

    const setPreviousState = (widths = currentWidths) => {
      prevStateRef.current = {
        count,
        direction: directionKey,
        widths,
      };
    };

    if (queryEnabled || count === 0 || (!didCountChange && !didDirectionChange && !didWidthChange) || (didCountChange && prevState.count === 0)) {
      setPreviousState();
      return;
    }

    if (didCountChange) {
      const pct = Math.round((100 / count) * 100) / 100;
      const widthAttrs = makeColumnWidthAttrs(pct, attributes.flexDirection);
      childrenIds.forEach((id) => updateBlockAttributes(id, widthAttrs));
      setPreviousState(childrenIds.map(() => widthAttrs.width));
      return;
    }

    setPreviousState();
  }, [childrenIds, childColumns, queryEnabled, updateBlockAttributes, attributes.flexDirection]);

  const settingsTabControls = applyFilters(
    'axcelersblocks.settingsTabControls',
    null,
    { blockName: BLOCK_NAME, clientId, attributes, setAttributes, queryEnabled }
  );

  const blockProps = useBlockProps({
    id: anchor || undefined,
    className: [
      'axcelersblocks-columns',
      getDirectionClasses( attributes.flexDirection ),
      uniqueClass,
    ].filter( Boolean ).join( ' ' ),
  });

  const { children, ...innerBlocksProps } = useInnerBlocksProps(
    {
      ...blockProps,
      'data-columns': columnCount,
      'data-query-enabled': queryEnabled ? '1' : undefined,
      'data-tag': tagName,
    },
    {
      allowedBlocks: [CHILD_NAME],
      // Query mode seeds a single locked column; otherwise the picker creates
      // the columns and the "+ Add Column" appender adds more.
      template: queryEnabled ? [[CHILD_NAME, {}]] : undefined,
      templateLock: queryEnabled ? 'all' : false,
      renderAppender: false,
    }
  );

  const onPickStructure = (widths) => {
    replaceInnerBlocks(clientId, createColumnsFromWidths(widths, attributes.flexDirection), false);
  };

  return (
    <>
      <InspectorControls>
        <TabPanel className="axcelersblocks-tabs" activeClass="is-active" tabs={[{ name: 'settings', title: <>{cog}</>, className: 'settings-tab' }, { name: 'style', title: <>{styles}</>, className: 'style-tab' }]}>
          {(tab) => (
            <>
              {tab.name === 'settings' && (
                <>
                  <PanelBody title={__('Columns', 'axcelersblocks')} initialOpen>
                    <ColumnTag value={tagName} onChange={(val) => setAttributes({ tagName: val })} />
                  </PanelBody>
                  {settingsTabControls}
                </>
              )}
              {tab.name === 'style' && (
                <DeviceProvider>
                  <div className="axcelersblocks-style-tab-wrapper">
                    <ResponsiveControl renderControl={() => (
                      <StyleTabs uniqueClass={uniqueClass} activeTab={activeStyleTab} setActiveTab={setActiveStyleTab} tabs={[{ name: 'main', label: __('Main', 'axcelersblocks') }]} renderContent={() => (
                        <>
                          <LayoutControls attributes={attributes} setAttributes={setAttributes} activeStyleTab={activeStyleTab} />
                          <SpacingControls attributes={attributes} setAttributes={setAttributes} activeStyleTab={activeStyleTab} />
                          <SizingControl attributes={attributes} setAttributes={setAttributes} activeStyleTab={activeStyleTab} />
                          <BackgroundControl attributes={attributes} setAttributes={setAttributes} activeStyleTab={activeStyleTab} />
                          <BorderControl attributes={attributes} setAttributes={setAttributes} activeStyleTab={activeStyleTab} />
                          <PositionControl attributes={attributes} setAttributes={setAttributes} activeStyleTab={activeStyleTab} />
                          <EffectControl attributes={attributes} setAttributes={setAttributes} activeStyleTab={activeStyleTab} />
                        </>
                      )} />
                    )} />
                  </div>
                </DeviceProvider>
              )}
            </>
          )}
        </TabPanel>
      </InspectorControls>
      { uniqueClass ? (
        <style>
          {generateBlockStyles(uniqueClass, devices, attributes)}
          {generateColumnsAppenderStyles(uniqueClass, devices, attributes.flexDirection, attributes.flexWrap, childColumns)}
        </style>
      ) : null }
      { showPicker ? (
        <div {...blockProps}>
          <span className="axcelersblocks-columns__badge" aria-hidden="true">{__('Columns', 'axcelersblocks')}</span>
          <StructurePicker onSelect={onPickStructure} />
        </div>
      ) : (
        <div {...innerBlocksProps}>
          <span className="axcelersblocks-columns__badge" aria-hidden="true">{__('Columns', 'axcelersblocks')}</span>
          {children}
          { ! queryEnabled && (
            <div className="axcelersblocks-columns__appender">
              <AddColumnAppender clientId={clientId} flexDirection={attributes.flexDirection} />
            </div>
          ) }
        </div>
      ) }
    </>
  );
}
