"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.V4AppProvider = exports.unhandledControllers = exports.Constants = void 0;
exports.getPagesAndConfigs = getPagesAndConfigs;
exports.getHome = getHome;
exports.getSettings = getSettings;
exports.getPages = getPages;
const appProvider_1 = require("../../../common/appProvider");
const application_1 = require("../../application");
const common_1 = require("../../../common");
const listReport_1 = require("../pages/listReport");
const objectPage_1 = require("../pages/objectPage");
const fpmCustomPage_1 = require("../pages/fpmCustomPage");
const ux_specification_types_1 = require("@sap/ux-specification-types");
const utils_1 = require("../utils");
const extensionLogger_1 = require("../../../../extensionLogger");
const i18next_1 = __importDefault(require("i18next"));
const i18n_1 = require("../../../../i18n/i18n");
const importProject_1 = require("../../../common/importProject");
var Constants;
(function (Constants) {
    Constants["OptionalQuery"] = ":?query:";
    Constants["Component"] = "sap/fe/AppComponent";
    Constants["Namespace"] = "sap.fe.templates";
})(Constants || (exports.Constants = Constants = {}));
// Store controller names which should be handled lately
exports.unhandledControllers = [];
// Store route associations
let associations = {};
/**
 * Sort in following way.
 * 1. string targets first.
 * 2. Then sort targets as array by elements count - from lowest to highest(ASC).
 *
 * @param {SapUi5RoutingRoute} route1 - First instance of router to compare.
 * @param {SapUi5RoutingRoute} route2 - Second instance of router to compare.
 * @returns {number} Compare result.
 */
function routesSorter(route1, route2) {
    const target1 = route1.target;
    const target2 = route2.target;
    const isT1Array = Array.isArray(target1);
    const isT2Array = Array.isArray(target2);
    if (isT1Array && isT2Array) {
        return target1.length - target2.length;
    }
    if (!isT1Array && !isT2Array) {
        return 0;
    }
    return !isT1Array ? -1 : 1;
}
/**
 * Method to resolve target and return target name as string.
 * Thing is that we can receive target as array of targets and we need detect which target is unique target in that array.
 *
 * @param {SapUi5RoutingRouteTarget} target - Target to resolve.
 * @param {Array<string>} usedTargets - Array of resolved targets(which means that those targets are not unique anymore).
 * @returns {string} Resolved unique target namet.
 */
function resolveTarget(target, usedTargets = []) {
    if (!Array.isArray(target)) {
        return target;
    }
    let targetParts = target;
    for (const usedTarget of usedTargets) {
        targetParts = targetParts.filter((targetPart) => !usedTarget.includes(targetPart));
    }
    return targetParts[0];
}
/**
 * Method which populates object with routings routes associations.
 *
 * @param {SapUi5RoutingRoute} routes - Routing routes.
 * @returns {object} Map object of association routes.
 */
function populateAssociations(routes) {
    const routesAssociations = {};
    routes = routes.concat().sort(routesSorter);
    const resolvedTargets = [];
    for (const route of routes) {
        const parts = route.pattern.split(':')[0].split('/');
        let target;
        if (Array.isArray(route.target)) {
            target = resolveTarget(route.target, resolvedTargets);
            resolvedTargets.push(route.target);
        }
        else {
            target = route.target;
        }
        for (const part of parts) {
            const prop = part.split('(')[0];
            routesAssociations[target] = routesAssociations[target] ? `${routesAssociations[target]}.${prop}` : prop;
        }
    }
    return routesAssociations;
}
/**
 * Determines the page type of a routing target.
 *
 * @param target - routing target, as defined in manifest
 * @param logger - logger for error messages
 * @param routingTargetId - ID of the routing target
 * @returns the page type
 */
