/**
 *
 * Copyright 2023-2025 InspectorRAGet Team
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 **/

'use client';

import { isEmpty } from 'lodash';
import { useState, useMemo, useEffect, useRef, memo } from 'react';

import { WarningAlt } from '@carbon/icons-react';
import { FilterableMultiSelect, Tag } from '@carbon/react';
import { HeatmapChart } from '@carbon/charts-react';
import { ColorLegendType, ScaleTypes } from '@carbon/charts';

import { useTheme } from '@/src/theme';
import { TaskEvaluation, Model, Metric } from '@/src/types';
import {
  extractMetricDisplayValue,
  extractMetricDisplayName,
  bin,
  compareMetricAggregatedValues,
  castToNumber,
} from '@/src/utilities/metrics';
import { spearman } from '@/src/utilities/correlation';
import { areObjectsIntersecting } from '@/src/utilities/objects';

import Filters from '@/src/components/filters/Filters';
import MetricSelector from '@/src/components/selectors/MetricSelector';
import TasksTable from '@/src/views/tasks-table/TasksTable';

import '@carbon/charts-react/styles.css';
import classes from './MetricBehavior.module.scss';

// ===================================================================================
//                                TYPES
// ===================================================================================
interface Props {
  evaluationsPerMetric: { [key: string]: TaskEvaluation[] };
  models: Model[];
  metrics: Metric[];
  filters: { [key: string]: string[] };
  onTaskSelection: Function;
}

// ===================================================================================
//                               COMPUTE FUNCTIONS
// ===================================================================================
/**
 * Calculate correlation for every metric against every other metric
 * @param evaluationsPerMetric
 * @param metrics
 * @returns
 */
function calculateCorrelation(
  evaluationsPerMetric: {
    [key: string]: TaskEvaluation[];
  },
  metrics: Metric[],
) {
  // Step 1: Prepare value arrays for metrics pair
  let valueArrays: {
    [key: string]: { [key: string]: any[] };
  } = {};
  Object.keys(evaluationsPerMetric).forEach((metric1: string) => {
    valueArrays[metric1] = {};
    Object.keys(evaluationsPerMetric).forEach(
      (metric2: string) => (valueArrays[metric1][metric2] = []),
    );
  });

  // Step2: Build evaluations map per metric for faster access later
  const evaluationsMapPerMetric = new Map();
  for (const [metric, evaluations] of Object.entries(evaluationsPerMetric)) {
    evaluationsMapPerMetric.set(
      metric,
      new Map<string, TaskEvaluation>(
        evaluations.map((evaluation) => [
          `${evaluation.taskId}::${evaluation.modelId}`,
          evaluation,
        ]),
      ),
    );
  }

  // Step 3: Populate values array
  for (const metricA of Object.keys(evaluationsPerMetric)) {
    for (const metricB of Object.keys(evaluationsPerMetric)) {
      if (metricA !== metricB) {
        // an average over all workers
        evaluationsPerMetric[metricA].forEach((evaluationA) => {
          const valueA = evaluationA[`${metricA}_agg`].value;
          const evaluationB = evaluationsMapPerMetric
            .get(metricB)
            .get(`${evaluationA.taskId}::${evaluationA.modelId}`);
          if (evaluationB) {
            const valueB = evaluationB[`${metricB}_agg`].value;
            valueArrays[metricA][metricB].push({
              valueA:
                typeof valueA === 'string'
                  ? castToNumber(
                      valueA,
                      metrics.find((metric) => metric.name === metricA)?.values,
                    )
                  : valueA,
              valueB:
                typeof valueB === 'string'
                  ? castToNumber(
                      valueB,
                      metrics.find((metric) => metric.name === metricB)?.values,
                    )
                  : valueB,
            });
          }
        });
      }
    }
  }

  // Step 4: Build spearman correlation map
  const correlationMap: { [key: string]: string | number }[] = [];
  for (const [metricNameA, values] of Object.entries(valueArrays)) {
    const metricA = metrics.find((entry) => entry.name === metricNameA);
    for (const [metricNameB, pairs] of Object.entries(values)) {
      const metricB = metrics.find((entry) => entry.name === metricNameB);
      correlationMap.push({
        metricA: metricA ? extractMetricDisplayName(metricA) : metricNameA,
        metricB: metricB ? extractMetricDisplayName(metricB) : metricNameB,
        value:
          metricNameA === metricNameB
            ? 1.0
            : parseFloat(spearman(pairs)?.toFixed(2)),
      });
    }
  }
  return correlationMap;
}

