"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;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ScheduleNotFoundError = exports.ScheduleAlreadyRunning = exports.ScheduleClient = void 0;
const grpc_js_1 = require("@grpc/grpc-js");
const uuid_1 = require("uuid");
const payload_search_attributes_1 = require("@temporalio/common/lib/converter/payload-search-attributes");
const interceptors_1 = require("@temporalio/common/lib/interceptors");
const internal_non_workflow_1 = require("@temporalio/common/lib/internal-non-workflow");
const internal_workflow_1 = require("@temporalio/common/lib/internal-workflow");
const proto_1 = require("@temporalio/proto");
const time_1 = require("@temporalio/common/lib/time");
const type_helpers_1 = require("@temporalio/common/lib/type-helpers");
const errors_1 = require("./errors");
const schedule_types_1 = require("./schedule-types");
const schedule_helpers_1 = require("./schedule-helpers");
const base_client_1 = require("./base-client");
const helpers_1 = require("./helpers");
function defaultScheduleClientOptions() {
    return {
        ...(0, base_client_1.defaultBaseClientOptions)(),
        interceptors: [],
    };
}
function assertRequiredScheduleOptions(opts, action) {
    const structureName = action === 'CREATE' ? 'ScheduleOptions' : 'ScheduleUpdateOptions';
    if (action === 'CREATE' && !opts.scheduleId) {
        throw new TypeError(`Missing ${structureName}.scheduleId`);
    }
    switch (opts.action.type) {
        case 'startWorkflow':
            if (!opts.action.taskQueue) {
                throw new TypeError(`Missing ${structureName}.action.taskQueue for 'startWorkflow' action`);
            }
            if (!opts.action.workflowType) {
                throw new TypeError(`Missing ${structureName}.action.workflowType for 'startWorkflow' action`);
            }
    }
}
/**
 * Client for starting Workflow executions and creating Workflow handles
 */
