/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you 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 * as zrUtil from 'zrender/src/core/util';
import ExtensionAPI from '../core/ExtensionAPI';
import GlobalModel from '../model/Global';
import Model from '../model/Model';
import SeriesModel from '../model/Series';
import {makeInner} from '../util/model';
import {Dictionary, DecalObject, InnerDecalObject, AriaOption} from '../util/types';
import {LocaleOption} from '../core/locale';
import { getDecalFromPalette } from '../model/mixin/palette';
import type {TitleOption} from '../component/title/install';

const DEFAULT_OPTION: AriaOption = {
    label: {
        enabled: true
    },
    decal: {
        show: false
    }
};

const inner = makeInner<{scope: object}, SeriesModel>();

const decalPaletteScope: Dictionary<DecalObject> = {};

type SeriesTypes = keyof LocaleOption['series']['typeNames'];

export default function ariaVisual(ecModel: GlobalModel, api: ExtensionAPI) {
    const ariaModel: Model<AriaOption> = ecModel.getModel('aria');

    // See "area enabled" detection code in `GlobalModel.ts`.
    if (!ariaModel.get('enabled')) {
        return;
    }

    const defaultOption = zrUtil.clone(DEFAULT_OPTION);
    zrUtil.merge(defaultOption.label, ecModel.getLocaleModel().get('aria'), false);
    zrUtil.merge(ariaModel.option, defaultOption, false);

    setDecal();
    setLabel();

    function setDecal() {
        const decalModel = ariaModel.getModel('decal');

        const useDecal = decalModel.get('show');
        if (useDecal) {
            // Each type of series use one scope.
            // Pie and funnel are using different scopes.
            const paletteScopeGroupByType = zrUtil.createHashMap<object>();
            ecModel.eachSeries((seriesModel: SeriesModel) => {
                if (seriesModel.isColorBySeries()) {
                    return;
                }
                let decalScope = paletteScopeGroupByType.get(seriesModel.type);
                if (!decalScope) {
                    decalScope = {};
                    paletteScopeGroupByType.set(seriesModel.type, decalScope);
                }
                inner(seriesModel).scope = decalScope;
            });

            ecModel.eachRawSeries((seriesModel: SeriesModel) => {
                if (ecModel.isSeriesFiltered(seriesModel)) {
                    return;
                }
                if (zrUtil.isFunction(seriesModel.enableAriaDecal)) {
                    // Let series define how to use decal palette on data
                    seriesModel.enableAriaDecal();
                    return;
                }

                const data = seriesModel.getData();

                if (!seriesModel.isColorBySeries()) {
                    const dataAll = seriesModel.getRawData();
                    const idxMap: Dictionary<number> = {};
                    const decalScope = inner(seriesModel).scope;

                    data.each(function (idx) {
                        const rawIdx = data.getRawIndex(idx);
                        idxMap[rawIdx] = idx;
                    });

                    const dataCount = dataAll.count();
                    dataAll.each(rawIdx => {
                        const idx = idxMap[rawIdx];
                        const name = dataAll.getName(rawIdx) || (rawIdx + '');
                        const paletteDecal = getDecalFromPalette(
                            seriesModel.ecModel,
                            name,
                            decalScope,
                            dataCount
                        );
                        const specifiedDecal = data.getItemVisual(idx, 'decal');
                        data.setItemVisual(idx, 'decal', mergeDecal(specifiedDecal, paletteDecal));
                    });
                }
                else {
                    const paletteDecal = getDecalFromPalette(
                        seriesModel.ecModel,
                        seriesModel.name,
                        decalPaletteScope,
                        ecModel.getSeriesCount()
                    );
                    const specifiedDecal = data.getVisual('decal');
                    data.setVisual('decal', mergeDecal(specifiedDecal, paletteDecal));
                }

                function mergeDecal(specifiedDecal: DecalObject, paletteDecal: DecalObject): DecalObject {
                    // Merge decal from palette to decal from itemStyle.
                    // User do not need to specify all of the decal props.
                    const resultDecal = specifiedDecal
                        ? zrUtil.extend(zrUtil.extend({}, paletteDecal), specifiedDecal)
                        : paletteDecal;
                    (resultDecal as InnerDecalObject).dirty = true;
                    return resultDecal;
                }
            });
        }
    }

    function setLabel() {
        const labelLocale = ecModel.getLocaleModel().get('aria');
        const labelModel = ariaModel.getModel('label');
        labelModel.option = zrUtil.defaults(labelModel.option, labelLocale);

        if (!labelModel.get('enabled')) {
            return;
        }

        const dom = api.getZr().dom;
        if (labelModel.get('description')) {
            dom.setAttribute('aria-label', labelModel.get('description'));
            return;
        }

        const seriesCnt = ecModel.getSeriesCount();
        const maxDataCnt = labelModel.get(['data', 'maxCount']) || 10;
        const maxSeriesCnt = labelModel.get(['series', 'maxCount']) || 10;
        const displaySeriesCnt = Math.min(seriesCnt, maxSeriesCnt);

        let ariaLabel;
        if (seriesCnt < 1) {
            // No series, no aria label
            return;
        }
        else {
            const title = getTitle();
            if (title) {
                const withTitle = labelModel.get(['general', 'withTitle']);
                ariaLabel = replace(withTitle, {
                    title: title
                });
            }
            else {
                ariaLabel = labelModel.get(['general', 'withoutTitle']);
            }

            const seriesLabels: string[] = [];
            const prefix = seriesCnt > 1
                ? labelModel.get(['series', 'multiple', 'prefix'])
                : labelModel.get(['series', 'single', 'prefix']);
            ariaLabel += replace(prefix, { seriesCount: seriesCnt });

            ecModel.eachSeries(function (seriesModel, idx) {
                if (idx < displaySeriesCnt) {
                    let seriesLabel;

                    const seriesName = seriesModel.get('name');
                    const withName = seriesName ? 'withName' : 'withoutName';
                    seriesLabel = seriesCnt > 1
                        ? labelModel.get(['series', 'multiple', withName])
                        : labelModel.get(['series', 'single', withName]);

                    seriesLabel = replace(seriesLabel, {
                        seriesId: seriesModel.seriesIndex,
                        seriesName: seriesModel.get('name'),
                        seriesType: getSeriesTypeName(seriesModel.subType as SeriesTypes)
                    });

                    const data = seriesModel.getData();
                    if (data.count() > maxDataCnt) {
                        // Show part of data
                        const partialLabel = labelModel.get(['data', 'partialData']);
                        seriesLabel += replace(partialLabel, {
                            displayCnt: maxDataCnt
                        });
                    }
                    else {
                        seriesLabel += labelModel.get(['data', 'allData']);
                    }

                    const middleSeparator = labelModel.get(['data', 'separator', 'middle']);
                    const endSeparator = labelModel.get(['data', 'separator', 'end']);
                    const dataLabels = [];
                    for (let i = 0; i < data.count(); i++) {
                        if (i < maxDataCnt) {
                            const name = data.getName(i);
                            const value = data.getValues(i);
                            const dataLabel = labelModel.get(['data', name ? 'withName' : 'withoutName']);
                            dataLabels.push(
                                replace(dataLabel, {
                                    name: name,
                                    value: value.join(middleSeparator)
                                })
                            );
                        }
                    }
                    seriesLabel += dataLabels.join(middleSeparator) + endSeparator;

                    seriesLabels.push(seriesLabel);
                }
            });

            const separatorModel = labelModel.getModel(['series', 'multiple', 'separator']);
            const middleSeparator = separatorModel.get('middle');
            const endSeparator = separatorModel.get('end');
            ariaLabel += seriesLabels.join(middleSeparator) + endSeparator;

            dom.setAttribute('aria-label', ariaLabel);
        }
    }

    function replace(str: string, keyValues: object) {
        if (!zrUtil.isString(str)) {
            return str;
        }

        let result = str;
        zrUtil.each(keyValues, function (value: string, key: string) {
            result = result.replace(
                new RegExp('\\{\\s*' + key + '\\s*\\}', 'g'),
                value
            );
        });
        return result;
    }

    function getTitle() {
        let title = ecModel.get('title') as TitleOption | TitleOption[];
        if (title && (title as TitleOption[]).length) {
            title = (title as TitleOption[])[0];
        }
        return title && (title as TitleOption).text;
    }

    function getSeriesTypeName(type: SeriesTypes) {
        const typeNames = ecModel.getLocaleModel().get(['series', 'typeNames']);
        return typeNames[type] || typeNames.chart;
    }
}
