{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://github.json-mde.tech/schemas/jm2mp/1.0.0/JM2MP-v1.0.0--JSON-Schema-Draft-2020-12.json",
  "title": "JSON Model-to-Model Projection (JM2MP) v1.0.0 JSON Schema (Draft 2020-12).",
  "description": "JSON Schema for JM2MP projection modules. A module is an object with named templates (like callable functions), a root template (named '@' as entry point for transformations), and optional properties '$options' and '$schema' for metadata.",

  "$ref": "#/$defs/Module",

  "$defs": {

    "Module": {
      "type": "object",
      "properties": {
        "$options": { "$ref": "#/$defs/Options" },
        "$schema":  { "type": "string" },
        "$":        { "$ref": "#/$defs/Projection" }
      },
      "patternProperties": {
        "^(?![\\$@%])(?!$).*$": { "$ref": "#/$defs/Projection" }
      },
      "additionalProperties": false
    },

    "Options": {
      "type": "object",
      "properties": {
        "$version": {
          "const": "1.0"
        },
        "$annotations": {
          "anyOf": [
            { "type": "null"    },
            { "type": "boolean" },
            { "type": "number"  },
            { "type": "string"  },
            { "type": "array"   },
            { "type": "object"  }
          ]
        },
        "$default-query-language": {
          "type": "string",
          "minLength": 1
        },
        "$depends-on": {
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          },
          "uniqueItems": true
        }
      },
      "patternProperties": {
        "^\\$": true
      },
      "additionalProperties": false
    },

    "Projection": {
      "description": "A projection is any JSON value: a literal constant (scalar, array or object) or an operation.",
      "oneOf": [
        { "type": "null" },
        { "type": "boolean" },
        { "type": "number" },
        { "type": "string" },
        { "$ref": "#/$defs/ArrayProjection" },
        { "$ref": "#/$defs/LiteralObject" },
        { "$ref": "#/$defs/Operation" }
      ]
    },

    "ArrayProjection": {
      "description": "A literal array, where all its items are always considered as projections.",
      "type": "array",
      "items": { "$ref": "#/$defs/Projection" }
    },

    "LiteralObject": {
      "description": "A literal object without operations. Keys that wanted to start by '$' must be escaped using '\\$'.",
      "type": "object",
      "patternProperties": {
        "^(?!\\$)": { "$ref": "#/$defs/Projection" },
        "^\\\\\\$": { "$ref": "#/$defs/Projection" }
      },
      "additionalProperties": false
    },

    "Operation": {
      "description": "A template command that represents an JM2MP operation: its '$op' key names the operator and every argument starts always by prefix '$'.",
      "type": "object",
      "required": ["$op"],
      "properties": {
        "$op": { "type": "string" }
      },
      "oneOf": [
        { "$ref": "#/$defs/Op_pipe" },
        { "$ref": "#/$defs/Op_get" },
        { "$ref": "#/$defs/Op_if" },
        { "$ref": "#/$defs/Op_foldArr" },
        { "$ref": "#/$defs/Op_foldObj" },
        { "$ref": "#/$defs/Op_cons" },
        { "$ref": "#/$defs/Op_insert" },
        { "$ref": "#/$defs/Op_let" },
        { "$ref": "#/$defs/Op_call" },
        { "$ref": "#/$defs/Op_eq" },
        { "$ref": "#/$defs/Op_lt" },
        { "$ref": "#/$defs/Op_gt" },
        { "$ref": "#/$defs/Op_lte" },
        { "$ref": "#/$defs/Op_gte" },
        { "$ref": "#/$defs/Op_neq" },
        { "$ref": "#/$defs/Op_not" },
        { "$ref": "#/$defs/Op_and" },
        { "$ref": "#/$defs/Op_or" },
        { "$ref": "#/$defs/Op_add" },
        { "$ref": "#/$defs/Op_sub" },
        { "$ref": "#/$defs/Op_mul" },
        { "$ref": "#/$defs/Op_div" },
        { "$ref": "#/$defs/Op_mod" },
        { "$ref": "#/$defs/Op_neg" },
        { "$ref": "#/$defs/Op_abs" },
        { "$ref": "#/$defs/Op_concat" },
        { "$ref": "#/$defs/Op_length" },
        { "$ref": "#/$defs/Op_substring" },
        { "$ref": "#/$defs/Op_upper" },
        { "$ref": "#/$defs/Op_lower" },
        { "$ref": "#/$defs/Op_typeof" },
        { "$ref": "#/$defs/Op_coalesce" },
        { "$ref": "#/$defs/Op_has" },
        { "$ref": "#/$defs/Op_sort" },
        { "$ref": "#/$defs/Op_lookup" },
        { "$ref": "#/$defs/Op_merge" }
      ]
    },

    "_BinaryOp": {
      "description": "Schema template used for binary operators: {$op,$left,$right}.",
      "type": "object",
      "required": ["$op", "$left", "$right"],
      "properties": {
        "$op":    { "type": "string" },
        "$left":  { "$ref": "#/$defs/Projection" },
        "$right": { "$ref": "#/$defs/Projection" }
      },
      "additionalProperties": false
    },

    "_UnaryOp": {
      "description": "Schema template used for unary operators: {$op,$value}.",
      "type": "object",
      "required": ["$op", "$value"],
      "properties": {
        "$op":    { "type": "string" },
        "$value": { "$ref": "#/$defs/Projection" }
      },
      "additionalProperties": false
    },

    "Op_pipe": {
      "description": "PIPE template command: sequential projection composition.",
      "type": "object",
      "required": ["$op", "$stages"],
      "properties": {
        "$op": { "const": "pipe" },
        "$stages": {
          "type": "array",
          "items": { "$ref": "#/$defs/Projection" }
        }
      },
      "additionalProperties": false
    },

    "Op_get": {
      "description": "GET template command: value accesor by path.",
      "type": "object",
      "required": ["$op", "$path"],
      "properties": {
        "$op": { "const": "get" },
        "$path": {
          "oneOf": [
            { "type": "string" },
            {
              "type": "array",
              "items": {
                "oneOf": [
                  { "type": "string" },
                  { "type": "integer", "minimum": 0 }
                ]
              }
            }
          ]
        },
        "$syntax": {
          "type": "string",
          "minLength": 1
        },
        "$from": { "$ref": "#/$defs/Projection" }
      },
      "additionalProperties": false
    },

    "Op_if": {
      "description": "IF template command: conditional projection (boolean anhilitation).",
      "type": "object",
      "required": ["$op", "$cond", "$then", "$else"],
      "properties": {
        "$op":   { "const": "if" },
        "$cond": { "$ref": "#/$defs/Projection" },
        "$then": { "$ref": "#/$defs/Projection" },
        "$else": { "$ref": "#/$defs/Projection" }
      },
      "additionalProperties": false
    },

    "Op_foldArr": {
      "description": "FOLDARR template command: catamorphism of arrays.",
      "type": "object",
      "required": ["$op", "$over", "$init", "$step"],
      "properties": {
        "$op":   { "const": "foldArr" },
        "$over": { "$ref": "#/$defs/Projection" },
        "$init": { "$ref": "#/$defs/Projection" },
        "$step": { "$ref": "#/$defs/Projection" }
      },
      "additionalProperties": false
    },

    "Op_foldObj": {
      "description": "FOLDOBJ template command: catamorphism of objects.",
      "type": "object",
      "required": ["$op", "$over", "$init", "$step"],
      "properties": {
        "$op":   { "const": "foldObj" },
        "$over": { "$ref": "#/$defs/Projection" },
        "$init": { "$ref": "#/$defs/Projection" },
        "$step": { "$ref": "#/$defs/Projection" }
      },
      "additionalProperties": false
    },

    "Op_cons": {
      "description": "CONS template command: dynamic insertion of new items into existing arrays.",
      "type": "object",
      "required": ["$op", "$head", "$tail"],
      "properties": {
        "$op":   { "const": "cons" },
        "$head": { "$ref": "#/$defs/Projection" },
        "$tail": { "$ref": "#/$defs/Projection" }
      },
      "additionalProperties": false
    },

    "Op_insert": {
      "description": "INSERT template command: dynamic addition of new propierties into existing objects.",
      "type": "object",
      "required": ["$op", "$key", "$value", "$into"],
      "properties": {
        "$op":    { "const": "insert" },
        "$key":   { "$ref": "#/$defs/Projection" },
        "$value": { "$ref": "#/$defs/Projection" },
        "$into":  { "$ref": "#/$defs/Projection" }
      },
      "additionalProperties": false
    },

    "Op_let": {
      "description": "LET template command: value bindings for aliases.",
      "type": "object",
      "required": ["$op", "$bindings", "$in"],
      "properties": {
        "$op": { "const": "let" },
        "$bindings": {
          "type": "object",
          "propertyNames": {
            "pattern": "^(?![\\$@%])(?!$).*$"
          },
          "additionalProperties": { "$ref": "#/$defs/Projection" }
        },
        "$in": { "$ref": "#/$defs/Projection" }
      },
      "additionalProperties": false
    },

    "Op_call": {
      "description": "CALL template command: for invoking named templates.",
      "type": "object",
      "required": ["$op", "$ref"],
      "properties": {
        "$op":  { "const": "call" },
        "$ref": {
          "type": "string",
          "pattern": "^(?![\\$@%])(?!$).*$"
        },
        "$at":  { "$ref": "#/$defs/Projection" }
      },
      "additionalProperties": false
    },

    "Op_not": {
      "description": "Logic predicate NOT: negation of a boolean value.",
      "allOf": [
        { "$ref": "#/$defs/_UnaryOp" },
        { "properties": { "$op": { "const": "not" } } }
      ]
    },

    "Op_and": {
      "description": "Logic predicate AND: disjuntion of two boolean values.",
      "allOf": [
        { "$ref": "#/$defs/_BinaryOp" },
        { "properties": { "$op": { "const": "and" } } }
      ]
    },

    "Op_or": {
      "description": "Logic predicate OR: conjuntion of two boolean values.",
      "allOf": [
        { "$ref": "#/$defs/_BinaryOp" },
        { "properties": { "$op": { "const": "or" } } }
      ]
    },

    "Op_eq": {
      "description": "Logic predicate EQ: (deep) equality of values.",
      "allOf": [
        { "$ref": "#/$defs/_BinaryOp" },
        { "properties": { "$op": { "const": "eq" } } }
      ]
    },

    "Op_lt": {
      "description": "Logic predicate LT: less-than operation between two scalar values.",
      "allOf": [
        { "$ref": "#/$defs/_BinaryOp" },
        { "properties": { "$op": { "const": "lt" } } }
      ]
    },

    "Op_gt": {
      "description": "Logic predicate LT: greater-than operation between two scalar values.",
      "allOf": [
        { "$ref": "#/$defs/_BinaryOp" },
        { "properties": { "$op": { "const": "gt" } } }
      ]
    },

    "Op_lte": {
      "description": "Logic predicate LTE: less-than-or-equal operation between two scalar values.",
      "allOf": [
        { "$ref": "#/$defs/_BinaryOp" },
        { "properties": { "$op": { "const": "lte" } } }
      ]
    },

    "Op_gte": {
      "description": "Logic predicate GTE: greater-than-or-equal operation between two scalar values.",
      "allOf": [
        { "$ref": "#/$defs/_BinaryOp" },
        { "properties": { "$op": { "const": "gte" } } }
      ]
    },

    "Op_neq": {
      "description": "Logic predicate NEQ: not-equal operation between two scalar values.",
      "allOf": [
        { "$ref": "#/$defs/_BinaryOp" },
        { "properties": { "$op": { "const": "neq" } } }
      ]
    },

    "Op_add": {
      "description": "ADD operator: addition operation between two scalar values of the same type.",
      "allOf": [
        { "$ref": "#/$defs/_BinaryOp" },
        { "properties": { "$op": { "const": "add" } } }
      ]
    },

    "Op_sub": {
      "description": "SUB operator: substraction operation between two scalar values of the same type.",
      "allOf": [
        { "$ref": "#/$defs/_BinaryOp" },
        { "properties": { "$op": { "const": "sub" } } }
      ]
    },

    "Op_mul": {
      "description": "MUL operator: multiplication operation between two scalar values of the same type.",
      "allOf": [
        { "$ref": "#/$defs/_BinaryOp" },
        { "properties": { "$op": { "const": "mul" } } }
      ]
    },

    "Op_div": {
      "description": "DIV operator: division operation between two scalar values of the same type.",
      "allOf": [
        { "$ref": "#/$defs/_BinaryOp" },
        { "properties": { "$op": { "const": "div" } } }
      ]
    },

    "Op_mod": {
      "description": "MOD operator: modulus operation between two scalar values of the same type.",
      "allOf": [
        { "$ref": "#/$defs/_BinaryOp" },
        { "properties": { "$op": { "const": "mod" } } }
      ]
    },

    "Op_neg": {
      "description": "NEG operator: negation operation of a boolean or numeric scalar value.",
      "allOf": [
        { "$ref": "#/$defs/_UnaryOp" },
        { "properties": { "$op": { "const": "neg" } } }
      ]
    },

    "Op_abs": {
      "description": "ABS operator: absolute operation of a numeric scalar value.",
      "allOf": [
        { "$ref": "#/$defs/_UnaryOp" },
        { "properties": { "$op": { "const": "abs" } } }
      ]
    },

    "Op_concat": {
      "description": "CONCAT operator: string concatenation of scalar values.",
      "type": "object",
      "required": ["$op", "$parts"],
      "properties": {
        "$op":    { "const": "concat" },
        "$parts": {
          "type": "array",
          "items": { "$ref": "#/$defs/Projection" }
        }
      },
      "additionalProperties": false
    },

    "Op_length": {
      "description": "LENGHT operator: get string's length of a scalar value.",
      "allOf": [
        { "$ref": "#/$defs/_UnaryOp" },
        { "properties": { "$op": { "const": "length" } } }
      ]
    },

    "Op_substring": {
      "description": "SUBSTRING operator: it gets a sub-string from specified start up to an end of a scalar value.",
      "type": "object",
      "required": ["$op", "$value", "$start"],
      "properties": {
        "$op":     { "const": "substring" },
        "$value":  { "$ref": "#/$defs/Projection" },
        "$start":  { "$ref": "#/$defs/Projection" },
        "$end":    { "$ref": "#/$defs/Projection" }
      },
      "additionalProperties": false
    },

    "Op_upper": {
      "description": "UPPER operator: it gets a string with all its characters as upper letters from a scalar value.",
      "allOf": [
        { "$ref": "#/$defs/_UnaryOp" },
        { "properties": { "$op": { "const": "upper" } } }
      ]
    },

    "Op_lower": {
      "description": "LOWER operator: it gets a string with all its characters as lower letters from a scalar value.",
      "allOf": [
        { "$ref": "#/$defs/_UnaryOp" },
        { "properties": { "$op": { "const": "lower" } } }
      ]
    },

    "Op_typeof": {
      "description": "TYPEOF operator: it gets the data type a JSON value.",
      "allOf": [
        { "$ref": "#/$defs/_UnaryOp" },
        { "properties": { "$op": { "const": "typeof" } } }
      ]
    },

    "Op_coalesce": {
      "description": "COALESCE operator: it gets a value if it is not null; otherwise, it returns a default value.",
      "type": "object",
      "required": ["$op", "$value", "$default"],
      "properties": {
        "$op":       { "const": "coalesce" },
        "$value":    { "$ref": "#/$defs/Projection" },
        "$default":  { "$ref": "#/$defs/Projection" }
      },
      "additionalProperties": false
    },

    "Op_has": {
      "description": "HAS operator: it determines if an object value has the specified key.",
      "type": "object",
      "required": ["$op", "$key", "$in"],
      "properties": {
        "$op":  { "const": "has" },
        "$key": { "$ref": "#/$defs/Projection" },
        "$in":  { "$ref": "#/$defs/Projection" }
      },
      "additionalProperties": false
    },

    "Op_sort": {
      "description": "SORT operator: it sorts complex types (array's items or object's keys) using an ascending or descending criteria.",
      "type": "object",
      "required": ["$op", "$value"],
      "properties": {
        "$op":    { "const": "sort" },
        "$value": { "$ref": "#/$defs/Projection" },
        "$by":    { "$ref": "#/$defs/Projection" },
        "$desc":  { "$ref": "#/$defs/Projection" }
      },
      "additionalProperties": false
    },

    "Op_lookup": {
      "description": "LOOKUP operator: it gets the specified key's value of an object (without traversing entire object using FOLDOBJ).",
      "type": "object",
      "required": ["$op", "$key", "$in"],
      "properties": {
        "$op":  { "const": "lookup" },
        "$key": { "$ref": "#/$defs/Projection" },
        "$in":  { "$ref": "#/$defs/Projection" }
      },
      "additionalProperties": false
    },

    "Op_merge": {
      "description": "MERGE operator: it merges two complex types (array's items or object's keys) avoiding traversal calculations (FOLDARR or FOLDOBJ).",
      "allOf": [
        { "$ref": "#/$defs/_BinaryOp" },
        { "properties": { "$op": { "const": "merge" } } }
      ]
    }
  }
}
