/*
 * Copyright OpenSearch Contributors
 * SPDX-License-Identifier: Apache-2.0
 */

import React, { Component, useCallback, useMemo, useState } from 'react';
import { cloneDeep, get } from 'lodash';
import { useDebounce } from 'react-use';
import { i18n } from '@osd/i18n';
import { EuiCallOut } from '@elastic/eui';
import { useTypedDispatch, useTypedSelector } from '../../utils/state_management';
import { DefaultEditorAggParams } from '../../../../../vis_default_editor/public';
import { Title } from './title';
import { useIndexPatterns, useVisualizationType } from '../../utils/use';
import {
  OpenSearchDashboardsContextProvider,
  useOpenSearchDashboards,
} from '../../../../../opensearch_dashboards_react/public';
import { VisBuilderServices } from '../../../types';
import { AggParam, IAggType, IFieldParamType } from '../../../../../data/public';
import { saveDraftAgg, editDraftAgg } from '../../utils/state_management/visualization_slice';
import { setError } from '../../utils/state_management/metadata_slice';
import { Storage } from '../../../../../opensearch_dashboards_utils/public';

const PANEL_KEY = 'SECONDARY_PANEL';

export function SecondaryPanel() {
  const { draftAgg, aggConfigParams } = useTypedSelector(
    (state) => state.visualization.activeVisualization!
  );
  const isEditorValid = useTypedSelector((state) => !state.metadata.editor.errors[PANEL_KEY]);
  const [touched, setTouched] = useState(false);
  const dispatch = useTypedDispatch();
  const vizType = useVisualizationType();
  const indexPattern = useIndexPatterns().selected;
  const { services } = useOpenSearchDashboards<VisBuilderServices>();
  const {
    data: {
      search: { aggs: aggService },
    },
  } = services;
  const schemas = vizType.ui.containerConfig.data.schemas.all;

  const aggConfigs = useMemo(() => {
    return (
      indexPattern && draftAgg && aggService.createAggConfigs(indexPattern, [cloneDeep(draftAgg)])
    );
  }, [draftAgg, aggService, indexPattern]);
  const aggConfig = aggConfigs?.aggs[0];

  const metricAggs = useMemo(
    () =>
      indexPattern
        ? aggService.createAggConfigs(
            indexPattern,
            cloneDeep(
              aggConfigParams.filter((aggConfigParam) => aggConfigParam.schema === 'metric')
            )
          ).aggs
        : [],
    [aggConfigParams, aggService, indexPattern]
  );

  const selectedSchema = useMemo(
    () => schemas.find((schema) => schema.name === aggConfig?.schema),
    [aggConfig?.schema, schemas]
  );

  const showAggParamEditor = !!(aggConfig && indexPattern);

  const closeMenu = useCallback(() => {
    dispatch(editDraftAgg(undefined));
  }, [dispatch]);

  const handleSetValid = useCallback(
    (isValid: boolean) => {
      // Set validity state globally
      dispatch(
        setError({
          key: PANEL_KEY,
          error: !isValid,
        })
      );
    },
    [dispatch]
  );

  // Autosave is agg value has changed and edits are valid
  useDebounce(
    () => {
      if (isEditorValid) {
        dispatch(saveDraftAgg());
      } else {
        // To indicate that an invalid edit was made
        setTouched(true);
      }
    },
    200,
    [draftAgg, isEditorValid]
  );

  return (
    <div className="vbConfig__section vbConfig--secondary">
      <Title title={selectedSchema?.title ?? 'Edit'} isSecondary closeMenu={closeMenu} />
      {showAggParamEditor && (
        <OpenSearchDashboardsContextProvider
          services={{
            ...services,
            storage: new Storage(window.localStorage), // This is necessary for filters
          }}
        >
          <EditorErrorBoundary>
            <DefaultEditorAggParams
              className="vbConfig__aggEditor"
              agg={aggConfig!}
              indexPattern={indexPattern!}
              setValidity={handleSetValid}
              setTouched={setTouched}
              schemas={schemas}
              formIsTouched={touched}
              groupName={selectedSchema?.group ?? 'none'}
              metricAggs={metricAggs}
              state={{
                data: {},
                description: '',
                title: '',
              }}
              setAggParamValue={function <T extends string | number | symbol>(
                aggId: string,
                paramName: T,
                value: any
              ): void {
                aggConfig.params[paramName] = value;
                dispatch(editDraftAgg(aggConfig.serialize()));
              }}
              onAggTypeChange={function (aggId: string, aggType: IAggType): void {
                aggConfig.type = aggType;

                // Persist field if the new agg type supports the existing field
                const fieldParam = (aggType.params as AggParam[]).find(
                  ({ type }) => type === 'field'
                );
                if (fieldParam) {
                  const availableFields = (fieldParam as IFieldParamType).getAvailableFields(
                    aggConfig
                  );
                  const indexField = availableFields.find(
                    ({ name }) => name === get(draftAgg, 'params.field')
                  );

                  if (indexField) {
                    aggConfig.params.field = indexField;
                  }
                }

                dispatch(editDraftAgg(aggConfig.serialize()));
              }}
            />
          </EditorErrorBoundary>
        </OpenSearchDashboardsContextProvider>
      )}
    </div>
  );
}

class EditorErrorBoundary extends Component<{}, { error?: any }> {
  state = {
    error: undefined,
  };

  static getDerivedStateFromError(error: any) {
    return { error };
  }

  componentDidCatch(error) {
    // eslint-disable-next-line no-console
    console.error(error);
  }

  render() {
    if (this.state.error) {
      return (
        <EuiCallOut
          title={i18n.translate('visBuilder.aggParamsEditor.errorTitle', {
            defaultMessage: 'Error',
          })}
          color="danger"
          iconType="alert"
        >
          <p>
            {i18n.translate('visBuilder.aggParamsEditor.errorMsg', {
              defaultMessage: 'Something went wrong while editing the aggregation',
            })}
          </p>
        </EuiCallOut>
      );
    }
    return this.props.children;
  }
}