import { Group } from '@progress/kendo-drawing';
import { Border, FocusHighlight, Margin, Padding } from './field-types';

/**
 * Represents the highlight options of the Sankey links.
 */
export interface SankeyLinkHighlight {
    /**
     * The opacity of the link.
     *
     * @default 0.8
     */
    opacity?: number;
    /**
     * The opacity of the inactive link.
     *
     * @default 0.2
     */
    inactiveOpacity?: number;
}

/**
 * Represents the focus highlight options of the Sankey node and link elements.
 *
 * @default `{ border: { width: 2, color: '#000', opacity: 1 } }`
 */
export interface SankeyFocusHighlight extends FocusHighlight { }

/**
 * Represents the options of the Sankey node label.
 */
export interface SankeyNodeLabel {
    /**
     * The aria-label template of the Sankey node label.
     *
     * @default '({ node }) => node.label.text'
     */
    ariaTemplate?: ((data: { node: SankeyNodeDataItem }) => string);
}

/**
 * Represents the options of the Sankey link label.
 */
export interface SankeyLinkLabel {
    /**
     * The aria-label template of the Sankey link label.
     *
     * @default '({ link }) => `${link.source.label.text} to ${link.target.label.text}`'
     */
    ariaTemplate?: ((data: { link: SankeyLinkDataItem }) => string);
}

/**
 * Represents the offset option of the Sankey node and label elements.
 */
export interface SankeyOffset {
    /**
     * The left offset of the element.
     *
     * @default 0
     */
    left?: number;

    /**
     * The top offset of the element.
     *
     * @default 0
     */
    top?: number;
}

/**
 * Represents the links options of the Sankey component.
 */
export interface SankeyLink {
    /**
     * The source node ID of the link.
     */
    sourceId: string | number;
    /**
     * The target node ID of the link.
     */
    targetId: string | number;
    /**
     * The value of the link.
     */
    value: number;
    /**
     * The color type of the link.
     * The supported values are:
     * * `static`&mdash;The link color is static. The color is determined by the link's `color` option.
     * * `source`&mdash;The link color is the same as the source node color.
     * * `target`&mdash;The link color is the same as the target node color.
     *
     * @default 'static'
     */
    colorType?: 'static' | 'source' | 'target';
    /**
     * The color of the link.
     * The color is used when the `colorType` option is set to `static`.
     */
    color?: string;
    /**
     * The opacity of the link.
     *
     * @default 0.4
     */
    opacity?: number;
    /**
     * The highlight options of the link.
     *
     * @default `{ opacity: 0.8, inactiveOpacity: 0.2 }`
     */
    highlight?: SankeyLinkHighlight;
}

/**
 * Represents the horizontal margin of a Sankey element.
 */
export interface SankeyHorizontalMargin extends Pick<Margin, 'left' | 'right'> { }

/**
 * Represents the stroke options of the Sankey label.
 */
export interface SankeyLabelStroke {
    /**
     * The stroke color of the label.
     * Accepts a valid CSS color string, including hex and rgb.
    */
    color?: string;
    /**
     * The stroke width of the label.
     *
     * @default 1
     */
    width?: number;
    /**
     * The line join option of the label.
     *
     * The supported values are:
     * * `round`&mdash;The outer corners of the stroke will be rounded.
     * * `bevel`&mdash;The outer corners of the stroke will be beveled.
     * * `miter`&mdash;The outer corners of the stroke will be squared off.
     *
     * @default 'round'
     */
    lineJoin?: 'round' | 'bevel' | 'miter';
}

/**
 * Represents the labels options of the Sankey component.
 */
export interface SankeyLabel {
    /**
     * The text of the label.
     */
    text?: string;
    /**
     * The visibility of the label.
     *
     * @default true
     */
    visible?: boolean;
    /**
     * The font of the label.
     */
    font?: string;
    /**
     * The color of the label.
     */
    color?: string;
    /**
     * The alignment of the label.
     *
     * @default 'left'
     */
    align?: 'left' | 'right' | 'center';
    /**
     * The position of the label.
     *
     * The supported values are:
     * * `inside`&mdash;The label is positioned after the node, except for the nodes at the end of the Sankey, that are placed before the node.
     * * `before`&mdash;The label is positioned before the node.
     * * `after`&mdash;The label is positioned after the node.
     *
     * @default 'inside'
     */
    position?: 'inside' | 'before' | 'after';
    /**
     * The padding of the label.
     *
     * @default 0
     */
    padding?: number | Padding;
    /**
     * The margin of the label.
     *
     * @default `{ left: 8, right: 8 }`
     */
    margin?: SankeyHorizontalMargin;
    /**
     * The border of the label.
     *
     * @default `{ width: 0 }`
     */
    border?: Border;
    /**
     * The offset applied to the label's position.
     *
     * @default `{ top: 0, left: 0 }`
     */
    offset?: SankeyOffset;