/**
 * Calculate overlap matix between metric values
 * @param evaluationsPerMetric
 * @param metricA
 * @param metricB
 * @returns
 */
function calculateOverlap(
  evaluationsPerMetric: { [key: string]: TaskEvaluation[] },
  metricA: Metric,
  metricB: Metric,
) {
  // Step 1: Identify evaluations for selected model and metrics
  const modelEvaluationsForMetricA = evaluationsPerMetric[metricA.name];
  const modelEvaluationsForMetricB = evaluationsPerMetric[metricB.name];

  // Step 2: Initialize a MxN matrix (where M and N are ranges of metricA and metricB) with 0s
  const overlapMap: { [key: string]: { [key: string]: number } } = {};
  if (metricA.values && metricB.values) {
    // Initializing indereminate row
    overlapMap['Indeterminate'] = { Indeterminate: 0 };
    for (const metricValueB of metricB.values) {
      overlapMap['Indeterminate'][metricValueB.value] = 0;
    }

    for (const metricValueA of metricA.values) {
      overlapMap[metricValueA.value] = { Indeterminate: 0 };
      for (const metricValueB of metricB.values) {
        overlapMap[metricValueA.value][metricValueB.value] = 0;
      }
    }
  }

  // Step 3: Create a MxN matrix (where M and N are ranges of metricA and metricB)
  modelEvaluationsForMetricA.forEach((evaluationA) => {
    const valueA = bin(evaluationA[`${metricA.name}_agg`].value, metricA);
    const taskId = evaluationA.taskId;
    const modelId = evaluationA.modelId;
    const evaluationB = modelEvaluationsForMetricB.find(
      (entry) => entry.taskId === taskId && entry.modelId === modelId,
    );
    if (evaluationB) {
      const valueB = bin(evaluationB[`${metricB.name}_agg`].value, metricB);
      if (overlapMap.hasOwnProperty(valueA)) {
        if (overlapMap[valueA].hasOwnProperty(valueB)) {
          overlapMap[valueA][valueB] += 1;
        } else {
          overlapMap[valueA][valueB] = 1;
        }
      } else {
        overlapMap[valueA] = { [valueB]: 1 };
      }
    } else {
      console.log(`Check the data in evaluation B for taskId ${taskId}`);
    }
  });

  // Step 4: Sorted MxN matrix (where M and N are ranges of metricA and metricB)
  const sortedOverlapMap: { [key: string]: { [key: string]: number } } = {};
  // Only sort keys when metric is of 'numerical' type
  if (metricA.type === 'numerical') {
    Object.keys(overlapMap)
      .sort()
      .forEach((a) => {
        if (metricB.type === 'numerical') {
          sortedOverlapMap[a] = Object.fromEntries(
            Object.keys(overlapMap[a])
              .sort()
              .map((b) => [b, overlapMap[a][b]]),
          );
        } else {
          sortedOverlapMap[a] = overlapMap[a];
        }
      });
  } else {
    Object.entries(overlapMap).forEach(([a, b]) => {
      sortedOverlapMap[a] = Object.fromEntries(
        Object.keys(b)
          .sort()
          .map((key) => [key, b[key]]),
      );
    });
  }

  return sortedOverlapMap;
}

function sortMetricAggregatedValues(values: string[], metric: Metric) {
  return values
    .map((v) => {
      return { key: v, value: 0 };
    })
    .sort((a, b) => compareMetricAggregatedValues(a, b, metric))
    .map((entry) => entry.key);
}

