// The MIT License
//
// Copyright (c) 2020 Temporal Technologies Inc.  All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

// (-- api-linter: core::0203::optional=disabled
//     aip.dev/not-precedent: field_behavior annotation not available in our gogo fork --)
// (-- api-linter: core::0203::input-only=disabled
//     aip.dev/not-precedent: field_behavior annotation not available in our gogo fork --)

syntax = "proto3";

package temporal.api.schedule.v1;

option go_package = "go.temporal.io/api/schedule/v1;schedule";
option java_package = "io.temporal.api.schedule.v1";
option java_multiple_files = true;
option java_outer_classname = "MessageProto";
option ruby_package = "Temporalio::Api::Schedule::V1";
option csharp_namespace = "Temporalio.Api.Schedule.V1";

import "google/protobuf/duration.proto";
import "google/protobuf/timestamp.proto";

import "temporal/api/common/v1/message.proto";
import "temporal/api/enums/v1/schedule.proto";
import "temporal/api/workflow/v1/message.proto";

// CalendarSpec describes an event specification relative to the calendar,
// similar to a traditional cron specification, but with labeled fields. Each
// field can be one of:
//   *: matches always
//   x: matches when the field equals x
//   x/y : matches when the field equals x+n*y where n is an integer
//   x-z: matches when the field is between x and z inclusive
//   w,x,y,...: matches when the field is one of the listed values
// Each x, y, z, ... is either a decimal integer, or a month or day of week name
// or abbreviation (in the appropriate fields).
// A timestamp matches if all fields match.
// Note that fields have different default values, for convenience.
// Note that the special case that some cron implementations have for treating
// day_of_month and day_of_week as "or" instead of "and" when both are set is
// not implemented.
// day_of_week can accept 0 or 7 as Sunday
// CalendarSpec gets compiled into StructuredCalendarSpec, which is what will be
// returned if you describe the schedule.
message CalendarSpec {
    // Expression to match seconds. Default: 0
    string second = 1;
    // Expression to match minutes. Default: 0
    string minute = 2;
    // Expression to match hours. Default: 0
    string hour = 3;
    // Expression to match days of the month. Default: *
    // (-- api-linter: core::0140::prepositions=disabled
    //     aip.dev/not-precedent: standard name of field --)
    string day_of_month = 4;
    // Expression to match months. Default: *
    string month = 5;
    // Expression to match years. Default: *
    string year = 6;
    // Expression to match days of the week. Default: *
    string day_of_week = 7;
    // Free-form comment describing the intention of this spec.
    string comment = 8;
}

// Range represents a set of integer values, used to match fields of a calendar
// time in StructuredCalendarSpec. If end < start, then end is interpreted as
// equal to start. This means you can use a Range with start set to a value, and
// end and step unset (defaulting to 0) to represent a single value.
message Range {
    // Start of range (inclusive).
    int32 start = 1;
    // End of range (inclusive).
    int32 end = 2;
    // Step (optional, default 1).
    int32 step = 3;
}

// StructuredCalendarSpec describes an event specification relative to the
// calendar, in a form that's easy to work with programmatically. Each field can
// be one or more ranges.
// A timestamp matches if at least one range of each field matches the
// corresponding fields of the timestamp, except for year: if year is missing,
// that means all years match. For all fields besides year, at least one Range
// must be present to match anything.
// TODO: add relative-to-end-of-month
// TODO: add nth day-of-week in month
message StructuredCalendarSpec {
    // Match seconds (0-59)
    repeated Range second = 1;
    // Match minutes (0-59)
    repeated Range minute = 2;
    // Match hours (0-23)
    repeated Range hour = 3;
    // Match days of the month (1-31)
    // (-- api-linter: core::0140::prepositions=disabled
    //     aip.dev/not-precedent: standard name of field --)
    repeated Range day_of_month = 4;
    // Match months (1-12)
    repeated Range month = 5;
    // Match years.
    repeated Range year = 6;
    // Match days of the week (0-6; 0 is Sunday).
    repeated Range day_of_week = 7;
    // Free-form comment describing the intention of this spec.
    string comment = 8;
}

// IntervalSpec matches times that can be expressed as:
// epoch + n * interval + phase
// where n is an integer.
// phase defaults to zero if missing. interval is required.
// Both interval and phase must be non-negative and are truncated to the nearest
// second before any calculations.
// For example, an interval of 1 hour with phase of zero would match every hour,
// on the hour. The same interval but a phase of 19 minutes would match every
// xx:19:00. An interval of 28 days with phase zero would match
// 2022-02-17T00:00:00Z (among other times). The same interval with a phase of 3
// days, 5 hours, and 23 minutes would match 2022-02-20T05:23:00Z instead.
message IntervalSpec {
    google.protobuf.Duration interval = 1;
    google.protobuf.Duration phase = 2;
}