class ScheduleClient extends base_client_1.BaseClient {
    options;
    constructor(options) {
        super(options);
        this.options = {
            ...defaultScheduleClientOptions(),
            ...(0, internal_workflow_1.filterNullAndUndefined)(options ?? {}),
            loadedDataConverter: this.dataConverter,
        };
    }
    /**
     * Raw gRPC access to the Temporal service. Schedule-related methods are included in {@link WorkflowService}.
     *
     * **NOTE**: The namespace provided in {@link options} is **not** automatically set on requests made to the service.
     */
    get workflowService() {
        return this.connection.workflowService;
    }
    async create(options) {
        await this._createSchedule(options);
        return this.getHandle(options.scheduleId);
    }
    /**
     * Create a new Schedule.
     */
    async _createSchedule(options) {
        assertRequiredScheduleOptions(options, 'CREATE');
        const compiledOptions = (0, schedule_helpers_1.compileScheduleOptions)(options);
        const create = (0, interceptors_1.composeInterceptors)(this.options.interceptors, 'create', this._createScheduleHandler.bind(this));
        await create({
            options: compiledOptions,
            headers: {},
        });
    }
    /**
     * Create a new Schedule.
     */
    async _createScheduleHandler(input) {
        const { options: opts, headers } = input;
        const { identity } = this.options;
        const req = {
            namespace: this.options.namespace,
            identity,
            requestId: (0, uuid_1.v4)(),
            scheduleId: opts.scheduleId,
            schedule: {
                spec: (0, schedule_helpers_1.encodeScheduleSpec)(opts.spec),
                action: await (0, schedule_helpers_1.encodeScheduleAction)(this.dataConverter, opts.action, headers),
                policies: (0, schedule_helpers_1.encodeSchedulePolicies)(opts.policies),
                state: (0, schedule_helpers_1.encodeScheduleState)(opts.state),
            },
            memo: opts.memo ? { fields: await (0, internal_non_workflow_1.encodeMapToPayloads)(this.dataConverter, opts.memo) } : undefined,
            searchAttributes: opts.searchAttributes || opts.typedSearchAttributes // eslint-disable-line @typescript-eslint/no-deprecated
                ? {
                    indexedFields: (0, payload_search_attributes_1.encodeUnifiedSearchAttributes)(opts.searchAttributes, opts.typedSearchAttributes), // eslint-disable-line @typescript-eslint/no-deprecated
                }
                : undefined,
            initialPatch: {
                triggerImmediately: opts.state?.triggerImmediately
                    ? { overlapPolicy: proto_1.temporal.api.enums.v1.ScheduleOverlapPolicy.SCHEDULE_OVERLAP_POLICY_ALLOW_ALL }
                    : undefined,
                backfillRequest: opts.state?.backfill
                    ? opts.state.backfill.map((x) => ({
                        startTime: (0, time_1.optionalDateToTs)(x.start),
                        endTime: (0, time_1.optionalDateToTs)(x.end),
                        overlapPolicy: x.overlap ? (0, schedule_types_1.encodeScheduleOverlapPolicy)(x.overlap) : undefined,
                    }))
                    : undefined,
            },
        };
        try {
            const res = await this.workflowService.createSchedule(req);
            return { conflictToken: res.conflictToken };
        }
        catch (err) {
            if (err.code === grpc_js_1.status.ALREADY_EXISTS) {
                throw new ScheduleAlreadyRunning('Schedule already exists and is running', opts.scheduleId);
            }
            this.rethrowGrpcError(err, 'Failed to create schedule', opts.scheduleId);
        }
    }
    /**
     * Describe a Schedule.
     */
    async _describeSchedule(scheduleId) {
        try {
            return await this.workflowService.describeSchedule({
                namespace: this.options.namespace,
                scheduleId,
            });
        }
        catch (err) {
            this.rethrowGrpcError(err, 'Failed to describe schedule', scheduleId);
        }
    }
    /**
     * Update a Schedule.
     */
    async _updateSchedule(scheduleId, opts, header) {
        const req = {
            namespace: this.options.namespace,
            scheduleId,
            schedule: {
                spec: (0, schedule_helpers_1.encodeScheduleSpec)(opts.spec),
                action: await (0, schedule_helpers_1.encodeScheduleAction)(this.dataConverter, opts.action, header),
                policies: (0, schedule_helpers_1.encodeSchedulePolicies)(opts.policies),
                state: (0, schedule_helpers_1.encodeScheduleState)(opts.state),
            },
            identity: this.options.identity,
            requestId: (0, uuid_1.v4)(),
            searchAttributes: opts.searchAttributes || opts.typedSearchAttributes // eslint-disable-line @typescript-eslint/no-deprecated
                ? {
                    indexedFields: (0, payload_search_attributes_1.encodeUnifiedSearchAttributes)(opts.searchAttributes, opts.typedSearchAttributes), // eslint-disable-line @typescript-eslint/no-deprecated
                }
                : undefined,
        };
        try {
            return await this.workflowService.updateSchedule(req);
        }
        catch (err) {
            this.rethrowGrpcError(err, 'Failed to update schedule', scheduleId);
        }
    }
    /**
     * Patch a Schedule.
     */
    async _patchSchedule(scheduleId, patch) {
        try {
            return await this.workflowService.patchSchedule({
                namespace: this.options.namespace,
                scheduleId,
                identity: this.options.identity,
                requestId: (0, uuid_1.v4)(),
                patch,
            });
        }
        catch (err) {
            this.rethrowGrpcError(err, 'Failed to patch schedule', scheduleId);
        }
    }
    /**
     * Delete a Schedule.
     */
    async _deleteSchedule(scheduleId) {
        try {
            return await this.workflowService.deleteSchedule({
                namespace: this.options.namespace,
                identity: this.options.identity,
                scheduleId,
            });
        }
        catch (err) {
            this.rethrowGrpcError(err, 'Failed to delete schedule', scheduleId);
        }
    }
    /**
     * List Schedules with an `AsyncIterator`:
     *
     * ```ts
     * for await (const schedule: Schedule of client.list()) {
     *   const { id, memo, searchAttributes } = schedule
     *   // ...
     * }
     * ```
     *
     * To list one page at a time, instead use the raw gRPC method {@link WorkflowService.listSchedules}:
     *
     * ```ts
     * await { schedules, nextPageToken } = client.scheduleService.listSchedules()
     * ```
     */
    async *list(options) {
        let nextPageToken = undefined;
        for (;;) {
            let response;
            try {
                response = await this.workflowService.listSchedules({
                    nextPageToken,
                    namespace: this.options.namespace,
                    maximumPageSize: options?.pageSize,
                    query: options?.query,
                });
            }
            catch (e) {
                this.rethrowGrpcError(e, 'Failed to list schedules', undefined);
            }
            for (const raw of response.schedules ?? []) {
                yield {
                    scheduleId: raw.scheduleId,
                    spec: (0, schedule_helpers_1.decodeScheduleSpec)(raw.info?.spec ?? {}),
                    action: raw.info?.workflowType && {
                        type: 'startWorkflow',
                        workflowType: raw.info.workflowType.name,
                    },
                    memo: await (0, internal_non_workflow_1.decodeMapFromPayloads)(this.dataConverter, raw.memo?.fields),
                    searchAttributes: (0, payload_search_attributes_1.decodeSearchAttributes)(raw.searchAttributes?.indexedFields),
                    typedSearchAttributes: (0, payload_search_attributes_1.decodeTypedSearchAttributes)(raw.searchAttributes?.indexedFields),
                    state: {
                        paused: raw.info?.paused === true,
                        note: raw.info?.notes ?? undefined,
                    },
                    info: {
                        recentActions: (0, schedule_helpers_1.decodeScheduleRecentActions)(raw.info?.recentActions),
                        nextActionTimes: raw.info?.futureActionTimes?.map(time_1.tsToDate) ?? [],
                    },
                };
            }
            if (response.nextPageToken == null || response.nextPageToken.length === 0)
                break;
            nextPageToken = response.nextPageToken;
        }
    }
    /**
     * Get a handle to a Schedule
     *
     * This method does not validate `scheduleId`. If there is no Schedule with the given `scheduleId`, handle
     * methods like `handle.describe()` will throw a {@link ScheduleNotFoundError} error.
     */
    getHandle(scheduleId) {
        return {
            client: this,
            scheduleId,
            async describe() {
                const raw = await this.client._describeSchedule(this.scheduleId);
                if (!raw.schedule?.spec || !raw.schedule.action)
                    throw new Error('Received invalid Schedule description from server');
                return {
                    scheduleId,
                    spec: (0, schedule_helpers_1.decodeScheduleSpec)(raw.schedule.spec),
                    action: await (0, schedule_helpers_1.decodeScheduleAction)(this.client.dataConverter, raw.schedule.action),
                    memo: await (0, internal_non_workflow_1.decodeMapFromPayloads)(this.client.dataConverter, raw.memo?.fields),
                    searchAttributes: (0, payload_search_attributes_1.decodeSearchAttributes)(raw.searchAttributes?.indexedFields),
                    typedSearchAttributes: (0, payload_search_attributes_1.decodeTypedSearchAttributes)(raw.searchAttributes?.indexedFields),
                    policies: {
                        // 'overlap' should never be missing on describe, as the server will replace UNSPECIFIED by an actual value
                        overlap: (0, schedule_types_1.decodeScheduleOverlapPolicy)(raw.schedule.policies?.overlapPolicy) ?? schedule_types_1.ScheduleOverlapPolicy.SKIP,
                        catchupWindow: (0, time_1.optionalTsToMs)(raw.schedule.policies?.catchupWindow) ?? 60_000,
                        pauseOnFailure: raw.schedule.policies?.pauseOnFailure === true,
                    },
                    state: {
                        paused: raw.schedule.state?.paused === true,
                        note: raw.schedule.state?.notes ?? undefined,
                        remainingActions: raw.schedule.state?.limitedActions
                            ? raw.schedule.state?.remainingActions?.toNumber() || 0
                            : undefined,
                    },
                    info: {
                        recentActions: (0, schedule_helpers_1.decodeScheduleRecentActions)(raw.info?.recentActions),
                        nextActionTimes: raw.info?.futureActionTimes?.map(time_1.tsToDate) ?? [],
                        createdAt: (0, time_1.requiredTsToDate)(raw.info?.createTime, 'createTime'),
                        lastUpdatedAt: (0, time_1.optionalTsToDate)(raw.info?.updateTime),
                        runningActions: (0, schedule_helpers_1.decodeScheduleRunningActions)(raw.info?.runningWorkflows),
                        numActionsMissedCatchupWindow: raw.info?.missedCatchupWindow?.toNumber() ?? 0,
                        numActionsSkippedOverlap: raw.info?.overlapSkipped?.toNumber() ?? 0,
                        numActionsTaken: raw.info?.actionCount?.toNumber() ?? 0,
                    },
                    raw,
                };
            },
            async update(updateFn) {
                const current = await this.describe();
                // Keep existing headers
                const currentHeader = current.raw.schedule?.action?.startWorkflow?.header?.fields ?? {};
                const updated = updateFn(current);
                assertRequiredScheduleOptions(updated, 'UPDATE');
                await this.client._updateSchedule(scheduleId, (0, schedule_helpers_1.compileUpdatedScheduleOptions)(scheduleId, updated), currentHeader);
            },
            async delete() {
                await this.client._deleteSchedule(this.scheduleId);
            },
            async pause(note) {
                await this.client._patchSchedule(this.scheduleId, {
                    pause: note ?? 'Paused via TypeScript SDK"',
                });
            },
            async unpause(note) {
                await this.client._patchSchedule(this.scheduleId, {
                    unpause: note ?? 'Unpaused via TypeScript SDK"',
                });
            },
            async trigger(overlap) {
                await this.client._patchSchedule(this.scheduleId, {
                    triggerImmediately: {
                        overlapPolicy: overlap
                            ? (0, schedule_types_1.encodeScheduleOverlapPolicy)(overlap)
                            : proto_1.temporal.api.enums.v1.ScheduleOverlapPolicy.SCHEDULE_OVERLAP_POLICY_ALLOW_ALL,
                    },
                });
            },
            async backfill(options) {
                const backfills = Array.isArray(options) ? options : [options];
                await this.client._patchSchedule(this.scheduleId, {
                    backfillRequest: backfills.map((x) => ({
                        startTime: (0, time_1.optionalDateToTs)(x.start),
                        endTime: (0, time_1.optionalDateToTs)(x.end),
                        overlapPolicy: x.overlap ? (0, schedule_types_1.encodeScheduleOverlapPolicy)(x.overlap) : undefined,
                    })),
                });
            },
        };
    }
    rethrowGrpcError(err, fallbackMessage, scheduleId) {
        if ((0, errors_1.isGrpcServiceError)(err)) {
            (0, helpers_1.rethrowKnownErrorTypes)(err);
            if (err.code === grpc_js_1.status.NOT_FOUND) {
                throw new ScheduleNotFoundError(err.details ?? 'Schedule not found', scheduleId ?? '');
            }
            if (err.code === grpc_js_1.status.INVALID_ARGUMENT &&
                err.message.match(/^3 INVALID_ARGUMENT: Invalid schedule spec: /)) {
                throw new TypeError(err.message.replace(/^3 INVALID_ARGUMENT: Invalid schedule spec: /, ''));
            }
            throw new errors_1.ServiceError(fallbackMessage, { cause: err });
        }
        throw new errors_1.ServiceError('Unexpected error while making gRPC request', { cause: err });
    }
}
exports.ScheduleClient = ScheduleClient;
/**
 * Thrown from {@link ScheduleClient.create} if there's a running (not deleted) Schedule with the given `id`.
 */
let ScheduleAlreadyRunning = class ScheduleAlreadyRunning extends Error {
    scheduleId;
    constructor(message, scheduleId) {
        super(message);
        this.scheduleId = scheduleId;
    }
};
exports.ScheduleAlreadyRunning = ScheduleAlreadyRunning;
exports.ScheduleAlreadyRunning = ScheduleAlreadyRunning = __decorate([
    (0, type_helpers_1.SymbolBasedInstanceOfError)('ScheduleAlreadyRunning')
], ScheduleAlreadyRunning);
/**
 * Thrown when a Schedule with the given Id is not known to Temporal Server.
 * It could be because:
 * - Id passed is incorrect
 * - Schedule was deleted
 */
let ScheduleNotFoundError = class ScheduleNotFoundError extends Error {
    scheduleId;
    constructor(message, scheduleId) {
        super(message);
        this.scheduleId = scheduleId;
    }
};
exports.ScheduleNotFoundError = ScheduleNotFoundError;
exports.ScheduleNotFoundError = ScheduleNotFoundError = __decorate([
    (0, type_helpers_1.SymbolBasedInstanceOfError)('ScheduleNotFoundError')
], ScheduleNotFoundError);
//# sourceMappingURL=schedule-client.js.map