// ===================================================================================
//                               RENDER FUNCTIONS
// ===================================================================================
export function prepareHeatMapData(
  metricA: Metric,
  metricB: Metric,
  heatMap: { [key: string]: { [key: string]: number } },
) {
  // step 1: Sort heatmap keys
  const sortedMetricAVals = sortMetricAggregatedValues(
    Object.keys(heatMap),
    metricA,
  );
  const metricBVals: string[] = [];
  for (const valueA in heatMap) {
    for (const valueB in heatMap[valueA]) {
      if (!metricBVals.includes(valueB)) {
        metricBVals.push(valueB);
      }
    }
  }
  const sortedMetricBVals = sortMetricAggregatedValues(metricBVals, metricB);

  // Step 2: Prepare heat map data
  const temp: any[] = [];
  let count: number = 0;
  sortedMetricAVals.forEach((metricValA) => {
    sortedMetricBVals.forEach((metricValB) => {
      if (heatMap[metricValA][metricValB]) {
        temp.push({
          metricA: extractMetricDisplayValue(metricValA, metricA.values),
          metricB: extractMetricDisplayValue(metricValB, metricB.values),
          value: heatMap[metricValA][metricValB]
            ? heatMap[metricValA][metricValB]
            : 0,
        });
        count += heatMap[metricValA][metricValB];
      }
    });
  });

  // Step 3: Normalize the counts to percentages
  if (count > 0) {
    const temp2 = temp.map((entry) => ({
      ...entry,
      value: parseFloat(((entry.value / count) * 100).toFixed(2)),
    }));

    return temp2;
  }

  return temp;
}