// ScheduleSpec is a complete description of a set of absolute timestamps
// (possibly infinite) that an action should occur at. The meaning of a
// ScheduleSpec depends only on its contents and never changes, except that the
// definition of a time zone can change over time (most commonly, when daylight
// saving time policy changes for an area). To create a totally self-contained
// ScheduleSpec, use UTC or include timezone_data.
//
// For input, you can provide zero or more of: structured_calendar, calendar,
// cron_string, interval, and exclude_structured_calendar, and all of them will
// be used (the schedule will take action at the union of all of their times,
// minus the ones that match exclude_structured_calendar).
//
// On input, calendar and cron_string fields will be compiled into
// structured_calendar (and maybe interval and timezone_name), so if you
// Describe a schedule, you'll see only structured_calendar, interval, etc.
//
// If a spec has no matching times after the current time, then the schedule
// will be subject to automatic deletion (after several days).
message ScheduleSpec {
    // Calendar-based specifications of times.
    repeated StructuredCalendarSpec structured_calendar = 7;
    // cron_string holds a traditional cron specification as a string. It
    // accepts 5, 6, or 7 fields, separated by spaces, and interprets them the
    // same way as CalendarSpec.
    // 5 fields:         minute, hour, day_of_month, month, day_of_week
    // 6 fields:         minute, hour, day_of_month, month, day_of_week, year
    // 7 fields: second, minute, hour, day_of_month, month, day_of_week, year
    // If year is not given, it defaults to *. If second is not given, it
    // defaults to 0.
    // Shorthands @yearly, @monthly, @weekly, @daily, and @hourly are also
    // accepted instead of the 5-7 time fields.
    // Optionally, the string can be preceded by CRON_TZ=<timezone name> or
    // TZ=<timezone name>, which will get copied to timezone_name. (There must
    // not also be a timezone_name present.)
    // Optionally "#" followed by a comment can appear at the end of the string.
    // Note that the special case that some cron implementations have for
    // treating day_of_month and day_of_week as "or" instead of "and" when both
    // are set is not implemented.
    // @every <interval>[/<phase>] is accepted and gets compiled into an
    // IntervalSpec instead. <interval> and <phase> should be a decimal integer
    // with a unit suffix s, m, h, or d.
    repeated string cron_string = 8;
    // Calendar-based specifications of times.
    repeated CalendarSpec calendar = 1;
    // Interval-based specifications of times.
    repeated IntervalSpec interval = 2;
    // Any timestamps matching any of exclude_* will be skipped.
    repeated CalendarSpec exclude_calendar = 3 [deprecated = true]; // use exclude_structured_calendar
    repeated StructuredCalendarSpec exclude_structured_calendar = 9;
    // If start_time is set, any timestamps before start_time will be skipped.
    // (Together, start_time and end_time make an inclusive interval.)
    google.protobuf.Timestamp start_time = 4;
    // If end_time is set, any timestamps after end_time will be skipped.
    google.protobuf.Timestamp end_time = 5;
    // All timestamps will be incremented by a random value from 0 to this
    // amount of jitter. Default: 0
    google.protobuf.Duration jitter = 6;

    // Time zone to interpret all calendar-based specs in.
    //
    // If unset, defaults to UTC. We recommend using UTC for your application if
    // at all possible, to avoid various surprising properties of time zones.
    //
    // Time zones may be provided by name, corresponding to names in the IANA
    // time zone database (see https://www.iana.org/time-zones). The definition
    // will be loaded by the Temporal server from the environment it runs in.
    //
    // If your application requires more control over the time zone definition
    // used, it may pass in a complete definition in the form of a TZif file
    // from the time zone database. If present, this will be used instead of
    // loading anything from the environment. You are then responsible for
    // updating timezone_data when the definition changes.
    //
    // Calendar spec matching is based on literal matching of the clock time
    // with no special handling of DST: if you write a calendar spec that fires
    // at 2:30am and specify a time zone that follows DST, that action will not
    // be triggered on the day that has no 2:30am. Similarly, an action that
    // fires at 1:30am will be triggered twice on the day that has two 1:30s.
    //
    // Also note that no actions are taken on leap-seconds (e.g. 23:59:60 UTC).
    string timezone_name = 10;
    bytes timezone_data = 11;
}

message SchedulePolicies {
    // Policy for overlaps.
    // Note that this can be changed after a schedule has taken some actions,
    // and some changes might produce unintuitive results. In general, the later
    // policy overrides the earlier policy.
    temporal.api.enums.v1.ScheduleOverlapPolicy overlap_policy = 1;

    // Policy for catchups:
    // If the Temporal server misses an action due to one or more components
    // being down, and comes back up, the action will be run if the scheduled
    // time is within this window from the current time.
    // This value defaults to one year, and can't be less than 10 seconds.
    google.protobuf.Duration catchup_window = 2;

    // If true, and a workflow run fails or times out, turn on "paused".
    // This applies after retry policies: the full chain of retries must fail to
    // trigger a pause here.
    bool pause_on_failure = 3;

    // If true, and the action would start a workflow, a timestamp will not be
    // appended to the scheduled workflow id.
    bool keep_original_workflow_id = 4;
}

message ScheduleAction {
    oneof action {
        // All fields of NewWorkflowExecutionInfo are valid except for:
        // - workflow_id_reuse_policy
        // - cron_schedule
        // The workflow id of the started workflow may not match this exactly,
        // it may have a timestamp appended for uniqueness.
        temporal.api.workflow.v1.NewWorkflowExecutionInfo start_workflow = 1;
    }
}

