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 __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
import QueryHelper from "../Types/Database/QueryHelper";
import DatabaseService from "./DatabaseService";
import BadDataException from "../../Types/Exception/BadDataException";
import ObjectID from "../../Types/ObjectID";
import Color from "../../Types/Color";
import { BrightColors } from "../../Types/BrandColors";
import GlobalCache from "../Infrastructure/GlobalCache";
import Model from "../../Models/DatabaseModels/Label";
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
const AUTO_LABEL_ID_CACHE_NAMESPACE = "auto-label-id";
const AUTO_LABEL_ID_CACHE_TTL_SECONDS = 24 * 60 * 60;
export class Service extends DatabaseService {
    constructor() {
        super(Model);
    }
    async onBeforeCreate(createBy) {
        let projectId = createBy.props.tenantId;
        if (createBy.props.isMasterAdmin || createBy.props.isRoot) {
            if (createBy.data.projectId) {
                projectId = createBy.data.projectId;
            }
        }
        if (!projectId) {
            throw new BadDataException("Project ID is required to create a label.");
        }
        let existingProjectWithSameNameCount = 0;
        existingProjectWithSameNameCount = (await this.countBy({
            query: {
                name: QueryHelper.findWithSameText(createBy.data.name),
                projectId: projectId,
            },
            props: {
                isRoot: true,
            },
        })).toNumber();
        if (existingProjectWithSameNameCount > 0) {
            throw new BadDataException("Label with the same name already exists in this project.");
        }
        return Promise.resolve({ createBy, carryForward: null });
    }
    /**
     * Find existing labels by name (case-insensitive) or auto-create
     * them, returning the ObjectIDs in the same order as the input.
     * Used by the OTel ingest pipeline to promote
     * `oneuptime.label.<dim>=<val>` resource attributes into project
     * labels attached to the discovered host or service.
     *
     * Names are matched case-insensitively, so an OTel attribute of
     * `production` will resolve to a manually-created `Production`
     * label rather than spawning a duplicate. New labels are colored
     * deterministically from the BrightColors palette so re-creating
     * the same label across processes lands on the same color.
     *
     * Resolved IDs are cached per (project, name) for 24h, so the
     * common case (steady-state collector pushing the same labels
     * every batch) is a single in-memory lookup.
     */
    async findOrCreateLabelsByNames(data) {
        if (!data.labelNames || data.labelNames.length === 0) {
            return [];
        }
        const result = [];
        const seenInBatch = new Set();
        for (const rawName of data.labelNames) {
            const name = (rawName || "").trim();
            if (!name) {
                continue;
            }
            const dedupeKey = name.toLowerCase();
            if (seenInBatch.has(dedupeKey)) {
                continue;
            }
            seenInBatch.add(dedupeKey);
            const cacheKey = `${data.projectId.toString()}:${dedupeKey}`;
            let idStr = await GlobalCache.getString(AUTO_LABEL_ID_CACHE_NAMESPACE, cacheKey);
            if (!idStr) {
                const id = await this.findOrCreateLabelByName(data.projectId, name);
                if (!id) {
                    continue;
                }
                idStr = id.toString();
                await GlobalCache.setString(AUTO_LABEL_ID_CACHE_NAMESPACE, cacheKey, idStr, { expiresInSeconds: AUTO_LABEL_ID_CACHE_TTL_SECONDS });
            }
            result.push(new ObjectID(idStr));
        }
        return result;
    }
    async findOrCreateLabelByName(projectId, name) {
        const existing = await this.findOneBy({
            query: {
                projectId: projectId,
                name: QueryHelper.findWithSameText(name),
            },
            select: { _id: true },
            props: { isRoot: true },
        });
        if (existing === null || existing === void 0 ? void 0 : existing._id) {
            return new ObjectID(existing._id.toString());
        }
        try {
            const newLabel = new Model();
            newLabel.projectId = projectId;
            newLabel.name = name;
            newLabel.color = pickColorForLabelName(name);
            const created = await this.create({
                data: newLabel,
                props: { isRoot: true },
            });
            if (created._id) {
                return new ObjectID(created._id.toString());
            }
            return null;
        }
        catch (_a) {
            /*
             * Either two ingest workers raced to create the same label, or
             * a label with this name in a different case existed and the
             * onBeforeCreate dup-check rejected the insert. Re-resolve
             * case-insensitively so the caller still gets a valid id.
             */
            const refetched = await this.findOneBy({
                query: {
                    projectId: projectId,
                    name: QueryHelper.findWithSameText(name),
                },
                select: { _id: true },
                props: { isRoot: true },
            });
            if (refetched === null || refetched === void 0 ? void 0 : refetched._id) {
                return new ObjectID(refetched._id.toString());
            }
            return null;
        }
    }
}
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "onBeforeCreate", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "findOrCreateLabelsByNames", null);
function pickColorForLabelName(name) {
    if (BrightColors.length === 0) {
        return Color.fromString("#3686be");
    }
    let hash = 0;
    for (let i = 0; i < name.length; i++) {
        hash = (hash * 31 + name.charCodeAt(i)) | 0;
    }
    const idx = Math.abs(hash) % BrightColors.length;
    return BrightColors[idx];
}
export default new Service();
//# sourceMappingURL=LabelService.js.map