// Copyright 2025 MOIA GmbH

syntax = "proto3";

package moia.trip.feedback.v1;

option go_package = "./feedbackv1";
option java_multiple_files = false;
option java_outer_classname = "FeedbackServiceV1";
option java_package = "io.moia.protos.trip.feedback.v1";

// A service to provide customer feedback.
service FeedbackService {
  // Lists the pending feedback forms to be answered by a customer.
  // Feedback entry-points are agreed upon and configured before integration and may include after trip, customer cancelled the trip and vehicle service cancelled.
  // May be called at the end of a trip and on app start.
  //
  // This is a customer-scoped endpoint.
  // The ID of the customer on behalf of whom the operation is requested needs to be provided in the request header (`“Customer-Id: <your-customer-id>”`).
  //
  // Fails with `PERMISSION_DENIED` with the following reasons:
  // * `CUSTOMER_ID_MISSING` if the customer ID is not present in the request header.
  // * `CUSTOMER_UNAUTHENTICATED` if the customer does not exist.
  //
  // Fails with `INVALID_ARGUMENT` with the following reasons:
  // * `INVALID_PAGE_SIZE` if the `page_size` is less than zero.
  // * `INVALID_PAGE_TOKEN` if the `page_token` does not match previous requests.
  rpc ListPendingCustomerFeedbackForms(ListPendingCustomerFeedbackFormsRequest) returns (ListPendingCustomerFeedbackFormsResponse);
  // Submits a feedback on behalf of a customer.
  //
  // This is a customer-scoped endpoint.
  // The ID of the customer on behalf of whom the operation is requested needs to be provided in the request header (`“Customer-Id: <your-customer-id>”`).
  //
  // Fails with `PERMISSION_DENIED` with the following reasons:
  // * `CUSTOMER_ID_MISSING` if the customer ID is not present in the request header.
  // * `CUSTOMER_UNAUTHENTICATED` if the customer does not exist.
  //
  // Fails with `INVALID_ARGUMENT` with the following reasons:
  // * `FEEDBACK_ID_MISSING` if the feedback ID is not present in the request.
  // * `FEEDBACK_ID_INVALID` if the feedback does not belong to the provided customer or does not exist.
  // * `ANSWERS_EMPTY` if the list of answers in the request is empty.
  // * `QUESTION_ID_MISSING` if the answer does not contain a question ID.
  // * `ANSWER_INVALID` if the answer is invalid. For example submitting an answer type that does not match the referenced question's type, or a skip answer for a mandatory question.
  //
  // Fails with `ALREADY_EXISTS` if the submitted customer feedback has already been answered.
  rpc SubmitCustomerFeedback(SubmitCustomerFeedbackRequest) returns (SubmitCustomerFeedbackResponse);
}

// Request a list of pending feedback forms to be answered by a given customer.
message ListPendingCustomerFeedbackFormsRequest {
  // The maximum number of customer feedback forms to return.
  // The service may return fewer than this value.
  // If unspecified or set to zero, at most 50 customer feedback forms will be returned.
  // Negative values won't be accepted.
  // The maximum value is 1000; values above 1000 will be coerced to 1000.
  optional int32 page_size = 1;
  // A page token, received from a previous `ListPendingCustomerFeedbackForms` call.
  // Provide this to retrieve the subsequent page.
  // When paginating, all other parameters provided to `ListPendingCustomerFeedbackForms` must match
  // the call that provided the page token.
  optional string page_token = 2;
}

// Returns the pending feedback forms for a given customer.
message ListPendingCustomerFeedbackFormsResponse {
  // The list of feedback forms for the given customer.
  // Contains all feedback forms that were not answered yet.
  // If empty, there are no feedback forms that need to be answered by the customer.
  repeated FeedbackForm feedback_forms = 1;
  // A token, which can be sent as `page_token` to retrieve the next page.
  // If this field is omitted, there are no subsequent pages.
  optional string next_page_token = 2;
}

