import { css, cx } from '@emotion/css';
import { useCallback } from 'react';

import { type GrafanaTheme2 } from '@grafana/data';
import { t, Trans } from '@grafana/i18n';
import {
  type AdHocFiltersVariable,
  GroupByVariable,
  type VariableValueOption,
  type VariableValueSingle,
} from '@grafana/scenes';
import { Button, Checkbox, ClickOutsideWrapper, FilterInput, Spinner, Stack, useStyles2 } from '@grafana/ui';

interface Props {
  groupByVariable: GroupByVariable | AdHocFiltersVariable;
  onCancel: () => void;
  isLoading: boolean;
  searchValue: string;
  setSearchValue: (value: string) => void;
  options: VariableValueOption[];
  values: VariableValueSingle[];
  onValuesChange: (value: VariableValueSingle[]) => void;
}

export function PanelGroupByActionPopover({
  groupByVariable,
  onCancel,
  isLoading,
  searchValue,
  setSearchValue,
  options,
  values,
  onValuesChange,
}: Props) {
  const styles = useStyles2(getStyles);

  const onCheckedChanged = useCallback(
    (option: VariableValueOption) => (event: React.FormEvent<HTMLInputElement>) => {
      const newValues = event.currentTarget.checked
        ? values.concat(option.value)
        : values.filter((c) => c !== option.value);

      onValuesChange(newValues);
    },
    [onValuesChange, values]
  );

  const isChecked = (option: VariableValueOption) => {
    return values.includes(option.value);
  };

  const handleApply = useCallback(() => {
    if (groupByVariable instanceof GroupByVariable) {
      groupByVariable.changeValueTo(values, values.map(String), true);
    } else {
      const selectedKeys = new Set(values.map(String));
      const originFilters = groupByVariable.state.originFilters ?? [];
      const originGroupByKeys = new Set<string>();

      const updatedOriginFilters = originFilters.map((f) => {
        if (f.operator !== 'groupBy') {
          return f;
        }
        originGroupByKeys.add(f.key);
        const shouldBeActive = selectedKeys.has(f.key);
        return { ...f, dismissedGroupBy: !shouldBeActive };
      });

      const nonGroupByFilters = groupByVariable.state.filters.filter((f) => f.operator !== 'groupBy');
      const newUserGroupBys = values
        .filter((v) => !originGroupByKeys.has(String(v)))
        .map((v) => ({ key: String(v), operator: 'groupBy', value: '' }));

      const allOriginsRestored = updatedOriginFilters
        .filter((f) => f.operator === 'groupBy' && f.origin)
        .every((f) => !f.dismissedGroupBy);
      const isBackToDefaults = allOriginsRestored && newUserGroupBys.length === 0;

      const finalOriginFilters = updatedOriginFilters.map((f) => {
        if (f.operator !== 'groupBy' || !f.origin) {
          return f;
        }
        return { ...f, restorable: !isBackToDefaults };
      });

      groupByVariable.setState({
        originFilters: finalOriginFilters,
        filters: [...nonGroupByFilters, ...newUserGroupBys],
      });
    }
    onCancel();
  }, [groupByVariable, onCancel, values]);

  const groupByHasCurrentValues =
    groupByVariable instanceof GroupByVariable
      ? Array.isArray(groupByVariable.state.value)
        ? groupByVariable.state.value.length > 0
        : Boolean(groupByVariable.state.value)
      : groupByVariable.state.filters.some((f) => f.operator === 'groupBy') ||
        (groupByVariable.state.originFilters ?? []).some((f) => f.operator === 'groupBy' && !f.dismissedGroupBy);

  return (
    <ClickOutsideWrapper onClick={onCancel} useCapture={true}>
      {/* This is just blocking click events from bubbeling and should not have a keyboard interaction. */}
      {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */}
      <div className={styles.menuContainer} onClick={(ev) => ev.stopPropagation()}>
        <Stack direction="column">
          <div className={styles.searchContainer}>
            <FilterInput
              placeholder={t('panel-group-by.search-placeholder', 'Search')}
              value={searchValue}
              onChange={setSearchValue}
              escapeRegex={false}
            />
          </div>

          <div className={styles.listContainer}>
            {isLoading ? (
              <div className={styles.emptyMessage}>
                <Spinner size="sm" inline />
                &nbsp;
                <Trans i18nKey="panel-group-by.loading">Loading options</Trans>
              </div>
            ) : options.length === 0 ? (
              <div className={styles.emptyMessage}>
                <Trans i18nKey="panel-group-by.no-options">No options found</Trans>
              </div>
            ) : (
              options.map((option) => {
                return (
                  <div key={String(option.value)} className={cx(styles.option)}>
                    <Checkbox value={isChecked(option)} label={option.label} onChange={onCheckedChanged(option)} />
                  </div>
                );
              })
            )}
          </div>

          <Stack justifyContent="end" direction="row-reverse">
            <Button size="sm" onClick={handleApply} disabled={!values.length && !groupByHasCurrentValues}>
              <Trans i18nKey="grafana-ui.table.filter-popup-apply">Ok</Trans>
            </Button>
            <Button size="sm" variant="secondary" onClick={onCancel}>
              <Trans i18nKey="grafana-ui.table.filter-popup-cancel">Cancel</Trans>
            </Button>
          </Stack>
        </Stack>
      </div>
    </ClickOutsideWrapper>
  );
}

const getStyles = (theme: GrafanaTheme2) => ({
  menuContainer: css({
    display: 'flex',
    flexDirection: 'column',
    background: theme.colors.background.elevated,
    border: `1px solid ${theme.colors.border.weak}`,
    borderRadius: theme.shape.radius.default,
    boxShadow: theme.shadows.z3,
    padding: theme.spacing(2),
  }),
  searchContainer: css({
    width: '100%',
    paddingBottom: theme.spacing(1),
    borderBottom: `1px solid ${theme.colors.border.weak}`,
  }),
  listContainer: css({
    flex: 1,
    overflow: 'auto',
    minHeight: '100px',
    maxHeight: '300px',
    padding: theme.spacing(0.5),
    borderBottom: `1px solid ${theme.colors.border.weak}`,
  }),
  option: css({
    display: 'flex',
    alignItems: 'center',
    gap: theme.spacing(1),
    padding: theme.spacing(1),
    cursor: 'pointer',
    borderRadius: theme.shape.radius.default,
    '&:hover': {
      background: theme.colors.background.secondary,
    },
    '&:focus-visible': {
      outline: `2px solid ${theme.colors.primary.border}`,
      outlineOffset: '-2px',
    },
  }),
  emptyMessage: css({
    padding: theme.spacing(2),
    textAlign: 'center',
    color: theme.colors.text.secondary,
  }),
});
