import { css } from '@emotion/css';
import { useId, useMemo } from 'react';
import * as React from 'react';
import { useAsync } from 'react-use';

import {
  type AnnotationQuery,
  type DataSourceInstanceSettings,
  getDataSourceRef,
  type GrafanaTheme2,
  type SelectableValue,
} from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { Trans, t } from '@grafana/i18n';
import { getDataSourceSrv } from '@grafana/runtime';
import { usePanelPluginMetasMap } from '@grafana/runtime/internal';
import { type VizPanel } from '@grafana/scenes';
import { type AnnotationPanelFilter } from '@grafana/schema';
import {
  Button,
  Checkbox,
  Field,
  FieldSet,
  Input,
  MultiSelect,
  Select,
  useStyles2,
  Stack,
  Alert,
  type ComboboxOption,
  Combobox,
} from '@grafana/ui';
import { ColorValueEditor } from 'app/core/components/OptionsUI/color';
import StandardAnnotationQueryEditor from 'app/features/annotations/components/StandardAnnotationQueryEditor';
import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker';

import { NEW_ANNOTATION_NAME } from '../../scene/DashboardDataLayerSet';
import { getPanelIdForVizPanel } from '../../utils/utils';

type Props = {
  annotation: AnnotationQuery;
  editIndex: number;
  panels: VizPanel[];
  onUpdate: (annotation: AnnotationQuery, editIndex: number) => void;
  onBackToList: () => void;
  onDelete: (index: number) => void;
};

const collator = Intl.Collator();

enum AnnotationControlsDisplay {
  Hidden,
  AboveDashboard,
  InControlsMenu,
}