function determinePageType(target, logger, routingTargetId) {
    let pageType;
    if (target['name']) {
        if (target['name'] === ux_specification_types_1.v4.FE_TEMPLATE_V4_CUSTOM_PAGE) {
            pageType = ux_specification_types_1.PageTypeV4.FPMCustomPage;
        }
        else if (target['name'].endsWith(ux_specification_types_1.PageTypeV4.ListReport) ||
            target['name'].endsWith(ux_specification_types_1.PageTypeV4.AnalyticalListPage)) {
            pageType = ux_specification_types_1.PageTypeV4.ListReport;
        }
        else if (target['name'].endsWith(ux_specification_types_1.PageTypeV4.ObjectPage)) {
            pageType = ux_specification_types_1.PageTypeV4.ObjectPage;
        }
        else {
            pageType = ux_specification_types_1.PageTypeV4.CustomPage;
        }
    }
    else if ('viewId' in target) {
        pageType = ux_specification_types_1.PageTypeV4.CustomPage;
    }
    else {
        (0, extensionLogger_1.log)(logger, {
            severity: "error" /* LogSeverity.Error */,
            message: i18next_1.default.t('INVALIDTARGETDEFINITION', { target: JSON.stringify(target) }),
            location: {
                path: ux_specification_types_1.MANIFESTPATH,
                range: [`${ux_specification_types_1.ManifestSection.ui5}/routing/targets/${routingTargetId}`]
            }
        });
        if (target['viewType'] in ux_specification_types_1.ViewTypes && 'viewName' in target) {
            pageType = ux_specification_types_1.PageTypeV4.CustomPage;
        }
    }
    return pageType;
}
/**
 * Converts the settings of a page to its corresponding configuration.
 *
 * @param params - The parameters required for the conversion, including target, pageType, manifest, schemas, logger, page, id, templateType, and extensionFiles.
 */
function convertSettingsToConfig(params) {
    const viewId = params.target['viewId'] || params.target['id'];
    const schemaId = (0, common_1.getSchemaFilePath)(params.page.pageType, params.id, viewId).filename;
    if (!schemaId || params.page.pageType === ux_specification_types_1.PageTypeV4.CustomPage) {
        return;
    }
    // Get the JSON schema for the page. If no page specific schema exists, fall back top the generic schema for the page type
    const jsonSchema = Object.keys(params.schemas).find((element) => element.includes(schemaId)) ||
        Object.keys(params.schemas).find((element) => element.includes(schemaId.slice(0, -1))) ||
        Object.keys(params.schemas).find((element) => element.includes(params.page.pageType));
    if (jsonSchema) {
        if (params.page.pageType === ux_specification_types_1.PageTypeV4.ObjectPage) {
            const importParameters = {
                manifest: params.manifest,
                jsonSchema: params.schemas[jsonSchema],
                routingId: params.id,
                fragments: params.extensionFiles.fragments,
                logger: params.logger
            };
            params.page.config = (0, objectPage_1.createObjectPageConfigV4)(importParameters);
        }
        else if (params.page.pageType === ux_specification_types_1.PageTypeV4.FPMCustomPage) {
            const importParameters = {
                manifest: params.manifest,
                jsonSchema: params.schemas[jsonSchema],
                logger: params.logger,
                routingId: params.id,
                fragments: params.extensionFiles.fragments,
                views: params.extensionFiles.views
            };
            params.page.config = (0, fpmCustomPage_1.createCustomPageConfig)(importParameters);
        }
        else if (params.templateType === ux_specification_types_1.TemplateType.ListReportObjectPageV4) {
            const importParameters = {
                manifest: params.manifest,
                jsonSchema: params.schemas[jsonSchema],
                routingId: params.id,
                logger: params.logger
            };
            params.page.config = (0, listReport_1.createListReportConfig)(importParameters, ux_specification_types_1.SchemaType.ListReport);
        }
        else if (params.templateType === ux_specification_types_1.TemplateType.AnalyticalListPageV4) {
            const importParameters = {
                manifest: params.manifest,
                jsonSchema: params.schemas[jsonSchema],
                routingId: params.id,
                logger: params.logger
            };
            params.page.config = (0, listReport_1.createListReportConfig)(importParameters, ux_specification_types_1.SchemaType.AnalyticalListPage);
        }
        else if (params.templateType === ux_specification_types_1.TemplateType.FreestylePageV4 &&
            params.page.pageType === ux_specification_types_1.PageTypeV4.ListReport) {
            const importParameters = {
                manifest: params.manifest,
                jsonSchema: params.schemas[jsonSchema],
                routingId: params.id,
                logger: params.logger
            };
            const templateType = (0, common_1.determineV4PageTemplateType)(params.target);
            const schemaType = templateType === ux_specification_types_1.TemplateType.AnalyticalListPageV4
                ? ux_specification_types_1.SchemaType.AnalyticalListPage
                : ux_specification_types_1.SchemaType.ListReport;
            params.page.config = (0, listReport_1.createListReportConfig)(importParameters, schemaType);
        }
    }
}
/**
 * Updates the class variable collecting the unhandled controllers.
 *
 * @param target - current routing target
 */
