import { css } from '@emotion/css';
import { camelCase, groupBy } from 'lodash';
import { memo, useCallback, useEffect, useMemo, useState } from 'react';

import {
  DataFrameType,
  type DataSourceApi,
  type GrafanaTheme2,
  hasLogsLabelTypesSupport,
  store,
  type TimeRange,
} from '@grafana/data';
import { t, Trans } from '@grafana/i18n';
import { getDataSourceSrv, reportInteraction } from '@grafana/runtime';
import { Box, ControlledCollapse, useStyles2 } from '@grafana/ui';

import { getLabelTypeFromRow } from '../../utils';
import { useAttributesExtensionLinks } from '../LogDetails';
import { createLogLineLinks } from '../logParser';

import { LogLineDetailsDisplayedFields } from './LogLineDetailsDisplayedFields';
import { type LabelWithLinks, LogLineDetailsFields, LogLineDetailsLabelFields } from './LogLineDetailsFields';
import { LogLineDetailsLinks } from './LogLineDetailsLinks';
import { LogLineDetailsLog } from './LogLineDetailsLog';
import { LogLineDetailsTrace } from './LogLineDetailsTrace';
import { useLogListContext } from './LogListContext';
import { reportInteractionOnce } from './analytics';
import { getTempoTraceFromLinks } from './links';
import { type LogListModel } from './processing';

interface LogLineDetailsComponentProps {
  log: LogListModel;
  logs: LogListModel[];
  search?: string;
  timeRange: TimeRange;
  timeZone: string;
}