export const AnnotationSettingsEdit = ({ annotation, editIndex, panels, onUpdate, onBackToList, onDelete }: Props) => {
  const styles = useStyles2(getStyles);
  const showInId = useId();

  const panelFilter = useMemo(() => {
    if (!annotation.filter) {
      return PanelFilterType.AllPanels;
    }
    return annotation.filter.exclude ? PanelFilterType.ExcludePanels : PanelFilterType.IncludePanels;
  }, [annotation.filter]);

  const { value: ds } = useAsync(() => {
    return getDataSourceSrv().get(annotation.datasource);
  }, [annotation.datasource]);

  const dsi = getDataSourceSrv().getInstanceSettings(annotation.datasource);

  const AnnotationControlsDisplayOptions = useMemo(
    () => [
      {
        value: AnnotationControlsDisplay.AboveDashboard,
        label: t(
          'dashboard-scene.annotation-settings-edit.control-display-options.above-dashboard.label',
          'Above dashboard'
        ),
      },
      {
        value: AnnotationControlsDisplay.InControlsMenu,
        label: t(
          'dashboard-scene.annotation-settings-edit.control-display-options.controls-menu.label',
          'Controls menu'
        ),
        description: t(
          'dashboard-scene.annotation-settings-edit.control-display-options.controls-menu.description',
          'Can be accessed when the controls menu is open'
        ),
      },
      {
        value: AnnotationControlsDisplay.Hidden,
        label: t('dashboard-scene.annotation-settings-edit.control-display-options.hidden.label', 'Hidden'),
        description: t(
          'dashboard-scene.annotation-settings-edit.control-display-options.hidden.description',
          'Hides the toggle for turning this annotation on or off'
        ),
      },
    ],
    []
  );

  // The UI is using a single select input for where to display the annotation controls, however under the hood
  // it is computed from different fields of the annotation.
  const annotationControlsDisplayValue = useMemo(() => {
    if (annotation.hide) {
      return AnnotationControlsDisplay.Hidden;
    }

    if (annotation.placement === 'inControlsMenu') {
      return AnnotationControlsDisplay.InControlsMenu;
    }

    return AnnotationControlsDisplay.AboveDashboard;
  }, [annotation]);

  const onAnnotationControlDisplayChange = (option: ComboboxOption<AnnotationControlsDisplay>) => {
    onUpdate(
      {
        ...annotation,
        placement: option.value === AnnotationControlsDisplay.InControlsMenu ? 'inControlsMenu' : undefined,
        hide: option.value === AnnotationControlsDisplay.Hidden ? true : false,
      },
      editIndex
    );
  };

  const onNameChange = (ev: React.FocusEvent<HTMLInputElement>) => {
    onUpdate(
      {
        ...annotation,
        name: ev.currentTarget.value,
      },
      editIndex
    );
  };

  const onDataSourceChange = (ds: DataSourceInstanceSettings) => {
    const dsRef = getDataSourceRef(ds);

    if (annotation.datasource?.type !== dsRef.type) {
      onUpdate(
        {
          datasource: dsRef,
          builtIn: annotation.builtIn,
          enable: annotation.enable,
          iconColor: annotation.iconColor,
          name: annotation.name,
          hide: annotation.hide,
          filter: annotation.filter,
          mappings: annotation.mappings,
          type: annotation.type,
        },
        editIndex
      );
    } else {
      onUpdate(
        {
          ...annotation,
          datasource: dsRef,
        },
        editIndex
      );
    }
  };

  const onChange = (ev: React.FocusEvent<HTMLInputElement>) => {
    const target = ev.currentTarget;
    onUpdate(
      {
        ...annotation,
        [target.name]: target.type === 'checkbox' ? target.checked : target.value,
      },
      editIndex
    );
  };

  const onColorChange = (color?: string) => {
    onUpdate(
      {
        ...annotation,
        iconColor: color!,
      },
      editIndex
    );
  };

  const onFilterTypeChange = (v: SelectableValue<PanelFilterType>) => {
    let filter =
      v.value === PanelFilterType.AllPanels
        ? undefined
        : {
            exclude: v.value === PanelFilterType.ExcludePanels,
            ids: annotation.filter?.ids ?? [],
          };
    onUpdate({ ...annotation, filter }, editIndex);
  };

  const onAddFilterPanelID = (selections: Array<SelectableValue<number>>) => {
    if (!Array.isArray(selections)) {
      return;
    }

    const filter: AnnotationPanelFilter = {
      exclude: panelFilter === PanelFilterType.ExcludePanels,
      ids: [],
    };

    selections.forEach((selection) => selection.value && filter.ids.push(selection.value));
    onUpdate({ ...annotation, filter }, editIndex);
  };

  const onDeleteAndLeavePage = () => {
    onDelete(editIndex);
    onBackToList();
  };

  const isNewAnnotation = annotation.name === NEW_ANNOTATION_NAME;

  const sortFn = (a: SelectableValue<number>, b: SelectableValue<number>) => {
    if (a.label && b.label) {
      return collator.compare(a.label, b.label);
    }

    return -1;
  };

  const { loading, value: panelPluginMetas, error: panelPluginMetasError } = usePanelPluginMetasMap();

  const selectablePanels: Array<SelectableValue<number>> = useMemo(
    () =>
      panels
        // Filtering out rows at the moment, revisit to only include panels that support annotations
        // However the information to know if a panel supports annotations requires it to be already loaded
        // panel.plugin?.dataSupport?.annotations
        .filter((panel) => panelPluginMetas?.[panel.state.pluginId])
        .map((panel) => ({
          value: getPanelIdForVizPanel(panel),
          label: panel.state.title ?? `Panel ${getPanelIdForVizPanel(panel)}`,
          description: panel.state.description,
          imgUrl: panelPluginMetas?.[panel.state.pluginId].info.logos.small,
        }))
        .sort(sortFn) ?? [],
    [panels, panelPluginMetas]
  );

  return (
    <div>
      <FieldSet className={styles.settingsForm}>
        <Stack direction="column" gap={2}>
          {/* Name */}
          <Field noMargin label={t('dashboard-scene.annotation-settings-edit.label-name', 'Name')}>
            <Input
              data-testid={selectors.pages.Dashboard.Settings.Annotations.Settings.name}
              name="name"
              id="name"
              autoFocus={isNewAnnotation}
              value={annotation.name}
              onChange={onNameChange}
            />
          </Field>

          {/* Data source */}
          <Field
            noMargin
            label={t('dashboard-scene.annotation-settings-edit.label-data-source', 'Data source')}
            htmlFor="data-source-picker"
          >
            <DataSourcePicker annotations variables current={annotation.datasource} onChange={onDataSourceChange} />
          </Field>
          {!ds?.meta.annotations && (
            <Alert
              title={t(
                'dashboard-scene.annotation-settings-edit.title-annotation-support-source',
                'No annotation support for this data source'
              )}
              severity="error"
            >
              <Trans i18nKey="errors.dashboard-settings.annotations.datasource">
                The selected data source does not support annotations. Please select a different data source.
              </Trans>
            </Alert>
          )}

          {/* Enabled */}
          <Field
            noMargin
            label={t('dashboard-scene.annotation-settings-edit.label-enabled', 'Enabled')}
            description={t(
              'dashboard-scene.annotation-settings-edit.description-enabled-annotation-query-issued-every-dashboard',
              'When enabled the annotation query is issued every dashboard refresh'
            )}
          >
            <Checkbox
              name="enable"
              id="enable"
              value={annotation.enable}
              onChange={onChange}
              data-testid={selectors.pages.Dashboard.Settings.Annotations.NewAnnotation.enable}
            />
          </Field>

          {/* Color */}
          <Field
            noMargin
            label={t('dashboard-scene.annotation-settings-edit.label-color', 'Color')}
            description={t(
              'dashboard-scene.annotation-settings-edit.description-color-annotation-event-markers',
              'Color to use for the annotation event markers'
            )}
          >
            <Stack>
              <ColorValueEditor value={annotation?.iconColor} onChange={onColorChange} />
            </Stack>
          </Field>

          {/* Annotation controls display */}
          <Field
            noMargin
            label={t(
              'dashboard-scene.annotation-settings-edit.label-annotation-controls-display',
              'Show annotation controls in'
            )}
            data-testid={selectors.pages.Dashboard.Settings.Annotations.NewAnnotation.annotationControlsDisplay}
          >
            <Combobox
              id="annotationControlsDisplay"
              options={AnnotationControlsDisplayOptions}
              value={annotationControlsDisplayValue}
              onChange={onAnnotationControlDisplayChange}
              width="auto"
              minWidth={100}
            />
          </Field>

          {/* Show in */}
          <Field
            noMargin
            htmlFor={showInId}
            label={t('dashboard-scene.annotation-settings-edit.label-show-in', 'Show in')}
            data-testid={selectors.pages.Dashboard.Settings.Annotations.NewAnnotation.showInLabel}
          >
            <>
              <Select
                inputId={showInId}
                isLoading={loading}
                options={getPanelFilters()}
                value={panelFilter}
                onChange={onFilterTypeChange}
                data-testid={selectors.components.Annotations.annotationsTypeInput}
              />
              {panelFilter !== PanelFilterType.AllPanels && (
                <>
                  {panelPluginMetasError && (
                    <Alert
                      title={t(
                        'dashboard-scene.annotation-settings-edit.error-loading-panels',
                        'Failed to load panel plugins'
                      )}
                      severity="warning"
                    >
                      {panelPluginMetasError.message}
                    </Alert>
                  )}
                  <MultiSelect
                    options={selectablePanels}
                    value={selectablePanels.filter((panel) => annotation.filter?.ids.includes(panel.value!))}
                    onChange={onAddFilterPanelID}
                    isClearable={true}
                    placeholder={t(
                      'dashboard-scene.annotation-settings-edit.placeholder-choose-panels',
                      'Choose panels'
                    )}
                    width={100}
                    closeMenuOnSelect={false}
                    className={styles.select}
                    data-testid={selectors.components.Annotations.annotationsChoosePanelInput}
                  />
                </>
              )}
            </>
          </Field>
        </Stack>
      </FieldSet>
      <FieldSet>
        <h3 className="page-heading">
          <Trans i18nKey="dashboard-scene.annotation-settings-edit.query">Query</Trans>
        </h3>
        {ds?.annotations && dsi && (
          <StandardAnnotationQueryEditor
            datasource={ds}
            datasourceInstanceSettings={dsi}
            annotation={annotation}
            onChange={(annotation) => onUpdate(annotation, editIndex)}
          />
        )}
      </FieldSet>
      <Stack>
        {!annotation.builtIn && (
          <Button
            variant="destructive"
            onClick={onDeleteAndLeavePage}
            data-testid={selectors.pages.Dashboard.Settings.Annotations.NewAnnotation.delete}
          >
            <Trans i18nKey="dashboard-scene.annotation-settings-edit.delete">Delete</Trans>
          </Button>
        )}
        <Button
          variant="secondary"
          onClick={onBackToList}
          data-testid={selectors.pages.Dashboard.Settings.Annotations.NewAnnotation.apply}
        >
          <Trans i18nKey="dashboard-scene.annotation-settings-edit.back-to-list">Back to list</Trans>
        </Button>
      </Stack>
    </div>
  );
};