function updateUnhandledControllers(target) {
    if (target.options) {
        if ('viewName' in target.options.settings) {
            const viewParts = target.options.settings.viewName.split('.');
            exports.unhandledControllers.push(viewParts[viewParts.length - 1]);
        }
    }
    else if ('viewId' in target) {
        // There no 'options' for custom page target -
        //    we need check target's controller source for navigation
        const viewParts = target.viewName.split('.');
        exports.unhandledControllers.push(viewParts[viewParts.length - 1]);
    }
}
/**
 * Evaluates the target options in manifest, determines variantManagement and page navigation.
 *
 * @param target - target definition in manifest
 * @param page - page config, to be updated
 */
function setVariantManagmentAndNavigation(target, page) {
    if (target.options) {
        if (target.options.settings.variantManagement) {
            page.variantManagement = target.options.settings.variantManagement;
        }
        page.navigation = {};
        for (const name in target.options.settings.navigation) {
            const navigationRoute = target.options.settings.navigation[name];
            page.navigation[name] = navigationRoute.detail;
        }
    }
}
/**
 * Evaluates the target options in manifest, determines entitySet, entityType and contextPath.
 *
 * @param target - target definition in manifest
 * @param page - page config, to be updated
 * @param importParameters - The parameters required for importing configurations, including manifest, schemas, and other related data.
 */
function setEntitySetEntityTypeContextPath(target, page, importParameters) {
    if (target.options) {
        page.entitySet = target.options.settings?.entitySet;
        page.contextPath = target.options.settings?.contextPath;
        const { entitySet, entityType } = (0, importProject_1.getEntityTypeEntitySetFromContextPath)(importParameters.serviceAVT, page.contextPath, page.entitySet, importParameters.logger);
        page.entitySet = page.entitySet || entitySet?.name;
        page.entityType = entityType?.fullyQualifiedName;
    }
}
/**
 * Checks if the manifest's routing target represents a custom page definition.
 *
 * @param target - target definition in manifest
 * @param page - page config, to be updated
 * @param pageType - page type
 */
function evaluateCustomPageSettings(target, page, pageType) {
    if ('viewId' in target) {
        // Populate view of custom page
        page.view = {
            id: target.viewId,
            name: target.viewName,
            ...(target.viewType && { viewType: target.viewType }),
            ...(target.path && { viewPath: target.path }),
            ...(target.viewPath && { viewPath: target.viewPath })
        };
    }
    else if ([ux_specification_types_1.PageTypeV4.CustomPage, ux_specification_types_1.PageTypeV4.FPMCustomPage].includes(pageType)) {
        let viewName = target.options?.settings?.viewName || target['viewName'];
        let template = target.name;
        if (!viewName && pageType === ux_specification_types_1.PageTypeV4.CustomPage) {
            // Freestyle page - read 'viewName' from property 'name' if 'viewName' does not exist.
            viewName = template;
            template = undefined;
        }
        page.view = {
            ...(target.id && { id: target.id }),
            ...(viewName && { name: viewName }),
            ...(template && { template }),
            ...(target['viewType'] && { viewType: target['viewType'] })
        };
    }
}
/**
 * Determines a single page  information for app.json and creates the configuration for the page.
 *
 * @param {v4.SapUi5RoutingV4} routing - complete routing information from manifest
 * @param {string} id - routing target id in manifest
 * @param {v4.Pages} pages - list of all pages, the current page information gets added
 * @param {ImportAllConfigsParameters} importParameters - structure comprising all input parameters
 */
function getSinglePageAndConfig(routing, id, pages, importParameters) {
    const target = routing.targets[id];
    const pageType = determinePageType(target, importParameters.logger, id);
    const page = { pageType };
    // Entity set
    setEntitySetEntityTypeContextPath(target, page, importParameters);
    if (target.controlAggregation) {
        page.controlAggregation = target.controlAggregation;
    }
    if (associations[id] && associations[id].indexOf('.') !== -1) {
        const parts = associations[id].split('.');
        page.navigationProperty = parts[parts.length - 1];
    }
    // Custom page's view object
    evaluateCustomPageSettings(target, page, pageType);
    setVariantManagmentAndNavigation(target, page);
    updateUnhandledControllers(target);
    // Get layout from route
    const route = routing.routes.find((routingRoute) => routingRoute?.target && routingRoute.target.indexOf(id) > -1);
    if (route) {
        page.routePattern = route.pattern;
        if ('layout' in route) {
            page.defaultLayoutType = route.layout;
        }
    }
    // Store original page name/template - it is necessary for export as there can be custom templates like "sap.fe.ariba.ListReport"
    if (!page.view?.template && 'name' in target && target.name) {
        page.template = target.name;
    }
    pages[id] = page;
    // convert settings to config
    const conversionParameters = {
        target,
        pageType,
        manifest: importParameters.manifest,
        schemas: importParameters.appSchemas,
        logger: importParameters.logger,
        page,
        id,
        templateType: importParameters.templateType,
        extensionFiles: { views: importParameters.views, fragments: importParameters.fragments }
    };
    convertSettingsToConfig(conversionParameters);
}
/**
 * Determines the Pages(app.json) using routing configuration from manifest and creates the configuration for each page.
 *
 * @param {ImportAllConfigsParameters} importParameters - structure comprising all input parameters
 * @returns a list of configs for all pages
 */
