// (C) 2021-2026 GoodData Corporation
import { createSelector } from "@reduxjs/toolkit";
import { isEmpty, negate } from "lodash-es";
import { areObjRefsEqual, getHierarchyAttributes, getHierarchyRef, idRef, objRefToString, } from "@gooddata/sdk-model";
import { newCatalogDateAttributeWithDatasetMap, } from "../../../_staging/catalog/dateAttributeWithDatasetMap.js";
import { createDisplayFormMap } from "../../../_staging/catalog/displayFormMap.js";
import { newCatalogAttributeMap, newCatalogDateDatasetMap, newCatalogMeasureMap, } from "../../../_staging/metadata/objRefMap.js";
import { createMemoizedSelector } from "../_infra/selectors.js";
import { selectBackendCapabilities, selectSupportsAttributeHierarchies, } from "../backendCapabilities/backendCapabilitiesSelectors.js";
const selectSelf = createSelector((state) => state, (state) => state.catalog);
/**
 * @public
 */
export const selectCatalogAttributes = createSelector(selectSelf, (state) => {
    return state.attributes ?? [];
});
/**
 * Returns the workspace parameters loaded into the dashboard catalog.
 *
 * @alpha
 */
export const selectCatalogParameters = createSelector(selectSelf, (state) => state.parameters.parameters);
/**
 * Returns a selector that yields the workspace parameter for a given ref, or `undefined`
 * if the catalog does not contain it.
 *
 * @alpha
 */
export const selectCatalogParameterByRef = createMemoizedSelector((ref) => createSelector(selectCatalogParameters, (parameters) => parameters.find((parameter) => areObjRefsEqual(parameter.ref, ref))));
/**
 * Returns the load status of catalog parameters.
 *
 * @alpha
 */
export const selectCatalogParametersStatus = createSelector(selectSelf, (state) => state.parameters.status);
/**
 * Returns true once the catalog parameters loader has succeeded.
 *
 * @alpha
 */
export const selectCatalogParametersIsLoaded = createSelector(selectCatalogParametersStatus, (status) => status === "loaded");
/**
 * Returns the dashboard-wide map from metric ref string to the parameter refs the metric depends on.
 * The map is populated during dashboard initialization from the workspace references service.
 *
 * @alpha
 */
export const selectCatalogMeasureParameters = createSelector(selectSelf, (state) => state.measureParameters.byMetric);
/**
 * Returns the load status of the dashboard-wide metric → parameter dependency map.
 *
 * @alpha
 */
export const selectCatalogMeasureParametersStatus = createSelector(selectSelf, (state) => state.measureParameters.status);
/**
 * @alpha
 */
export const selectHasCatalogAttributes = createSelector(selectCatalogAttributes, negate(isEmpty));
/**
 * @public
 */
export const selectCatalogAttributeDisplayForms = createSelector(selectCatalogAttributes, (attributes) => {
    return attributes.flatMap((attribute) => [
        ...attribute.displayForms,
        ...attribute.geoPinDisplayForms,
    ]);
});
/**
 * @public
 */
export const selectCatalogAttributeDisplayFormsById = createSelector(selectCatalogAttributeDisplayForms, (displayForms) => {
    return displayForms.reduce((acc, displayForm) => {
        acc[displayForm.id] = displayForm;
        return acc;
    }, {});
});
/**
 * @public
 */
export const selectCatalogMeasures = createSelector(selectSelf, (state) => {
    return state.measures ?? [];
});
/**
 * @alpha
 */
export const selectHasCatalogMeasures = createSelector(selectCatalogMeasures, negate(isEmpty));
/**
 * @public
 */
export const selectCatalogFacts = createSelector(selectSelf, (state) => {
    return state.facts ?? [];
});
/**
 * @alpha
 */
export const selectHasCatalogFacts = createSelector(selectCatalogFacts, negate(isEmpty));
/**
 * @public
 */
export const selectCatalogDateDatasets = createSelector(selectSelf, (state) => {
    return state.dateDatasets ?? [];
});
/**
 * @alpha
 */