// A request to submit feedback.
// Must contain answers to all questions that were provided as part of the feedback form.
// Call ListPendingCustomerFeedbackFormsResponse to get the feedback forms to be answered.
message SubmitCustomerFeedbackRequest {
  // The unique ID of the feedback being submitted.
  string feedback_id = 1;
  // The answers to the feedback form.
  // Must not be empty.
  // Must contain answers to all questions returned by `ListPendingCustomerFeedbackForms`.
  // Each question must have a valid answer or explicitly marked as skipped with the corresponding reason.
  repeated Answer answers = 2;
}

// A response indicating that the feedback was successfully submitted.
message SubmitCustomerFeedbackResponse {}

// A feedback form.
message FeedbackForm {
  // The unique ID of the feedback.
  // Should be used to submit the answers to the feedback form.
  string feedback_id = 1;
  // The list of questions of the feedback.
  // Must not be empty.
  repeated Question questions = 2;
}

// A question to show to the customer. The question may have specific attributes depending on the type.
message Question {
  // The ID of the question.
  string id = 1;

  // The title of the question, provided in different languages.
  // The key is an IETF BCP-47 language tag: https://en.wikipedia.org/wiki/IETF_language_tag.
  // For example 'en' or 'de'.
  // The value is the localized string.
  map<string, string> title = 2;

  // The optional description of the question, containing clarifying information, provided in different languages.
  // The key is an IETF BCP-47 language tag: https://en.wikipedia.org/wiki/IETF_language_tag.
  // For example 'en' or 'de'.
  // The value is the localized string.
  map<string, string> description = 3;

  // Whether the question is mandatory.
  // A mandatory question cannot be skipped by the customer.
  bool mandatory = 4;

  // The type of question, which may contain custom attributes.
  Type type = 5;

  // A condition that has to be fulfilled (i.e., should evaluate to true) for this question to be shown to the customer.
  // If no condition is provided, this question is always shown.
  optional Condition condition = 6;

  // A wrapper of question types which contain custom attributes.
  message Type {
    // The type of the question.
    oneof value {
      // A rating question.
      Rating rating_question = 1;
      // A five-point rating question.
      FivePointRating five_point_rating_question = 2;
      // A select question.
      Select select_question = 3;
      // An open text question.
      OpenText open_text_question = 4;
    }

    // A question offering a list of rating options, of which only one can be selected.
    message Rating {
      // All possible options to select from. Must not be empty.
      repeated RatingOption options = 1;
    }

    // A question offering exactly five rating options, of which only one can be selected.
    message FivePointRating {
      // The lowest rating option.
      RatingOption lowest = 1;
      // The second-lowest rating option.
      RatingOption low = 2;
      // The neutral rating option.
      RatingOption neutral = 3;
      // The second-highest rating option.
      RatingOption high = 4;
      // The highest rating option.
      RatingOption highest = 5;
    }

    // An option of a rating question.
    message RatingOption {
      // The label of the option in all supported languages.
      // The key is an IETF BCP-47 language tag: https://en.wikipedia.org/wiki/IETF_language_tag.
      // For example 'en' or 'de'.
      // The value is the localized string.
      map<string, string> label = 1;
      // The score value of the option.
      // Must be greater than zero.
      int32 score = 2;
    }

    // A select from options question.
    // Can be configured for single or multiple selection.
    message Select {
      // All possible options to select from. Must not be empty.
      repeated SelectOption options = 1;
      // Whether or not multiple selection is possible.
      // If not set, the default is false.
      bool multiple_selection = 2;
    }

    // A select option with a string value.
    message SelectOption {
      // The label of the option in all supported languages.
      map<string, string> label = 1;
      // The value of the option, e.g. "OPTION_ONE".
      string value = 2;
    }

    // An open text question.
    message OpenText {
      // Placeholder string of the text field, provided in different languages.
      // The key is an IETF BCP-47 language tag: https://en.wikipedia.org/wiki/IETF_language_tag.
      // For example 'en' or 'de'.
      // The value is the localized string.
      map<string, string> placeholder = 1;
    }
  }

  // A condition that the integrator evaluates when deciding whether or not to show the question to the customer.
  // If the condition evaluates to true, the question that contains it is shown to the customer.
  // If the condition evaluates to false, the question that contains it must not be shown to the customer and the answer is set to `Skip` with the reason `REASON_CONDITION_NOT_MET`.
  message Condition {
    // The type of condition.
    oneof type {
      // A condition checking if an answer is within a numeric range.
      AnswerInRange answer_in_range_condition = 1;
      // A condition checking if an answer exists.
      AnswerExists answer_exists_condition = 2;
      // A condition checking if a specific value is contained in the answer.
      AnswerContains answer_contains_condition = 3;
    }

    // A condition that evaluates to true when the answer of the referenced question is larger than or equal to the lower bound
    // and smaller than or equal to the upper bound.
    // References a previous question in the feedback form in order to evaluate its answer.
    // Must only reference questions that may be answered with numeric values, i.e., `RatingOption`.
    message AnswerInRange {
      // The ID of the referenced question.
      string question_id = 1;
      // The lower bound of the range.
      int32 lower_bound = 2;
      // The upper bound of the range. Must be larger than or equal to the lower bound.
      int32 upper_bound = 3;
    }

    // A condition that evaluates to true when the customer was shown the referenced question and an answer (including `Skip` with reason `REASON_SKIPPED_BY_CUSTOMER` and excluding `Skip` with any other reason) was recorded.
    // References a previous question in the feedback form in order to evaluate its answer.
    // May reference any question type.
    message AnswerExists {
      // The ID of the referenced question.
      string question_id = 1;
    }

    // A condition that evaluates to true when the answer to the referenced question contains the specified value.
    // References a previous question in the feedback form in order to evaluate its answer.
    // Must only reference questions that may be answered with select option values, i.e., `SelectOption`.
    message AnswerContains {
      // The ID of the referenced question.
      string question_id = 1;
      // The value which should be contained in the answer.
      string value = 2;
    }
  }
}

