"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var CategoryPlugin_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.CategoryPlugin = void 0;
const models_1 = require("../../models");
const models_2 = require("../../models");
const components_1 = require("../components");
const converter_1 = require("../converter");
const utils_1 = require("../../utils");
/**
 * A handler that sorts and categorizes the found reflections in the resolving phase.
 *
 * The handler sets the ´category´ property of all reflections.
 */
let CategoryPlugin = CategoryPlugin_1 = class CategoryPlugin extends components_1.ConverterComponent {
    /**
     * Create a new CategoryPlugin instance.
     */
    initialize() {
        this.listenTo(this.owner, {
            [converter_1.Converter.EVENT_BEGIN]: this.onBegin,
            [converter_1.Converter.EVENT_RESOLVE]: this.onResolve,
            [converter_1.Converter.EVENT_RESOLVE_END]: this.onEndResolve,
        }, undefined, -200);
    }
    /**
     * Triggered when the converter begins converting a project.
     */
    onBegin(_context) {
        // Set up static properties
        if (this.defaultCategory) {
            CategoryPlugin_1.defaultCategory = this.defaultCategory;
        }
        if (this.categoryOrder) {
            CategoryPlugin_1.WEIGHTS = this.categoryOrder;
        }
    }
    /**
     * Triggered when the converter resolves a reflection.
     *
     * @param context  The context object describing the current state the converter is in.
     * @param reflection  The reflection that is currently resolved.
     */
    onResolve(context, reflection) {
        var _a, _b;
        if (reflection instanceof models_1.ContainerReflection) {
            this.categorize(reflection, (_b = (_a = context.getSearchOptions()) === null || _a === void 0 ? void 0 : _a.searchCategoryBoosts) !== null && _b !== void 0 ? _b : {});
        }
    }
    /**
     * Triggered when the converter has finished resolving a project.
     *
     * @param context  The context object describing the current state the converter is in.
     */
    onEndResolve(context) {
        var _a, _b;
        const project = context.project;
        this.categorize(project, (_b = (_a = context.getSearchOptions()) === null || _a === void 0 ? void 0 : _a.searchCategoryBoosts) !== null && _b !== void 0 ? _b : {});
    }
    categorize(obj, categorySearchBoosts) {
        if (this.categorizeByGroup) {
            this.groupCategorize(obj, categorySearchBoosts);
        }
        else {
            CategoryPlugin_1.lumpCategorize(obj, categorySearchBoosts);
        }
    }
    groupCategorize(obj, categorySearchBoosts) {
        if (!obj.groups || obj.groups.length === 0) {
            return;
        }
        obj.groups.forEach((group) => {
            if (group.categories)
                return;
            group.categories = CategoryPlugin_1.getReflectionCategories(group.children, categorySearchBoosts);
            if (group.categories && group.categories.length > 1) {
                group.categories.sort(CategoryPlugin_1.sortCatCallback);
            }
            else if (group.categories.length === 1 &&
                group.categories[0].title === CategoryPlugin_1.defaultCategory) {
                // no categories if everything is uncategorized
                group.categories = undefined;
            }
        });
    }
    static lumpCategorize(obj, categorySearchBoosts) {
        if (!obj.children || obj.children.length === 0 || obj.categories) {
            return;
        }
        obj.categories = CategoryPlugin_1.getReflectionCategories(obj.children, categorySearchBoosts);
        if (obj.categories && obj.categories.length > 1) {
            obj.categories.sort(CategoryPlugin_1.sortCatCallback);
        }
        else if (obj.categories.length === 1 &&
            obj.categories[0].title === CategoryPlugin_1.defaultCategory) {
            // no categories if everything is uncategorized
            obj.categories = undefined;
        }
    }
    /**
     * Create a categorized representation of the given list of reflections.
     *
     * @param reflections  The reflections that should be categorized.
     * @param categorySearchBoosts A user-supplied map of category titles, for computing a
     *   relevance boost to be used when searching
     * @returns An array containing all children of the given reflection categorized
     */
    static getReflectionCategories(reflections, categorySearchBoosts) {
        const categories = [];
        let defaultCat;
        reflections.forEach((child) => {
            var _a, _b;
            const childCategories = CategoryPlugin_1.getCategories(child);
            if (childCategories.size === 0) {
                if (!defaultCat) {
                    defaultCat = categories.find((category) => category.title === CategoryPlugin_1.defaultCategory);
                    if (!defaultCat) {
                        defaultCat = new models_2.ReflectionCategory(CategoryPlugin_1.defaultCategory);
                        categories.push(defaultCat);
                    }
                }
                defaultCat.children.push(child);
                return;
            }
            for (const childCat of childCategories) {
                let category = categories.find((cat) => cat.title === childCat);
                const catBoost = categorySearchBoosts[(_a = category === null || category === void 0 ? void 0 : category.title) !== null && _a !== void 0 ? _a : -1];
                if (catBoost != undefined) {
                    child.relevanceBoost =
                        ((_b = child.relevanceBoost) !== null && _b !== void 0 ? _b : 1) * catBoost;
                }
                if (category) {
                    category.children.push(child);
                    continue;
                }
                category = new models_2.ReflectionCategory(childCat);
                category.children.push(child);
                categories.push(category);
            }
        });
        return categories;
    }
    /**
     * Return the category of a given reflection.
     *
     * @param reflection The reflection.
     * @returns The category the reflection belongs to
     */
    static getCategories(reflection) {
        var _a, _b, _c;
        function extractCategoryTag(comment) {
            const categories = new Set();
            if (!comment)
                return categories;
            const tags = comment.tags;
            const commentTags = [];
            tags.forEach((tag) => {
                if (tag.tagName !== "category") {
                    commentTags.push(tag);
                    return;
                }
                const text = tag.text.trim();
                if (!text) {
                    return;
                }
                categories.add(text);
            });
            comment.tags = commentTags;
            return categories;
        }
        let categories = new Set();
        if (reflection.comment) {
            categories = extractCategoryTag(reflection.comment);
        }
        else if (reflection.signatures) {
            for (const sig of reflection.signatures) {
                for (const cat of extractCategoryTag(sig.comment)) {
                    categories.add(cat);
                }
            }
        }
        if (((_a = reflection.type) === null || _a === void 0 ? void 0 : _a.type) === "reflection") {
            (_b = reflection.type.declaration.comment) === null || _b === void 0 ? void 0 : _b.removeTags("category");
            (_c = reflection.type.declaration.signatures) === null || _c === void 0 ? void 0 : _c.forEach((s) => { var _a; return (_a = s.comment) === null || _a === void 0 ? void 0 : _a.removeTags("category"); });
        }
        return categories;
    }
    /**
     * Callback used to sort categories by name.
     *
     * @param a The left reflection to sort.
     * @param b The right reflection to sort.
     * @returns The sorting weight.
     */
    static sortCatCallback(a, b) {
        let aWeight = CategoryPlugin_1.WEIGHTS.indexOf(a.title);
        let bWeight = CategoryPlugin_1.WEIGHTS.indexOf(b.title);
        if (aWeight === -1 || bWeight === -1) {
            let asteriskIndex = CategoryPlugin_1.WEIGHTS.indexOf("*");
            if (asteriskIndex === -1) {
                asteriskIndex = CategoryPlugin_1.WEIGHTS.length;
            }
            if (aWeight === -1) {
                aWeight = asteriskIndex;
            }
            if (bWeight === -1) {
                bWeight = asteriskIndex;
            }
        }
        if (aWeight === bWeight) {
            return a.title > b.title ? 1 : -1;
        }
        return aWeight - bWeight;
    }
};
// For use in static methods
CategoryPlugin.defaultCategory = "Other";
CategoryPlugin.WEIGHTS = [];
__decorate([
    (0, utils_1.BindOption)("defaultCategory")
], CategoryPlugin.prototype, "defaultCategory", void 0);
__decorate([
    (0, utils_1.BindOption)("categoryOrder")
], CategoryPlugin.prototype, "categoryOrder", void 0);
__decorate([
    (0, utils_1.BindOption)("categorizeByGroup")
], CategoryPlugin.prototype, "categorizeByGroup", void 0);
CategoryPlugin = CategoryPlugin_1 = __decorate([
    (0, components_1.Component)({ name: "category" })
], CategoryPlugin);
exports.CategoryPlugin = CategoryPlugin;