function getPagesAndConfigs(importParameters) {
    const pages = {};
    const routing = importParameters.manifest[ux_specification_types_1.ManifestSection.ui5].routing;
    if (!routing || !routing.routes) {
        const message = i18next_1.default.t('NOROUTING');
        (0, extensionLogger_1.log)(importParameters.logger, {
            severity: "error" /* LogSeverity.Error */,
            message,
            location: {
                path: ux_specification_types_1.MANIFESTPATH,
                range: [ux_specification_types_1.ManifestSection.ui5]
            }
        });
        return pages;
    }
    associations = populateAssociations(routing.routes || []);
    for (const id in routing.targets) {
        getSinglePageAndConfig(routing, id, pages, importParameters);
    }
    return pages;
}
/**
 * Get the id of the page that is to open when the application is started.
 *
 * @param routing UI5 routing configuration
 * @returns the id of the home page
 */
function getHome(routing) {
    let routeHome;
    if (routing && routing.routes && routing.routes.length > 0) {
        for (const route of routing.routes) {
            //find the home, i.e. route which starts with ':?query:' pattern for List Report,
            //resp. doesn't contain '/' for Form Page
            if (route.pattern.startsWith(':?query:')) {
                routeHome = route;
                break;
            }
            else if (route.pattern.indexOf('/') === -1) {
                routeHome = route;
                continue;
            }
        }
        if (routeHome) {
            return resolveTarget(routeHome.target);
        }
        else {
            return resolveTarget(routing.routes[0].target);
        }
    }
    else {
        return undefined;
    }
}
/**
 * Method which detects app settings from manifest.
 * Currently only some settings are detected - Flexible Column Layout, viewPath, viewType, path.
 *
 * @param routing UI5 routing configuration.
 * @returns {AppSettings} Application settings.
 */
function getSettings(routing) {
    const config = routing?.config;
    return config
        ? {
            ...(config.flexibleColumnLayout && { flexibleColumnLayout: config.flexibleColumnLayout }),
            ...(config.viewType && { viewType: config.viewType }),
            ...(config.path && { viewPath: config.path }),
            ...(config.viewPath && { viewPath: config.viewPath })
        }
        : {};
}
/**
 * Retrieves the pages from the manifest that match the specified target name.
 *
 * @param manifest - The manifest object containing the routing configuration.
 * @param targetName - The name of the target to filter pages by.
 * @returns An object containing the pages that match the target name.
 */
function getPages(manifest, targetName) {
    const routing = manifest[ux_specification_types_1.ManifestSection.ui5].routing;
    const pages = {};
    if (routing) {
        for (const id in routing.targets) {
            const v4Page = routing.targets[id];
            if ((0, common_1.compareTemplateNames)(targetName, v4Page['name'])) {
                pages[id] = v4Page;
            }
        }
    }
    return pages;
}
class V4AppProvider extends appProvider_1.AppProvider {
    /**
     *
     * @param importParameters - The parameters required for importing configurations, including manifest, schemas, and other related data.
     */
    constructor(importParameters) {
        (0, i18n_1.initI18n)();
        // Import settings
        const appSettings = new application_1.AppSettings();
        const schemaFile = (0, common_1.getSchemaFilePath)(ux_specification_types_1.SchemaType.Application);
        const appSchema = importParameters.appSchemas[schemaFile.filename];
        (0, utils_1.importSettingsOfObject)(appSettings, importParameters.manifest, appSchema.definitions.AppSettings, '', []);
        super(importParameters, getHome(importParameters.manifest[ux_specification_types_1.ManifestSection.ui5].routing), getPagesAndConfigs(importParameters), {
            ...getSettings(importParameters.manifest[ux_specification_types_1.ManifestSection.ui5].routing),
            ...appSettings
        });
        this.unhandledControllers = exports.unhandledControllers;
    }
}
exports.V4AppProvider = V4AppProvider;
//# sourceMappingURL=appProvider.js.map