// Copyright (C) 2026 The Android Open Source Project
//
// 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.

import m from 'mithril';
import type {EChartsCoreOption} from 'echarts/core';
import {formatNumber} from './chart_utils';
import {
  type ThemeColors,
  EChartView,
  type EChartEventHandler,
  type EChartClickParams,
} from './echart_view';
import {buildTooltipOption} from './chart_option_builder';

export interface SankeyNode {
  readonly name: string;
  readonly color?: string;
  readonly depth?: number;
}

export interface SankeyLink {
  readonly source: string;
  readonly target: string;
  readonly value: number;
}

export interface SankeyData {
  readonly nodes: readonly SankeyNode[];
  readonly links: readonly SankeyLink[];
}

export interface SankeyChartAttrs {
  readonly data: SankeyData | undefined;
  readonly height?: number;
  readonly fillParent?: boolean;
  readonly className?: string;
  readonly formatValue?: (value: number) => string;
  readonly onNodeClick?: (node: SankeyNode) => void;
}

export class Sankey implements m.ClassComponent<SankeyChartAttrs> {
  view({attrs}: m.Vnode<SankeyChartAttrs>) {
    const {data, height, fillParent, className} = attrs;

    const isEmpty =
      data !== undefined &&
      (data.nodes.length === 0 || data.links.length === 0);
    const option =
      data !== undefined && !isEmpty
        ? buildSankeyOption(attrs, data)
        : undefined;

    return m(EChartView, {
      option,
      height,
      fillParent,
      className,
      empty: isEmpty,
      eventHandlers: buildSankeyEventHandlers(attrs, data),
      resolveOption: applySankeyTheme,
    });
  }
}

function buildSankeyOption(
  attrs: SankeyChartAttrs,
  data: SankeyData,
): EChartsCoreOption {
  const {formatValue = (v: number) => formatNumber(v)} = attrs;

  // Compute total outflow per source node for percentage in tooltips.
  const outflow = new Map<string, number>();
  for (const link of data.links) {
    outflow.set(link.source, (outflow.get(link.source) ?? 0) + link.value);
  }

  return {
    animation: false,
    tooltip: buildTooltipOption({
      trigger: 'item' as const,
      formatter: (params: {
        dataType?: string;
        name?: string;
        value?: number;
        data?: {source?: string; target?: string; value?: number};
      }) => {
        if (params.dataType === 'edge') {
          const d = params.data;
          if (d === undefined) return '';
          const total = outflow.get(d.source ?? '') ?? 0;
          const pct =
            total > 0 ? (((d.value ?? 0) / total) * 100).toFixed(1) : '0';
          return [
            `${d.source} → ${d.target}`,
            `${formatValue(d.value ?? 0)}`,
            `${pct}% of ${d.source}`,
          ].join('<br>');
        }
        // Node tooltip.
        const name = params.name ?? '';
        const value = params.value ?? 0;
        return `${name}<br>${formatValue(value)}`;
      },
    }),
    series: [
      {
        type: 'sankey',
        orient: 'horizontal',
        data: data.nodes.map((n) => ({
          name: n.name,
          ...(n.depth !== undefined ? {depth: n.depth} : {}),
        })),
        links: data.links.map((l) => ({
          source: l.source,
          target: l.target,
          value: l.value,
        })),
        label: {
          show: true,
          fontSize: 11,
        },
        lineStyle: {
          color: 'gradient',
          opacity: 0.4,
        },
        emphasis: {
          focus: 'adjacency',
        },
        draggable: false,
        nodeWidth: 24,
        nodeGap: 16,
        layoutIterations: 0,
        left: 20,
        right: 160,
        top: 10,
        bottom: 10,
      },
    ],
  };
}

interface SankeySeries {
  type?: string;
  label?: {color?: unknown};
}

function applySankeyTheme(
  option: EChartsCoreOption,
  colors: ThemeColors,
): EChartsCoreOption {
  const series = (option as {series?: unknown[]}).series;
  if (!Array.isArray(series) || series.length === 0) return option;

  const sankey = series[0] as SankeySeries;
  if (sankey.type !== 'sankey') return option;

  if (sankey.label) {
    sankey.label.color = colors.textColor;
  }

  return option;
}

function buildSankeyEventHandlers(
  attrs: SankeyChartAttrs,
  data: SankeyData | undefined,
): ReadonlyArray<EChartEventHandler> {
  if (!attrs.onNodeClick || data === undefined) return [];

  const nodeMap = new Map<string, SankeyNode>();
  for (const node of data.nodes) {
    nodeMap.set(node.name, node);
  }

  const onNodeClick = attrs.onNodeClick;

  return [
    {
      eventName: 'click',
      handler: (params) => {
        const p = params as EChartClickParams;
        if (p.name !== undefined) {
          const node = nodeMap.get(p.name);
          if (node !== undefined) {
            onNodeClick(node);
          }
        }
      },
    },
  ];
}