    /**
     * The stroke of the label.
     *
     * @default `{ width: 1, lineJoin: 'round' }`
     */
    stroke?: SankeyLabelStroke;
}

/**
 * Represents the nodes options of the Sankey component.
 */
export interface SankeyNode {
    /**
     * The ID of the node.
     */
    id: string | number;
    /**
     * The label options of the node.
     */
    label: SankeyLabel;
    /**
     * The color of the node.
     */
    color?: string;
    /**
     * The opacity of the node.
     *
     * @default 1
     */
    opacity?: number;
    /**
     * The offset applied to the node's position.
     *
     * @default `{ top: 0, left: 0 }`
     */
    offset?: SankeyOffset;
    /**
     * The minimum vertical space between two nodes.
     *
     * @default 16
     */
    padding?: number;
    /**
     * The width of the node.
     *
     * @default 24
     */
    width?: number;

    /**
     * The alignment of the node.
     *
     * @default 'stretch'
     */
    align?: 'stretch' | 'left' | 'right';
}

/**
 * Represents the title options of the Sankey component.
 */
export interface SankeyTitle {
    /**
     * The alignment of the title.
     */
    align?: 'center' | 'left' | 'right';
    /**
     * The background of the title. Accepts a valid CSS color string, including hex and rgb.
     */
    background?: string;
    /**
     * The border of the title.
     */
    border?: Border;
    /**
     * The color of the title. Accepts a valid CSS color string, including hex and rgb.
     */
    color?: string;
    /**
     * The font of the title.
     */
    font?: string;
    /**
     * The margin of the title.
     */
    margin?: Margin | number;
    /**
     * The padding of the title.
     */
    padding?: Padding | number;
    /**
     * The position of the title.
     */
    position?: 'top' | 'bottom';
    /**
     * The text of the title.
     */
    text?: string;
    /**
     * The accessible description of the Sankey. The description is announced by screen readers when the Sankey is focused.
     */
    description?: string;
    /**
     * The visibility of the title. If set to `false`, the title will not be displayed.
     */
    visible?: boolean;
}

/**
 * @hidden
 */
interface LegendTitle extends Omit<SankeyTitle, 'description'> { }

/**
 * Represents the legend item options of the Sankey component.
 */
export interface SankeyLegendItem {
    /**
     * The background color of the legend item square element. Accepts a valid CSS color string, including HEX and RGB.
     * Defaults to the corresponding node color.
     */
    areaBackground?: string;
    /**
     * The opacity of the legend item square element.
     * Defaults to the corresponding node opacity.
     */
    areaOpacity?: number;
    /**
     * The [cursor style](https://developer.mozilla.org/en-US/docs/Web/CSS/cursor) of the legend item.
     */
    cursor?: string;
    /**
     * A function that can be used to create a custom visual for the legend items. The available argument fields are:
     *
     * * `options`&mdash;The options of the legend item.
     * * `createVisual`&mdash;A function for getting the default visual.
     */
    visual?: (args: { options: any; createVisual: () => Group }) => Group;
}

/**
 * Represents the legend label options of the Sankey component.
 */
export interface SankeyLegendLabel {
    /**
     * The color of the legend item label.
     */
    color?: string;
    /**
     * The font of the legend item label.
     */
    font?: string;
    /**
     * The margin of the legend item label.
     */
    margin?: Margin | number;
    /**
     * The padding of the legend item label.
     */
    padding?: Padding | number;
}

/**
 * Represents the legend options of the Sankey component.
 */
export interface SankeyLegend {
    /**
     * The alignment of the legend.
     */
    align?: 'start' | 'center' | 'end';
    /**
     * The background of the legend. Accepts a valid CSS color string, including hex and rgb.
     */
    background?: string;
    /**
     * The border of the legend.
     */
    border?: Border;
    /**
     * The height of the legend.
     */
    height?: number;
    /**
     * The item options of the legend. The item options apply to the legend items and will override the default options.
     */
    item?: SankeyLegendItem;
    /**
     * The labels options of the legend.
     */
    labels?: SankeyLegendLabel;
    /**
     * The margin of the legend.
     */
    margin?: Margin | number;
    /**
     * The X offset of the Sankey legend. The offset is relative to the default position of the legend.
     * For instance, a value of 20 will move the legend 20 pixels to the right of its initial position.
     * A negative value will move the legend to the left of its current position.
     */
    offsetX?: number;
    /**
     * The Y offset of the Sankey legend.  The offset is relative to the current position of the legend.
     * For instance, a value of 20 will move the legend 20 pixels down from its initial position.
     * A negative value will move the legend upwards from its current position.
     */
    offsetY?: number;
    /**
     * The orientation of the legend items.
     */
    orientation?: 'vertical' | 'horizontal';
    /**
     * The padding of the legend. A numeric value will set all paddings.
     */
    padding?: Padding | number;
    /**
     * The position of the legend.
     *
     * The supported values are:
     *
     * * `top`&mdash;The legend is positioned on the top.
     * * `bottom`&mdash;The legend is positioned on the bottom.
     * * `left`&mdash;The legend is positioned on the left.
     * * `right`&mdash;The legend is positioned on the right.
     * * `custom`&mdash;The legend is positioned based on the `offsetX` and `offsetY` options.
     */
    position?: 'top' | 'bottom' | 'left' | 'right' | 'custom';
    /**
     * If set to `true` the legend items will be reversed.
     */
    reverse?: boolean;
    /**
     * The spacing between the labels in pixels when the `legend.orientation` is "horizontal".
     */
    spacing?: number;
    /**
     * The title options of the legend.
     */
    title?: LegendTitle;
    /**
     * The visibility of the legend.
     */
    visible?: boolean;
    /**
     * The width of the legend.
     */
    width?: number;
}