export const selectHasCatalogDateDatasets = createSelector(selectCatalogDateDatasets, negate(isEmpty));
/**
 * @alpha
 */
export const selectCatalogIsLoaded = createSelector(selectSelf, (state) => {
    return [
        state.attributes,
        state.measures,
        state.dateDatasets,
        state.facts,
        state.attributeHierarchies,
        state.dateHierarchyTemplates,
    ].every((item) => item !== undefined);
});
/**
 * @public
 */
export const selectCatalogDateAttributes = createSelector(selectCatalogDateDatasets, (dateDatasets) => {
    return dateDatasets.flatMap((dd) => dd.dateAttributes);
});
/**
 * @beta
 */
export const selectCatalogAttributeHierarchies = createSelector(selectSelf, (state) => {
    return state.attributeHierarchies ?? [];
});
/**
 * @alpha
 */
export const selectDateHierarchyTemplates = createSelector(selectSelf, (state) => {
    return state.dateHierarchyTemplates ?? [];
});
/**
 * @alpha
 */
export const selectAdhocDateHierarchies = createSelector([selectDateHierarchyTemplates, selectCatalogDateDatasets, selectSupportsAttributeHierarchies], (dateHierarchyTemplates, catalogDateDatasets, isSupportAttributeHierarchies) => {
    if (isSupportAttributeHierarchies) {
        return buildAdhocDateHierarchies(dateHierarchyTemplates, catalogDateDatasets);
    }
    return [];
});
/**
 * @alpha
 */
export const selectAllCatalogAttributeHierarchies = createSelector([selectCatalogAttributeHierarchies, selectAdhocDateHierarchies], (catalogAttributeHierarchies, adhocDateHierarchies) => {
    return [...catalogAttributeHierarchies, ...adhocDateHierarchies];
});
/**
 * @alpha
 */
export const selectAttributesWithDrillDown = createSelector([selectCatalogAttributes, selectCatalogDateAttributes], (attributes = [], dateAttributes = []) => {
    return [...attributes, ...dateAttributes].filter((attr) => attr.attribute.drillDownStep);
});
/**
 * @alpha
 */
export const selectAttributesWithHierarchyDescendants = createSelector([selectCatalogAttributes, selectCatalogDateAttributes, selectAllCatalogAttributeHierarchies], (attributes = [], dateAttributes = [], attributeHierarchies = []) => {
    return getAttributesWithHierarchyDescendants(attributes, dateAttributes, attributeHierarchies);
});
/**
 * @internal
 */
export const selectAttributesWithDisplayFormLink = createSelector([selectCatalogAttributes], (attributes = []) => {
    return attributes.filter((attr) => attr.attribute.drillToAttributeLink);
});
/**
 * Selects all date datasets in the catalog as a mapping of obj ref to date dataset.
 *
 * @alpha
 */
export const selectAllCatalogDateDatasetsMap = createSelector([selectCatalogDateDatasets, selectBackendCapabilities], (dateDatasets, capabilities) => {
    return newCatalogDateDatasetMap(dateDatasets, capabilities.hasTypeScopedIdentifiers);
});
/**
 * Selects all display forms in the catalog as a mapping of obj ref to display form
 *
 * @alpha
 */
export const selectAllCatalogDisplayFormsMap = createSelector([selectCatalogAttributes, selectCatalogDateDatasets, selectBackendCapabilities], (attributes, dateDatasets, capabilities) => {
    return createDisplayFormMap(attributes, dateDatasets, capabilities.hasTypeScopedIdentifiers);
});
/**
 * Selects all attributes in the catalog as a mapping of ref to catalog's attribute object. The mapping
 * will include both 'normal' attributes and attributes from date datasets.
 *
 * @remarks see `isCatalogAttribute` guard; this can be used to determine type of attribute
 * @alpha
 */
export const selectAllCatalogAttributesMap = createSelector([selectCatalogAttributes, selectCatalogDateDatasets, selectBackendCapabilities], (attributes, dateDatasets, capabilities) => {
    const dateAttributes = dateDatasets.flatMap((d) => d.dateAttributes);
    return newCatalogAttributeMap([...attributes, ...dateAttributes], capabilities.hasTypeScopedIdentifiers);
});
/**
 * Selects all measures in the catalog as a mapping of ref to catalog's measure object.
 *
 * @alpha
 */