export const LogLineDetailsComponent = memo(
  ({ log, logs, search = '', timeRange, timeZone }: LogLineDetailsComponentProps) => {
    const { displayedFields, noInteractions, logOptionsStorageKey, setDisplayedFields, syntaxHighlighting } =
      useLogListContext();

    const [ds, setDs] = useState<DataSourceApi | null | undefined>(undefined);
    const styles = useStyles2(getStyles);

    const extensionLinks = useAttributesExtensionLinks(log, timeRange);

    const fieldsWithLinks = useMemo(() => {
      const fieldsWithLinks = log.fields.filter((f) => f.links?.length);
      const displayedFieldsWithLinks = fieldsWithLinks.filter((f) => f.fieldIndex !== log.entryFieldIndex).sort();
      const hiddenFieldsWithLinks = fieldsWithLinks.filter((f) => f.fieldIndex === log.entryFieldIndex).sort();
      const fieldsWithLinksFromVariableMap = createLogLineLinks(hiddenFieldsWithLinks);
      return {
        links: displayedFieldsWithLinks,
        linksFromVariableMap: fieldsWithLinksFromVariableMap,
      };
    }, [log.entryFieldIndex, log.fields]);

    const fieldsWithoutLinks = useMemo(
      () =>
        log.dataFrame.meta?.type === DataFrameType.LogLines
          ? // for LogLines frames (dataplane) we don't want to show any additional fields besides already extracted labels and links
            []
          : // for other frames, do not show the log message unless there is a link attached
            log.fields.filter((f) => f.links?.length === 0 && f.fieldIndex !== log.entryFieldIndex).sort(),
      [log.dataFrame.meta?.type, log.entryFieldIndex, log.fields]
    );

    const labelsWithLinks: LabelWithLinks[] = useMemo(
      () =>
        Object.keys(log.labels)
          .sort()
          .map((label) => ({
            key: label,
            value: log.labels[label],
            links: extensionLinks?.[label],
          })),
      [extensionLinks, log.labels]
    );

    const trace = useMemo(() => getTempoTraceFromLinks(fieldsWithLinks.links), [fieldsWithLinks.links]);

    const groupedLabels = useMemo(() => {
      if (!ds) {
        return labelsWithLinks.length > 0
          ? {
              '': labelsWithLinks,
            }
          : {};
      }
      return groupBy(labelsWithLinks, (label) => {
        if (hasLogsLabelTypesSupport(ds)) {
          return ds.getLabelDisplayTypeFromFrame(label.key, log.dataFrame, log.rowIndex) ?? '';
        }
        return getLabelTypeFromRow(label.key, log, true) ?? '';
      });
    }, [ds, labelsWithLinks, log]);

    const labelGroups = useMemo(() => Object.keys(groupedLabels), [groupedLabels]);

    const logLineOpen = logOptionsStorageKey
      ? store.getBool(`${logOptionsStorageKey}.log-details.logLineOpen`, false)
      : false;
    const linksOpen = logOptionsStorageKey
      ? store.getBool(`${logOptionsStorageKey}.log-details.linksOpen`, true)
      : true;
    const fieldsOpen = logOptionsStorageKey
      ? store.getBool(`${logOptionsStorageKey}.log-details.fieldsOpen`, true)
      : true;
    const displayedFieldsOpen = logOptionsStorageKey
      ? store.getBool(`${logOptionsStorageKey}.log-details.displayedFieldsOpen`, false)
      : false;
    const traceOpen = logOptionsStorageKey
      ? store.getBool(`${logOptionsStorageKey}.log-details.traceOpen`, false)
      : false;

    const handleToggle = useCallback(
      (option: string, isOpen: boolean) => {
        store.set(`${logOptionsStorageKey}.log-details.${option}`, isOpen);
        if (!noInteractions) {
          reportInteraction('logs_log_line_details_section_toggled', {
            section: option.replace('Open', ''),
            state: isOpen ? 'open' : 'closed',
          });
        }
      },
      [logOptionsStorageKey, noInteractions]
    );

    const noDetails =
      !fieldsWithLinks.links.length &&
      !fieldsWithLinks.linksFromVariableMap.length &&
      !labelGroups.length &&
      !fieldsWithoutLinks.length;

    const allLinks = useMemo(
      () => [...fieldsWithLinks.links, ...fieldsWithLinks.linksFromVariableMap],
      [fieldsWithLinks.links, fieldsWithLinks.linksFromVariableMap]
    );

    useEffect(() => {
      if (noInteractions) {
        return;
      }
      reportInteractionOnce('logs_log_line_details_fields_displayed', {
        links: allLinks.length,
        trace: trace !== undefined,
        fields: fieldsWithoutLinks.length,
        labels: labelsWithLinks.length,
        labelGroups: labelGroups.join(', '),
      });
      // Once
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);

    useEffect(() => {
      getDataSourceSrv()
        .get(log.datasourceUid)
        .then(setDs)
        .catch(() => setDs(null));
    }, [log.datasourceUid]);

    // Wait for ds to be resolved to DataSourceApi or null on error
    if (ds === undefined) {
      return null;
    }

    return (
      <div className={styles.componentWrapper}>
        <ControlledCollapse
          className={styles.collapsable}
          label={t('logs.log-line-details.log-line-section', 'Log line')}
          isOpen={logLineOpen}
          onToggle={(isOpen: boolean) => handleToggle('logLineOpen', isOpen)}
        >
          <LogLineDetailsLog log={log} syntaxHighlighting={syntaxHighlighting ?? true} />
        </ControlledCollapse>
        {displayedFields.length > 0 && setDisplayedFields && (
          <ControlledCollapse
            label={t('logs.log-line-details.displayed-fields-section', 'Organize displayed fields')}
            isOpen={displayedFieldsOpen}
            onToggle={(isOpen: boolean) => handleToggle('displayedFieldsOpen', isOpen)}
          >
            <LogLineDetailsDisplayedFields />
          </ControlledCollapse>
        )}
        {allLinks.length > 0 && (
          <ControlledCollapse
            className={styles.collapsable}
            label={t('logs.log-line-details.links-section', 'Links')}
            isOpen={linksOpen}
            onToggle={(isOpen: boolean) => handleToggle('linksOpen', isOpen)}
          >
            <LogLineDetailsLinks log={log} logs={logs} fields={allLinks} search={search} />
          </ControlledCollapse>
        )}
        {trace && (
          <ControlledCollapse
            label={t('logs.log-line-details.trace-section', 'Trace')}
            isOpen={traceOpen}
            onToggle={(isOpen: boolean) => handleToggle('traceOpen', isOpen)}
          >
            <LogLineDetailsTrace timeRange={timeRange} timeZone={timeZone} traceRef={trace} />
          </ControlledCollapse>
        )}
        {labelGroups.map((group) =>
          group === '' ? (
            <ControlledCollapse
              className={styles.collapsable}
              key={'fields'}
              label={t('logs.log-line-details.fields-section', 'Fields')}
              isOpen={fieldsOpen}
              onToggle={(isOpen: boolean) => handleToggle('fieldsOpen', isOpen)}
            >
              <LogLineDetailsLabelFields log={log} logs={logs} fields={groupedLabels[group]} search={search} />
              <LogLineDetailsFields log={log} logs={logs} fields={fieldsWithoutLinks} search={search} />
            </ControlledCollapse>
          ) : (
            <ControlledCollapse
              className={styles.collapsable}
              key={group}
              label={group}
              isOpen={store.getBool(`${logOptionsStorageKey}.log-details.${groupOptionName(group)}`, true)}
              onToggle={(isOpen: boolean) => handleToggle(groupOptionName(group), isOpen)}
            >
              <LogLineDetailsLabelFields log={log} logs={logs} fields={groupedLabels[group]} search={search} />
            </ControlledCollapse>
          )
        )}
        {!labelGroups.length && fieldsWithoutLinks.length > 0 && (
          <ControlledCollapse
            className={styles.collapsable}
            key={'fields'}
            label={t('logs.log-line-details.fields-section', 'Fields')}
            isOpen={fieldsOpen}
            onToggle={(isOpen: boolean) => handleToggle('fieldsOpen', isOpen)}
          >
            <LogLineDetailsFields log={log} logs={logs} fields={fieldsWithoutLinks} search={search} />
          </ControlledCollapse>
        )}
        {noDetails && (
          <Box marginTop={1} paddingLeft={0.5}>
            <Trans i18nKey="logs.log-line-details.no-details">No fields to display.</Trans>
          </Box>
        )}
      </div>
    );
  }
);
LogLineDetailsComponent.displayName = 'LogLineDetailsComponent';

function groupOptionName(group: string) {
  return `${camelCase(group)}Open`;
}

const getStyles = (theme: GrafanaTheme2) => ({
  collapsable: css({
    '&:last-of-type': {
      marginBottom: 0,
    },
  }),
  componentWrapper: css({
    padding: theme.spacing(0, 1, 1, 1),
  }),
});