/**
 * Represents the tooltip options of the Sankey component.
 */
export interface SankeyTooltip {
    /**
     * Indicates whether the tooltip will follow the mouse pointer.
     *
     * @default false
     */
    followPointer?: boolean;

    /**
     * Represents the delay of the tooltip displaying.
     * The delay is measured in milliseconds.
     *
     * @default 200
     */
    delay?: number;
}

/**
 * Represents the default link options of the Sankey component.
 * These settings will be applied to all links unless overridden by individual data items.
 */
export interface SankeyLinkDefaults extends Omit<SankeyLink, 'sourceId' | 'targetId' | 'value'> {
    /**
     * The focus highlight options of the Sankey link elements.
     */
    focusHighlight?: SankeyFocusHighlight;
    /**
     * The labels options of the Sankey link.
     */
    labels?: SankeyLinkLabel;
}

/**
 * Represents the default label options of the Sankey component.
 * These settings will be applied to all labels unless overridden by individual data items.
 */
export interface SankeyLabelDefaults extends Omit<SankeyLabel, 'text'> { }

/**
 * Represents the default node options of the Sankey component.
 * These settings will be applied to all nodes unless overridden by individual data items.
 */
export interface SankeyNodeDefaults extends Omit<SankeyNode, 'id' | 'label'> {
    /**
     * The focus highlight options of the Sankey node elements.
     */
    focusHighlight?: SankeyFocusHighlight;

    /**
     * The labels options of the Sankey node.
     */
    labels?: SankeyNodeLabel;
}

/**
 * Represents the Sankey data object.
 */
export interface SankeyData {
    /**
     * The links of the Sankey. The links are the connections between the nodes.
     * Each link has a `sourceId` and `targetId` that correspond to the id of the source and target nodes, and a `value` that represents the value of the link.
    */
    links: SankeyLink[];
    /**
     * The nodes of the Sankey. The nodes are the elements that are connected by the links.
     * Each node has an `id` that is used to connect the nodes with the links.
     */
    nodes: SankeyNode[];
}

/**
 * Represents the options of the Sankey component.
 */
export interface SankeyOptions {
    /**
     * The data of the Sankey. The data object contains the Sankey nodes and links configuration.
     */
    data: SankeyData;
    /**
     * The default label options of the Sankey.
     */
    labels?: SankeyLabelDefaults;
    /**
     * The default link options of the Sankey.
     */
    links?: SankeyLinkDefaults;
    /**
     * The default node options of the Sankey.
     */
    nodes?: SankeyNodeDefaults;
    /**
     * If set to `true`, the Sankey will not automatically reorder the nodes to reduce the number of links that cross over each other.
     */
    disableAutoLayout?: boolean;
    /**
     * The Sankey title configuration options.
     */
    title?: SankeyTitle;
    /**
     * The legend configuration options of the Sankey.
     */
    legend?: SankeyLegend;
    /**
     * The tooltip configuration options of the Sankey.
     */
    tooltip?: SankeyTooltip;
    /**
     * If set to `true`, the Sankey keyboard navigation will be disabled.
     */
    disableKeyboardNavigation?: boolean;
    /**
     * If set to `true`, the Sankey will render from right to left.
     */
    rtl?: boolean;
}

/**
 * @hidden
 */
export interface SankeyTheme {
    labels?: SankeyLabelDefaults;
    links?: SankeyLinkDefaults;
    nodes?: SankeyNodeDefaults;
    nodeColors: string[];
    title?: SankeyTitle;
    legend?: SankeyLegend;
}

/**
 * Represents the options for exporting the Sankey visual.
 */