message ScheduleActionResult {
    // Time that the action was taken (according to the schedule, including jitter).
    google.protobuf.Timestamp schedule_time = 1;

    // Time that the action was taken (real time).
    google.protobuf.Timestamp actual_time = 2;

    // If action was start_workflow:
    temporal.api.common.v1.WorkflowExecution start_workflow_result = 11;
}

message ScheduleState {
    // Informative human-readable message with contextual notes, e.g. the reason
    // a schedule is paused. The system may overwrite this message on certain
    // conditions, e.g. when pause-on-failure happens.
    string notes = 1;

    // If true, do not take any actions based on the schedule spec.
    bool paused = 2;

    // If limited_actions is true, decrement remaining_actions after each
    // action, and do not take any more scheduled actions if remaining_actions
    // is zero. Actions may still be taken by explicit request (i.e. trigger
    // immediately or backfill). Skipped actions (due to overlap policy) do not
    // count against remaining actions.
    // If a schedule has no more remaining actions, then the schedule will be
    // subject to automatic deletion (after several days).
    bool limited_actions = 3;
    int64 remaining_actions = 4;
}

message TriggerImmediatelyRequest {
    // If set, override overlap policy for this one request.
    temporal.api.enums.v1.ScheduleOverlapPolicy overlap_policy = 1;
}

message BackfillRequest {
    // Time range to evaluate schedule in. Currently, this time range is
    // exclusive on start_time and inclusive on end_time. (This is admittedly
    // counterintuitive and it may change in the future, so to be safe, use a
    // start time strictly before a scheduled time.) Also note that an action
    // nominally scheduled in the interval but with jitter that pushes it after
    // end_time will not be included.
    google.protobuf.Timestamp start_time = 1;
    google.protobuf.Timestamp end_time = 2;
    // If set, override overlap policy for this request.
    temporal.api.enums.v1.ScheduleOverlapPolicy overlap_policy = 3;
}

message SchedulePatch {
    // If set, trigger one action immediately.
    TriggerImmediatelyRequest trigger_immediately = 1;

    // If set, runs though the specified time period(s) and takes actions as if that time
    // passed by right now, all at once. The overlap policy can be overridden for the
    // scope of the backfill.
    repeated BackfillRequest backfill_request = 2;

    // If set, change the state to paused or unpaused (respectively) and set the
    // notes field to the value of the string.
    string pause = 3;
    string unpause = 4;
}

message ScheduleInfo {
    // Number of actions taken so far.
    int64 action_count = 1;

    // Number of times a scheduled action was skipped due to missing the catchup window.
    int64 missed_catchup_window = 2;

    // Number of skipped actions due to overlap.
    int64 overlap_skipped = 3;

    // Number of dropped actions due to buffer limit.
    int64 buffer_dropped = 10;

    // Number of actions in the buffer. The buffer holds the actions that cannot
    // be immediately triggered (due to the overlap policy). These actions can be a result of
    // the normal schedule or a backfill.
    int64 buffer_size = 11;

    // Currently-running workflows started by this schedule. (There might be
    // more than one if the overlap policy allows overlaps.)
    // Note that the run_ids in here are the original execution run ids as
    // started by the schedule. If the workflows retried, did continue-as-new,
    // or were reset, they might still be running but with a different run_id.
    repeated temporal.api.common.v1.WorkflowExecution running_workflows = 9;

    // Most recent ten actual action times (including manual triggers).
    repeated ScheduleActionResult recent_actions = 4;

    // Next ten scheduled action times.
    repeated google.protobuf.Timestamp future_action_times = 5;

    // Timestamps of schedule creation and last update.
    google.protobuf.Timestamp create_time = 6;
    google.protobuf.Timestamp update_time = 7;

    string invalid_schedule_error = 8 [deprecated = true];
}

message Schedule {
    ScheduleSpec spec = 1;
    ScheduleAction action = 2;
    SchedulePolicies policies = 3;
    ScheduleState state = 4;
}

// ScheduleListInfo is an abbreviated set of values from Schedule and ScheduleInfo
// that's returned in ListSchedules.
message ScheduleListInfo {
    // From spec:
    // Some fields are dropped from this copy of spec: timezone_data
    ScheduleSpec spec = 1;

    // From action:
    // Action is a oneof field, but we need to encode this in JSON and oneof fields don't work
    // well with JSON. If action is start_workflow, this is set:
    temporal.api.common.v1.WorkflowType workflow_type = 2;

    // From state:
    string notes = 3;
    bool paused = 4;

    // From info (maybe fewer entries):
    repeated ScheduleActionResult recent_actions = 5;
    repeated google.protobuf.Timestamp future_action_times = 6;
}

// ScheduleListEntry is returned by ListSchedules.
message ScheduleListEntry {
    string schedule_id = 1;
    temporal.api.common.v1.Memo memo = 2;
    temporal.api.common.v1.SearchAttributes search_attributes = 3;
    ScheduleListInfo info = 4;
}