export const selectAllCatalogMeasuresMap = createSelector([selectCatalogMeasures, selectBackendCapabilities], (measures, capabilities) => {
    return newCatalogMeasureMap(measures, capabilities.hasTypeScopedIdentifiers);
});
/**
 * Selects lookup mapping between date dataset attributes and date datasets. The entry in lookup contains both the date dataset attribute
 * and the date dataset to which it belongs. The lookup is indexed by the date dataset attribute and entries can be obtained using
 * attribute refs.
 *
 * @alpha
 */
export const selectCatalogDateAttributeToDataset = createSelector([selectCatalogDateDatasets, selectBackendCapabilities], (dateDatasets, capabilities) => {
    const attributesWithDatasets = dateDatasets.flatMap((dataset) => dataset.dateAttributes.map((attribute) => {
        return {
            attribute,
            dataset,
        };
    }));
    return newCatalogDateAttributeWithDatasetMap(attributesWithDatasets, capabilities.hasTypeScopedIdentifiers);
});
//
function getAttributesWithHierarchyDescendants(attributes, dateAttributes, attributeHierarchies) {
    const allCatalogAttributes = [...attributes, ...dateAttributes];
    const attributeDescendants = {};
    allCatalogAttributes.forEach((attribute) => {
        const attributeRef = attribute.attribute.ref;
        attributeHierarchies.forEach((hierarchy) => {
            const hierarchyRef = getHierarchyRef(hierarchy);
            const attributes = getHierarchyAttributes(hierarchy);
            const foundAttributeIndex = attributes.findIndex((ref) => areObjRefsEqual(ref, attributeRef));
            if (foundAttributeIndex < 0) {
                return;
            }
            const foundDescendant = attributes[foundAttributeIndex + 1];
            if (!foundDescendant) {
                return;
            }
            const attributeRefAsString = objRefToString(attributeRef);
            const descendantWithHierarchy = {
                hierarchyRef,
                descendantRef: foundDescendant,
            };
            if (attributeDescendants[attributeRefAsString]) {
                attributeDescendants[attributeRefAsString].push(descendantWithHierarchy);
            }
            else {
                attributeDescendants[attributeRefAsString] = [descendantWithHierarchy];
            }
        });
    });
    return attributeDescendants;
}
function buildAdhocDateHierarchies(dateHierarchyTemplates, catalogDateDatasets) {
    const adhocDateHierarchies = [];
    catalogDateDatasets.forEach((dateDataset) => {
        dateHierarchyTemplates.forEach((template) => {
            const dateHierarchy = buildDateHierarchy(dateDataset, template);
            if (dateHierarchy) {
                adhocDateHierarchies.push(dateHierarchy);
            }
        });
    });
    return adhocDateHierarchies;
}
function buildDateHierarchy(dateDataset, dateTemplate) {
    const dateAttributes = dateDataset.dateAttributes;
    const dateAttributesInHierarchy = [];
    dateTemplate.granularities.forEach((templateGranularity) => {
        const dateAttribute = dateAttributes.find((attr) => attr.granularity === templateGranularity);
        if (dateAttribute) {
            dateAttributesInHierarchy.push(dateAttribute.attribute.ref);
        }
    });
    if (!isEmpty(dateAttributesInHierarchy)) {
        return {
            type: "dateAttributeHierarchy",
            // create ref of adhoc date hierarchy by combining date dataset id and date template id
            // this ref should not be stored on MD server as it is not existing MD object
            ref: idRef(`${dateTemplate.id}/${objRefToString(dateDataset.dataSet.ref)}`, "dateAttributeHierarchy"),
            templateId: dateTemplate.id,
            dateDatasetRef: dateDataset.dataSet.ref,
            title: dateDataset.dataSet.title,
            attributes: dateAttributesInHierarchy,
        };
    }
    return undefined;
}