// An answer to a question.
message Answer {
  // The ID of the question to which this answer belongs.
  string question_id = 1;
  // The type of answer. Not all answer types are valid for all questions.
  Type type = 2;

  // A wrapper of answer types which contain custom attributes.
  message Type {
    // The type of the answer.
    oneof value {
      // The question was skipped for a given reason.
      Skip skip_answer = 1;
      // The answer to a rating question.
      Rating rating_answer = 2;
      // The answer to a five-point rating question.
      FivePointRating five_point_rating_answer = 3;
      // The answer to a select question.
      Select select_answer = 4;
      // The answer to an open-text question.
      OpenText open_text_answer = 5;
    }

    // An answer specifying that the question was skipped.
    message Skip {
      // The reason why the question was skipped.
      Reason skip_reason = 1;

      // The possible reasons for a question being skipped.
      enum Reason {
        // The default reason in case the reason is not set. Should not be used.
        REASON_UNSPECIFIED = 0;
        // The question was skipped by the customer.
        REASON_SKIPPED_BY_CUSTOMER = 1;
        // The question was not displayed to the customer because its condition was not fulfilled.
        REASON_CONDITION_NOT_MET = 2;
        // The question could not be displayed to the customer because it was unsupported by the client.
        REASON_QUESTION_UNSUPPORTED = 3;
      }
    }

    // An answer to a rating question.
    message Rating {
      // The customer-selected rating.
      int32 selected_rating = 1;
    }

    // An answer to a five-point rating question.
    message FivePointRating {
      // The customer-selected rating.
      int32 selected_rating = 1;
    }

    // An answer to a select question.
    message Select {
      // The values of the customer-selected option(s).
      // Must not be empty.
      // Must be exactly one, if multiple_selection is false for that question.
      repeated string selected_options = 1;
    }

    // An answer to an open-text question.
    message OpenText {
      // The text entered by the customer.
      string text = 1;
    }
  }
}
