{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "recon_source_template",
  "title": "Recon Source Template",
  "description": "Declares a reusable file format: how to parse it, which columns map to canonical fields, and how to normalize values. Global — shared across tenants (D9). Stored in recon_source_templates.definition.",
  "type": "object",
  "required": ["name", "display_name", "source_role", "file_format", "fields"],
  "additionalProperties": false,
  "properties": {
    "name": {
      "type": "string",
      "pattern": "^[a-z0-9_]+$",
      "description": "Unique slug, e.g. 'hdfc_card_mpr'"
    },
    "display_name": { "type": "string", "minLength": 1, "maxLength": 200 },
    "source_role": {
      "enum": ["internal_system", "gateway", "settlement_bank", "processor"],
      "description": "Business-agnostic role. UI renders the tenant's label for it (HIS/POS/PMS...)."
    },
    "file_format": { "enum": ["xlsx", "csv"] },
    "sheet": {
      "type": ["string", "integer"],
      "description": "xlsx only: sheet name or 0-based index. Default: first sheet."
    },
    "csv_options": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "delimiter": { "type": "string", "default": "," },
        "encoding": { "type": "string", "default": "utf-8" }
      }
    },
    "header": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "row": {
          "type": ["integer", "string"],
          "description": "0-based header row index, or 'auto' to detect the first row where required aliases appear.",
          "default": 0
        },
        "skip_rows_containing": {
          "type": "array",
          "items": { "type": "string" },
          "description": "Drop body rows where any cell contains one of these strings (bank portal summary/footer rows)."
        }
      }
    },
    "row_limit": {
      "type": "integer",
      "default": 200000,
      "description": "Hard guard (D7). Engine fails with a clear error above this."
    },
    "fields": {
      "type": "array",
      "minItems": 1,
      "items": { "$ref": "#/definitions/field" }
    }
  },
  "definitions": {
    "field": {
      "type": "object",
      "required": ["canonical"],
      "additionalProperties": false,
      "properties": {
        "canonical": {
          "type": "string",
          "pattern": "^[a-z0-9_]+$",
          "description": "Canonical field name referenced by rule keys, e.g. 'card_last4', 'amount'."
        },
        "aliases": {
          "type": "array",
          "items": { "type": "string" },
          "minItems": 1,
          "description": "Column headers as they appear in files, tried in order. Omit when 'derive' is set."
        },
        "derive": {
          "type": "object",
          "required": ["op", "from"],
          "additionalProperties": false,
          "description": "Derived field computed from other canonical fields instead of a file column.",
          "properties": {
            "op": { "enum": ["coalesce", "sum", "concat"] },
            "from": {
              "type": "array",
              "items": { "type": "string" },
              "minItems": 1,
              "description": "Canonical field names. coalesce: first non-empty/non-zero. sum: numeric sum. concat: string join."
            },
            "separator": { "type": "string", "description": "concat only", "default": "" }
          }
        },
        "required": { "type": "boolean", "default": true },
        "dtype": { "enum": ["string", "number", "date"], "default": "string" },
        "normalize": {
          "type": "array",
          "items": { "$ref": "#/definitions/normalize_step" },
          "description": "Applied in order. Replaces ALL manual SQL preprocessing from the reference workflow."
        }
      },
      "oneOf": [
        { "required": ["aliases"] },
        { "required": ["derive"] }
      ]
    },
    "normalize_step": {
      "type": "object",
      "required": ["step"],
      "additionalProperties": false,
      "properties": {
        "step": {
          "enum": [
            "trim",
            "upper",
            "lower",
            "last4",
            "lpad",
            "strip_chars",
            "to_number",
            "date_parse",
            "value_map"
          ]
        },
        "length": { "type": "integer", "description": "lpad: target length" },
        "fill": { "type": "string", "description": "lpad: fill char, default '0'" },
        "chars": {
          "type": "array",
          "items": { "type": "string" },
          "description": "strip_chars: characters/substrings to remove, e.g. [\",\", \"'\", \"₹\"]"
        },
        "formats": {
          "type": "array",
          "items": { "type": "string" },
          "description": "date_parse: strptime formats tried in order, e.g. ['%d-%m-%Y %H:%M', '%d/%m/%Y']"
        },
        "map": {
          "type": "object",
          "description": "value_map: inline raw→canonical mapping"
        },
        "map_ref": {
          "enum": ["location_map"],
          "description": "value_map: use the tenant's recon_location_maps binding (e.g. long unit/store names → short codes). Tenant-specific even when the template is global."
        }
      }
    }
  }
}