const getStyles = (theme: GrafanaTheme2) => {
  return {
    settingsForm: css({
      maxWidth: theme.spacing(60),
      marginBottom: theme.spacing(2),
    }),
    select: css({
      marginTop: '8px',
    }),
  };
};

// Synthetic type
enum PanelFilterType {
  AllPanels,
  IncludePanels,
  ExcludePanels,
}

const getPanelFilters = () => [
  {
    label: t('dashboard-scene.get-panel-filters.label.all-panels', 'All panels'),
    value: PanelFilterType.AllPanels,
    description: t(
      'dashboard-scene.get-panel-filters.description.annotation-panels-support-annotations',
      'Send the annotation data to all panels that support annotations'
    ),
  },
  {
    label: t('dashboard-scene.get-panel-filters.label.selected-panels', 'Selected panels'),
    value: PanelFilterType.IncludePanels,
    description: t(
      'dashboard-scene.get-panel-filters.description.annotations-explicitly-listed-panels',
      'Send the annotations to the explicitly listed panels'
    ),
  },
  {
    label: t('dashboard-scene.get-panel-filters.label.all-panels-except', 'All panels except'),
    value: PanelFilterType.ExcludePanels,
    description: t(
      'dashboard-scene.get-panel-filters.description.annotation-following-panels',
      'Do not send annotation data to the following panels'
    ),
  },
];