// ===================================================================================
//                               MAIN FUNCTION
// ===================================================================================
export default memo(function MetricBehavior({
  evaluationsPerMetric,
  models,
  metrics,
  filters,
  onTaskSelection,
}: Props) {
  // Step 1: Initialize state and necessary variables
  const [WindowWidth, setWindowWidth] = useState<number>(
    global?.window && window.innerWidth,
  );
  const [WindowHeight, setWindowHeight] = useState<number>(
    global?.window && window.innerHeight,
  );
  const [selectedModels, setSelectedModels] = useState<Model[]>(models);
  const [selectedMetricA, setSelectedMetricA] = useState<Metric | undefined>();
  const [selectedMetricB, setSelectedMetricB] = useState<Metric | undefined>();
  const [selectedFilters, setSelectedFilters] = useState<{
    [key: string]: string[];
  }>({});
  const [selectedMetricARange, setSelectedMetricARange] = useState<
    number[] | string
  >();
  const [selectedMetricBRange, setSelectedMetricBRange] = useState<
    number[] | string
  >();
  const tableRef = useRef(null);
  const chartRef = useRef(null);

  // Step 2: Run effects
  // Step 2.a: Window resizing
  useEffect(() => {
    const handleWindowResize = () => {
      setWindowWidth(window.innerWidth);
      setWindowHeight(window.innerHeight);
    };

    // Step: Add event listener
    window.addEventListener('resize', handleWindowResize);

    // Step: Cleanup to remove event listener
    return () => {
      window.removeEventListener('resize', handleWindowResize);
    };
  }, []);

  // Step 2.a: Fetch theme
  const { theme } = useTheme();

  // Step 2.b: Filter evaluations based on selected models
  const filteredEvaluationsPerMetric = useMemo(() => {
    const filtered: { [key: string]: TaskEvaluation[] } = {};
    for (const [metric, evals] of Object.entries(evaluationsPerMetric)) {
      filtered[metric] = evals.filter(
        (evaluation) =>
          selectedModels
            .map((model) => model.modelId)
            .includes(evaluation.modelId) &&
          (!isEmpty(selectedFilters)
            ? areObjectsIntersecting(selectedFilters, evaluation)
            : true),
      );
    }
    return filtered;
  }, [evaluationsPerMetric, selectedModels, selectedFilters]);

  // Step 2.c: Calculate metric-to-metric correlation for selected models
  const metricToMetricCorrelation = useMemo(() => {
    return calculateCorrelation(filteredEvaluationsPerMetric, metrics);
  }, [filteredEvaluationsPerMetric, metrics]);

  // Step 2.d: Calculate metric-to-metric overlap for selected models and metrics
  const metricToMetricOverlap = useMemo(() => {
    if (selectedMetricA && selectedMetricB) {
      return calculateOverlap(
        filteredEvaluationsPerMetric,
        selectedMetricA,
        selectedMetricB,
      );
    } else {
      return undefined;
    }
  }, [filteredEvaluationsPerMetric, selectedMetricA, selectedMetricB]);

  // Step 2.e: Reset ranges on selected metric change
  useEffect(() => {
    setSelectedMetricARange(undefined);
    setSelectedMetricBRange(undefined);
  }, [selectedMetricA, selectedMetricB]);

  // Step 2.f: Identify visible evaluations
  const visibleEvaluations: TaskEvaluation[] = useMemo(() => {
    if (selectedMetricA && selectedMetricB) {
      // Step 1: Initialize necessary variables
      const selectedModelIds = selectedModels.map((model) => model.modelId);

      const evaluationsPerTask: { [key: string]: TaskEvaluation } = {};

      // Step 2: Add eligible evaluations for 1st selected metric (A)
      filteredEvaluationsPerMetric[selectedMetricA.name].forEach(
        (evaluation) => {
          if (selectedModelIds.includes(evaluation.modelId)) {
            const UUID = `${evaluation.taskId}<::>${evaluation.modelId}`;
            if (selectedMetricARange) {
              if (typeof selectedMetricARange === 'string') {
                if (
                  extractMetricDisplayValue(
                    evaluation[`${selectedMetricA.name}_agg`].value,
                    selectedMetricA.values,
                  ) === selectedMetricARange
                ) {
                  if (evaluationsPerTask.hasOwnProperty(UUID)) {
                    evaluationsPerTask[UUID] = {
                      ...evaluationsPerTask[UUID],
                      [`${selectedMetricA.name}`]:
                        evaluation[`${selectedMetricA.name}`],
                      [`${selectedMetricA.name}_agg`]:
                        evaluation[`${selectedMetricA.name}_agg`],
                    };
                  } else {
                    evaluationsPerTask[UUID] = evaluation;
                  }
                }
              } else if (Array.isArray(selectedMetricARange)) {
                if (
                  evaluation[`${selectedMetricA.name}_agg`].value >=
                    selectedMetricARange[0] &&
                  evaluation[`${selectedMetricA.name}_agg`].value <=
                    selectedMetricARange[1]
                ) {
                  if (evaluationsPerTask.hasOwnProperty(UUID)) {
                    evaluationsPerTask[UUID] = {
                      ...evaluationsPerTask[UUID],
                      [`${selectedMetricA.name}`]:
                        evaluation[`${selectedMetricA.name}`],
                      [`${selectedMetricA.name}_agg`]:
                        evaluation[`${selectedMetricA.name}_agg`],
                    };
                  } else {
                    evaluationsPerTask[UUID] = evaluation;
                  }
                }
              }
            } else {
              if (evaluationsPerTask.hasOwnProperty(UUID)) {
                evaluationsPerTask[UUID] = {
                  ...evaluationsPerTask[UUID],
                  [`${selectedMetricA.name}`]:
                    evaluation[`${selectedMetricA.name}`],
                  [`${selectedMetricA.name}_agg`]:
                    evaluation[`${selectedMetricA.name}_agg`],
                };
              } else {
                evaluationsPerTask[UUID] = evaluation;
              }
            }
          }
        },
      );

      // Step 2: Add eligible evaluations for 2nd selected metric (B)
      filteredEvaluationsPerMetric[selectedMetricB.name].forEach(
        (evaluation) => {
          if (selectedModelIds.includes(evaluation.modelId)) {
            const UUID = `${evaluation.taskId}<::>${evaluation.modelId}`;
            if (selectedMetricBRange) {
              if (typeof selectedMetricBRange === 'string') {
                if (
                  extractMetricDisplayValue(
                    evaluation[`${selectedMetricB.name}_agg`].value,
                    selectedMetricB.values,
                  ) === selectedMetricBRange
                ) {
                  if (evaluationsPerTask.hasOwnProperty(UUID)) {
                    evaluationsPerTask[UUID] = {
                      ...evaluationsPerTask[UUID],
                      [`${selectedMetricB.name}`]:
                        evaluation[`${selectedMetricB.name}`],
                      [`${selectedMetricB.name}_agg`]:
                        evaluation[`${selectedMetricB.name}_agg`],
                    };
                  } else {
                    evaluationsPerTask[UUID] = evaluation;
                  }
                }
              } else if (Array.isArray(selectedMetricBRange)) {
                if (
                  evaluation[`${selectedMetricB.name}_agg`].value >=
                    selectedMetricBRange[0] &&
                  evaluation[`${selectedMetricB.name}_agg`].value <=
                    selectedMetricBRange[1]
                ) {
                  if (evaluationsPerTask.hasOwnProperty(UUID)) {
                    evaluationsPerTask[UUID] = {
                      ...evaluationsPerTask[UUID],
                      [`${selectedMetricB.name}`]:
                        evaluation[`${selectedMetricB.name}`],
                      [`${selectedMetricB.name}_agg`]:
                        evaluation[`${selectedMetricB.name}_agg`],
                    };
                  } else {
                    evaluationsPerTask[UUID] = evaluation;
                  }
                }
              }
            } else {
              if (evaluationsPerTask.hasOwnProperty(UUID)) {
                evaluationsPerTask[UUID] = {
                  ...evaluationsPerTask[UUID],
                  [`${selectedMetricB.name}`]:
                    evaluation[`${selectedMetricB.name}`],
                  [`${selectedMetricB.name}_agg`]:
                    evaluation[`${selectedMetricB.name}_agg`],
                };
              } else {
                evaluationsPerTask[UUID] = evaluation;
              }
            }
          }
        },
      );

      // Step 3: Only retains evaluation tasks where both metric values are available
      return Object.values(evaluationsPerTask).filter(
        (evaluation) =>
          evaluation.hasOwnProperty(`${selectedMetricA.name}`) &&
          evaluation.hasOwnProperty(`${selectedMetricA.name}_agg`) &&
          evaluation.hasOwnProperty(`${selectedMetricB.name}`) &&
          evaluation.hasOwnProperty(`${selectedMetricB.name}_agg`),
      );
    }
    return [];
  }, [
    filteredEvaluationsPerMetric,
    selectedModels,
    selectedMetricA,
    selectedMetricARange,
    selectedMetricB,
    selectedMetricBRange,
  ]);

  // Step 2.g: Add chart event
  useEffect(() => {
    // Step 2.g.i: Update function
    function onClick(event) {
      // Set range for 1st selected metric (A)
      if (selectedMetricA?.type === 'numerical') {
        if (event.detail.datum['metricA'].substring(1).includes('-')) {
          const match = event.detail.datum['metricA'].match(
            /^(-?\d*\.?\d*)-(-?\d*\.?\d*)$/,
          );
          setSelectedMetricARange([parseFloat(match[1]), parseFloat(match[2])]);
        } else {
          setSelectedMetricARange([
            parseFloat(event.detail.datum['metricA']),
            parseFloat(event.detail.datum['metricA']),
          ]);
        }
      } else {
        setSelectedMetricARange(event.detail.datum['metricA']);
      }

      // Set range for 2nd selected metric (B)
      if (selectedMetricB?.type === 'numerical') {
        if (event.detail.datum['metricB'].substring(1).includes('-')) {
          const match = event.detail.datum['metricB'].match(
            /^(-?\d*\.?\d*)-(-?\d*\.?\d*)$/,
          );
          setSelectedMetricBRange([parseFloat(match[1]), parseFloat(match[2])]);
        } else {
          setSelectedMetricARange([
            parseFloat(event.detail.datum['metricB']),
            parseFloat(event.detail.datum['metricB']),
          ]);
        }
      } else {
        setSelectedMetricBRange(event.detail.datum['metricB']);
      }
    }

    // Step 2.g.ii: Local copy of reference
    let ref = null;

    // Step 2.g.iii: Update reference and add event
    if (chartRef && chartRef.current) {
      ref = chartRef.current;

      //@ts-ignore
      ref.chart.services.events.addEventListener('heatmap-click', onClick);
    }

    // Step 2.g.iv: Cleanup function
    return () => {
      if (ref) {
        //@ts-ignore
        ref.chart.services.events.removeEventListener('heatmap-click', onClick);
      }
    };
  }, [chartRef, selectedMetricA, selectedMetricB, metricToMetricOverlap]);

  // Step 2.h: Scroll task table into view
  useEffect(() => {
    if (
      selectedMetricARange &&
      selectedMetricBRange &&
      tableRef &&
      tableRef.current
    ) {
      //@ts-ignore
      tableRef.current.scrollIntoView({
        behavior: 'smooth',
        block: 'end',
        inline: 'center',
      });
    }
  }, [tableRef, selectedMetricARange, selectedMetricBRange]);

  // Step 3: Render
  return (
    <div className={classes.page}>
      <div className={classes.selectors}>
        <div className={classes.modelSelector}>
          <FilterableMultiSelect
            id={'model-selector'}
            titleText="Choose models"
            items={models}
            initialSelectedItems={selectedModels}
            itemToString={(item) => item.name}
            onChange={(event) => {
              setSelectedModels(event.selectedItems);
            }}
            invalid={selectedModels.length === 0}
            invalidText={'You must select a model to review.'}
          ></FilterableMultiSelect>
          <div>
            {selectedModels.map((model) => {
              return (
                <Tag type={'cool-gray'} key={'model-' + model.modelId}>
                  {model.name}
                </Tag>
              );
            })}
          </div>
        </div>
        <div id={'metricA-selector'} className={classes.metricSelector}>
          <MetricSelector
            disabled={Array.isArray(metrics) && !metrics.length}
            metrics={metrics}
            onSelect={(metric: Metric | undefined) => {
              setSelectedMetricA(metric);
            }}
            warn={Array.isArray(metrics) && !metrics.length && !selectedMetricA}
            warnText={'You must select a first metric to proceed.'}
            defaultValue={'all'}
            disabledMetrics={selectedMetricB ? [selectedMetricB] : []}
          />
        </div>
        <div id={'metricB-selector'} className={classes.metricSelector}>
          <MetricSelector
            disabled={Array.isArray(metrics) && !metrics.length}
            metrics={metrics}
            onSelect={(metric: Metric | undefined) => {
              setSelectedMetricB(metric);
            }}
            warn={
              (Array.isArray(metrics) && !metrics.length) ||
              (typeof selectedMetricA !== 'undefined' && !selectedMetricB)
            }
            warnText={'You must select a second metric to proceed.'}
            defaultValue={'all'}
            disabledMetrics={selectedMetricA ? [selectedMetricA] : []}
          />
        </div>
      </div>

      {!isEmpty(filters) ? (
        <Filters
          keyPrefix="MetricBehavior"
          filters={filters}
          selectedFilters={selectedFilters}
          setSelectedFilters={setSelectedFilters}
        />
      ) : null}

      {!selectedMetricA && !selectedMetricB ? (
        !metricToMetricCorrelation ? (
          <div className={classes.tasksContainerWarning}>
            <WarningAlt
              height={'24px'}
              width={'24px'}
              className={classes.tasksContainerWarningIcon}
            />
            <span className={classes.tasksContainerWarningText}>
              Press calculate to compute correlation across all metrics.
            </span>
          </div>
        ) : (
          <div className={classes.row}>
            <h4 className={classes.graphTitle}>
              <strong>Spearman correlation</strong>
              <span>
                {`(${Object.values(filteredEvaluationsPerMetric)[0].length ? Object.values(filteredEvaluationsPerMetric)[0].length / (selectedModels ? selectedModels.length : 1) : 0}/${Object.values(evaluationsPerMetric)[0].length / models.length})`}
              </span>
            </h4>
            <HeatmapChart
              data={metricToMetricCorrelation}
              options={{
                // @ts-ignore
                axes: {
                  bottom: {
                    title: 'Metrics',
                    mapsTo: 'metricA',
                    scaleType: ScaleTypes.LABELS,
                  },
                  left: {
                    title: 'Metrics',
                    mapsTo: 'metricB',
                    scaleType: ScaleTypes.LABELS,
                  },
                },
                heatmap: {
                  colorLegend: {
                    type: ColorLegendType.QUANTIZE,
                  },
                },
                width: Math.round(WindowWidth * 0.6) + 'px',
                height: Math.round(WindowHeight * 0.6) + 'px',
                toolbar: {
                  enabled: false,
                },
                theme: theme,
              }}
            ></HeatmapChart>
          </div>
        )
      ) : !selectedMetricA || !selectedMetricB ? (
        <div className={classes.tasksContainerWarning}>
          <WarningAlt
            height={'24px'}
            width={'24px'}
            className={classes.tasksContainerWarningIcon}
          />
          <span className={classes.tasksContainerWarningText}>
            You must select both metrics in order to see head-to-head %
            instances comparison.
          </span>
        </div>
      ) : selectedMetricA && selectedMetricB ? (
        !metricToMetricOverlap ? (
          <div className={classes.tasksContainerWarning}>
            <WarningAlt
              height={'24px'}
              width={'24px'}
              className={classes.tasksContainerWarningIcon}
            />
            <span className={classes.tasksContainerWarningText}>
              Press calculate to compute overlap between{' '}
              {extractMetricDisplayName(selectedMetricA)} and{' '}
              {extractMetricDisplayName(selectedMetricB)}.
            </span>
          </div>
        ) : (
          <div className={classes.row}>
            <h4 className={classes.graphTitle}>
              <strong>
                % instances with same scores (
                {extractMetricDisplayName(selectedMetricA)} vs.
                {extractMetricDisplayName(selectedMetricB)})
              </strong>
              <span>
                {`(${Object.values(filteredEvaluationsPerMetric)[0].length ? Object.values(filteredEvaluationsPerMetric)[0].length / (selectedModels ? selectedModels.length : 1) : 0}/${Object.values(evaluationsPerMetric)[0].length / models.length})`}
              </span>
            </h4>
            <HeatmapChart
              ref={chartRef}
              data={prepareHeatMapData(
                selectedMetricA,
                selectedMetricB,
                metricToMetricOverlap,
              )}
              options={{
                // @ts-ignore
                axes: {
                  bottom: {
                    title: extractMetricDisplayName(selectedMetricA),
                    mapsTo: 'metricA',
                    scaleType: ScaleTypes.LABELS,
                  },
                  left: {
                    title: extractMetricDisplayName(selectedMetricB),
                    mapsTo: 'metricB',
                    scaleType: ScaleTypes.LABELS,
                  },
                },
                heatmap: {
                  colorLegend: {
                    type: ColorLegendType.QUANTIZE,
                  },
                },
                experimental: false,
                width: `${Math.round(WindowWidth * 0.6)}px`,
                height: `${Math.round(WindowHeight * 0.6)}px`,
                toolbar: {
                  enabled: false,
                },
                theme: theme,
              }}
            ></HeatmapChart>
          </div>
        )
      ) : null}

      {selectedMetricA && selectedMetricB && !isEmpty(visibleEvaluations) ? (
        <div ref={tableRef} className={classes.tasksTableContainer}>
          <h4>
            Tasks<sup>*</sup>
          </h4>

          <TasksTable
            metrics={[selectedMetricA, selectedMetricB]}
            evaluations={visibleEvaluations}
            models={selectedModels}
            filters={filters}
            onClick={onTaskSelection}
          />
          <span className={classes.tasksTableWarning}>
            <sup>*</sup> Only tasks with aggregate scores in selected range are
            shown in the above table.
          </span>
        </div>
      ) : null}
    </div>
  );
});
