/*
 * Copyright 2021-2024 Avaiga Private Limited
 *
 * 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 React, { CSSProperties, lazy, Suspense, useMemo } from "react";
import { Data, Delta, Layout } from "plotly.js";
import Box from "@mui/material/Box";
import Skeleton from "@mui/material/Skeleton";
import Tooltip from "@mui/material/Tooltip";
import { useTheme } from "@mui/material";
import { useClassNames, useDynamicJsonProperty, useDynamicProperty } from "../../utils/hooks";
import { extractPrefix, extractSuffix, sprintfToD3Converter } from "../../utils/formatConversion";
import { TaipyBaseProps, TaipyHoverProps } from "./utils";
import { darkThemeTemplate } from "../../themes/darkThemeTemplate";

const Plot = lazy(() => import("react-plotly.js"));

interface MetricProps extends TaipyBaseProps, TaipyHoverProps {
    value?: number;
    defaultValue?: number;
    delta?: number;
    defaultDelta?: number;
    type?: string;
    min?: number;
    max?: number;
    deltaColor?: string;
    negativeDeltaColor?: string;
    threshold?: number;
    defaultThreshold?: number;
    format?: string;
    deltaFormat?: string;
    barColor?: string;
    showValue?: boolean;
    colorMap?: string;
    title?: string;
    testId?: string;
    layout?: string;
    defaultLayout?: string;
    style?: string;
    defaultStyle?: string;
    width?: string | number;
    height?: string | number;
    template?: string;
    template_Dark_?: string;
    template_Light_?: string;
}

const emptyLayout = {} as Partial<Layout>;
const defaultStyle = { position: "relative", display: "inline-block" };

const Metric = (props: MetricProps) => {
    const { width = "100%", height, showValue = true, deltaColor, negativeDeltaColor } = props;
    const value = useDynamicProperty(props.value, props.defaultValue, 0);
    const threshold = useDynamicProperty(props.threshold, props.defaultThreshold, undefined);
    const delta = useDynamicProperty(props.delta, props.defaultDelta, undefined);
    const className = useClassNames(props.libClassName, props.dynamicClassName, props.className);
    const baseLayout = useDynamicJsonProperty(props.layout, props.defaultLayout || "", emptyLayout);
    const hover = useDynamicProperty(props.hoverText, props.defaultHoverText, undefined);
    const theme = useTheme();

    const colorMap = useMemo(() => {
        try {
            const obj = props.colorMap ? JSON.parse(props.colorMap) : null;
            if (obj && typeof obj === "object") {
                const keys = Object.keys(obj);
                return keys
                    .sort((a, b) => Number(a) - Number(b))
                    .map((key, index) => {
                        const nextKey = keys[index + 1] !== undefined ? Number(keys[index + 1]) : props.max || 100;
                        return { range: [Number(key), nextKey], color: obj[key] };
                    })
                    .filter((item) => item.color !== null);
            }
        } catch (e) {
            console.info(`Error parsing color_map value (metric).\n${(e as Error).message || e}`);
        }
        return undefined;
    }, [props.colorMap, props.max]);

    const data = useMemo(() => {
        const mode = props.type === "none" ? [] : ["gauge"];
        showValue && mode.push("number");
        delta !== undefined && mode.push("delta");
        const deltaIncreasing = deltaColor
            ? {
                  color: deltaColor == "invert" ? "#FF4136" : deltaColor,
              }
            : undefined;
        const deltaDecreasing =
            deltaColor == "invert"
                ? {
                      color: "#3D9970",
                  }
                : negativeDeltaColor
                  ? { color: negativeDeltaColor }
                  : undefined;
        return [
            {
                domain: { x: [0, 1], y: [0, 1] },
                value: value,
                type: "indicator",
                mode: mode.join("+"),
                number: {
                    prefix: extractPrefix(props.format),
                    suffix: extractSuffix(props.format),
                    valueformat: sprintfToD3Converter(props.format),
                },
                delta: {
                    reference: typeof value === "number" && typeof delta === "number" ? value - delta : undefined,
                    prefix: extractPrefix(props.deltaFormat),
                    suffix: extractSuffix(props.deltaFormat),
                    valueformat: sprintfToD3Converter(props.deltaFormat),
                    increasing: deltaIncreasing,
                    decreasing: deltaDecreasing,
                } as Partial<Delta>,
                gauge: {
                    axis: {
                        range: [props.min || 0, props.max || 100],
                    },
                    bar: {
                        color: props.barColor,
                    },
                    steps: colorMap,
                    shape: props.type === "linear" ? "bullet" : "angular",
                    threshold: {
                        line: { color: "red", width: 4 },
                        thickness: 0.75,
                        value: threshold,
                    },
                },
            },
        ] as Data[];
    }, [
        value,
        delta,
        props.type,
        props.min,
        props.max,
        deltaColor,
        negativeDeltaColor,
        threshold,
        props.format,
        props.deltaFormat,
        props.barColor,
        showValue,
        colorMap,
    ]);

    const style = useMemo(
        () =>
            height === undefined
                ? ({ ...defaultStyle, width: width } as CSSProperties)
                : ({ ...defaultStyle, width: width, height: height } as CSSProperties),
        [height, width]
    );

    const skelStyle = useMemo(() => ({ ...style, minHeight: "7em" }), [style]);

    const layout = useMemo(() => {
        const layout = { ...baseLayout };
        let template = undefined;
        try {
            const tpl = props.template && JSON.parse(props.template);
            const tplTheme =
                theme.palette.mode === "dark"
                    ? props.template_Dark_
                        ? JSON.parse(props.template_Dark_)
                        : darkTemplate
                    : props.template_Light_ && JSON.parse(props.template_Light_);
            template = tpl ? (tplTheme ? { ...tpl, ...tplTheme } : tpl) : tplTheme ? tplTheme : undefined;
        } catch (e) {
            console.info(`Error while parsing Metric.template\n${(e as Error).message || e}`);
        }
        if (template) {
            layout.template = template;
        }

        if (props.title) {
            layout.title = props.title;
        }

        return layout as Partial<Layout>;
    }, [props.title, props.template, props.template_Dark_, props.template_Light_, theme.palette.mode, baseLayout]);

    const plotConfig = {displaylogo: false}
    return (
        <Tooltip title={hover || ""}>
            <Box data-testid={props.testId} className={className}>
                <Suspense fallback={<Skeleton key="skeleton" sx={skelStyle} />}>
                    <Plot data={data} layout={layout} style={style} config={plotConfig} useResizeHandler />
                </Suspense>
            </Box>
        </Tooltip>
    );
};

export default Metric;

const { colorscale, colorway, font } = darkThemeTemplate.layout;
const darkTemplate = {
    layout: {
        colorscale,
        colorway,
        font,
        paper_bgcolor: "rgb(31,47,68)",
    },
};