export interface SankeyExportVisualOptions {
    /**
     * The width of the exported visual.
     */
    width?: number;
    /**
     * The height of the exported visual.
     */
    height?: number;
    /**
     * The Sankey options to be used for the exported visual.
     * The options will extend and override the Sankey configuration.
     */
    options?: SankeyOptions;
}

/**
 * Represents the Kendo Sankey component.
 */
export class Sankey {
    public element: HTMLElement;
    constructor(element: HTMLElement, options: SankeyOptions, theme?: SankeyTheme);

    public setOptions(options: SankeyOptions): void;
    public bind(event: any, handlers: any): void;
    public unbind(event?: any, handlers?: any): void;
    public exportVisual(options?: SankeyExportVisualOptions): Group;
    public destroy(): void;
}

/**
 * Represents the Sankey component event object.
 */
export interface SankeyEvent {
    /**
     * The Sankey component instance.
     */
    sender: Sankey;
    /**
     * The native DOM event.
     */
    originalEvent: Event;
    /**
     * The event type.
     */
    type: string;
    /**
     * The target element type.
     */
    targetType: 'node' | 'link';
    /**
     * The dataItem of the related element.
     */
    dataItem: SankeyLinkDataItem | SankeyNodeDataItem;
    /**
     * Prevents the default action.
     */
    preventDefault(): void;
    /**
     * Indicates whether the default action has been prevented.
     */
    isDefaultPrevented(): boolean;
}

/**
 * Represents the Sankey dataItem of the link element.
 */
export interface SankeyLinkDataItem {
    /**
     * The source node dataItem.
     */
    source: SankeyNodeDataItem;
    /**
     * The target node dataItem.
     */
    target: SankeyNodeDataItem;
    /**
     * The value of the link.
     */
    value: number;
}

/**
 * Represents the Sankey dataItem of the node element.
 */
export interface SankeyNodeDataItem extends SankeyNode {
    /**
     * The source links that are connected to the node.
     * Contains the `sourceId`, `targetId` and `value` of the links.
     */
    sourceLinks: SankeyLink[];
    /**
     * The target links that are connected to the node.
     * Contains the `sourceId`, `targetId` and `value` of the links.
     */
    targetLinks: SankeyLink[];
}

/**
 * Represents the Sankey `tooltipShow` event object.
 */
export interface SankeyTooltipEvent extends SankeyEvent {
    /**
     * The tooltip data object.
     */
    tooltipData: {
        popupOffset: { left: number, top: number };
        popupAlign: { horizontal: string, vertical: string };
    };

    /**
     * The target element value.
     * Available for the `node` target type.
     */
    nodeValue?: number;
}

/**
 * Converts data objects to Sankey data.
 *
 * @param data - The data objects.
 * @param dimensions - The dimensions of the data.
 * @param measure - The measure of the data.
 * @returns The Sankey data.
 *
 * @example
 * ```ts
 * const data = [
 *  { gender: 'Female', device: 'Tablet', age: '< 18', value: 4 },
 *  { gender: 'Female', device: 'Tablet', age: "> 40", value: 8 },
 *  { gender: 'Female', device: 'Mobile', age: '< 18', value: 4 },
 *  { gender: 'Female', device: 'Mobile', age: '18-26', value: 24 },
 *  { gender: 'Female', device: 'Mobile', age: '27-40', value: 10 },
 *  { gender: 'Female', device: 'Mobile', age: '> 40', value: 2 },
 *  { gender: 'Female', device: 'Desktop', age: '18-26', value: 11 },
 *  { gender: 'Female', device: 'Desktop', age: '27-40', value: 28 },
 *  { gender: 'Female', device: 'Desktop', age: '> 40', value: 9 },
 *  { gender: 'Male', device: 'Mobile', age: '< 18', value: 4 },
 *  { gender: 'Male', device: 'Mobile', age: '18-26', value: 11 },
 *  { gender: 'Male', device: 'Mobile', age: '27-40', value: 28 },
 *  { gender: 'Male', device: 'Mobile', age: '> 40', value: 9 },
 *  { gender: 'Male', device: 'Desktop', age: '18-26', value: 11 },
 *  { gender: 'Male', device: 'Desktop', age: '27-40', value: 28 },
 *  { gender: 'Male', device: 'Desktop', age: '> 40', value: 9 },
 * ];
 *
 * const dimensions = [
 *  { value: item => item.gender },
 *  { value: item => item.device },
 *  { value: item => 'Age ' + item.age }
 * ];
 * const measure = { value: item => item.value };
 *
 * const sankeyData = createSankeyData(data, dimensions, measure);
 *
 * const sankey = new Sankey(document.getElementById('sankey'), {
 *  data: sankeyData
 * });
 * ```
 */
export function createSankeyData(
    data: object[],
    dimensions: { value: (item: object) => string }[],
    measure: { value: (item: object) => number }
): { links: SankeyLink[]; nodes: SankeyNode[] };
