syntax = "proto3";

package nominal.file_ingest.v1;

import "nominal/protos/gen/v1/alias.proto";
import "nominal/protos/types/object_storage/handle.proto";
import "nominal/protos/types/time/timestamp_parsers.proto";

option java_multiple_files = true;
option java_package = "io.nominal.file_ingest.v1";

message IngestFileRequest {
  string ingest_job_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.ingest.api.IngestJobRid"}];
  string dataset_file_id = 2 [(nominal.gen.v1.alias) = {java_name: "io.nominal.datasource.DatasetFileId"}];

  string dataset_rid = 3 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.DatasetRid"}];
  string org_rid = 4 [(nominal.gen.v1.alias) = {java_name: "io.nominal.authentication.api.OrgRid"}];
  string workspace_rid = 5 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.WorkspaceRid"}];

  nominal.types.object_storage.Handle handle = 6;

  TimestampMetadata timestamp_metadata = 7;

  map<string, string> additional_tags = 8 [(nominal.gen.v1.alias) = {
    key_java_name: "io.nominal.api.TagName"
    value_java_name: "io.nominal.api.TagValue"
  }];

  oneof ingest {
    LogFileIngest log_ingest = 9;
    DataFileIngest data_ingest = 10;
  }
}

// Single-channel, line-delimited log files (e.g. JSON-L journal logs) where each line is one
// sample for the same channel.
message LogFileIngest {
  string log_channel = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.Channel"}];
}

// Data files. The `shape` oneof picks the logical layout of the file: wide (row holds many
// channels), long (row holds one sample), or batch (record holds arrays of samples for one
// channel). Each shape's options carry their own format oneof so any of CSV/Parquet/Avro
// can encode any shape. Format-specific parser config lives inside CsvOpts/ParquetOpts/
// AvroOpts.
message DataFileIngest {
  oneof shape {
    WideOpts wide_opts = 1;
    LongOpts long_opts = 2;
    BatchOpts batch_opts = 3;
  }

  map<string, string> units = 4 [(nominal.gen.v1.alias) = {
    key_java_name: "io.nominal.api.Channel"
    value_java_name: "io.nominal.api.Unit"
  }];

  optional string channel_prefix = 5;
}

// Wide format: each row (or Avro record) holds one timestamp and many channels-as-columns,
// so a single row contributes one sample to each channel. Non-channel columns can be
// declared as tag sources or excluded entirely.
message WideOpts {
  oneof format {
    CsvOpts csv_opts = 1;
    ParquetOpts parquet_opts = 2;
    AvroOpts avro_opts = 3;
  }

  // Maps a tag name to the column/Avro-field whose value is that tag.
  map<string, string> tag_columns = 4 [(nominal.gen.v1.alias) = {
    key_java_name: "io.nominal.api.TagName"
    value_java_name: "io.nominal.api.ColumnName"
  }];

  // Columns/Avro-fields to skip entirely (neither channel nor tag).
  repeated string exclude_columns = 5 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.ColumnName"}];
}

// Long format: each row (or Avro record) is a single (channel, timestamp, value, tags) sample,
// with channel name and value carried in dedicated columns. The timestamp column is supplied
// by the top-level TimestampMetadata.
message LongOpts {
  oneof format {
    CsvOpts csv_opts = 1;
    ParquetOpts parquet_opts = 2;
    AvroOpts avro_opts = 3;
  }

  string channel_column = 4 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.ColumnName"}];
  string value_column = 5 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.ColumnName"}];
  // Column/field that carries this row's tags as a serialized map (e.g. JSON object).
  optional string tags_column = 6 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.ColumnName"}];
}

// Batch / record-of-arrays: each record carries one channel plus parallel arrays of timestamps
// and values. Avro today; CSV/Parquet record-format encodings can be added later.
message BatchOpts {
  oneof format {
    AvroOpts avro_opts = 1;
  }

  // Field paths into the record that locate each piece of the batch.
  string channel_field = 2 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.ColumnName"}];
  string timestamps_field = 3 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.ColumnName"}];
  string values_field = 4 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.ColumnName"}];
  optional string tags_field = 5 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.ColumnName"}];
}

// Pairs the column that holds the timestamp with the parser that interprets its values.
// The parser variants live in nominal/types/time/timestamp_parsers.proto so other surfaces
// (procedures, registry, etc.) share the same wire format.
message TimestampMetadata {
  // Column/field in the file that holds the timestamp. The caller is responsible for supplying
  // this — for Avro that means the schema-fixed timestamp field name.
  string column = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.ColumnName"}];

  nominal.types.time.TimestampType type = 2;
}

message CsvOpts {}

message ParquetOpts {}

message AvroOpts {}
