{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://www.your-applications.com/schemas/workout-intent.json",
  "title": "Workout Intent",
  "description": "Structured intent for one workout. Higher-level than the .ytw file format (snake_case, seconds explicit, named sections). Consumed by tools/scripts/generate_workout_library.py and mirrors the future yourtrainer-mcp build_workout_from_intent input shape so the two stay portable. Field-set is intentionally close to .ytw (canonical reference: public/your-trainer/workout-schema.html); the generator translates each intent block to a .ytw interval 1:1.",
  "type": "object",
  "required": ["name", "description", "set", "category", "workout_type", "warmup", "intervals", "cooldown"],
  "additionalProperties": false,
  "properties": {
    "name": {"type": "string", "description": "Display name in the primary locale."},
    "description": {"type": "string", "description": "One- or two-sentence description shown on the workout card."},
    "name_i18n": {
      "type": "object",
      "description": "Per-locale name translations. Keys are BCP-47 locale codes; values are translated names. Generator weaves into .ytw strings.<locale>.name.",
      "additionalProperties": {"type": "string"}
    },
    "description_i18n": {
      "type": "object",
      "description": "Per-locale description translations. Same structure as name_i18n.",
      "additionalProperties": {"type": "string"}
    },
    "slug": {"type": "string", "description": "Optional explicit slug. Auto-derived from name if omitted. Kebab-case."},
    "bundled": {"type": "boolean", "description": "When true, the generator also writes the .ytw (as .json) to the bundled-root path (default ./assets/workouts/) so the APK ships it as a Built-in workout per FEAT-0081. Default false — only the downloadable .ytwpack carries the workout. Set true on intents that should be available pre-network for first-launch riders."},
    "named": {"type": "string", "description": "Optional named-workout identifier (e.g. 'Carson' for a famous-workout reference). Surfaces above the regular name on the card."},
    "set": {"type": "string", "enum": ["power", "hr-zone"], "description": "Top-level taxonomy. Drives library directory layout."},
    "category": {"type": "string", "description": "Sub-taxonomy inside the set. For power: endurance | sweet-spot | threshold | vo2max | anaerobic | recovery | tests-and-specials. For hr-zone: steady-holds | zone-progressions | adjacent-zone-ladders | sustained-intervals | hr-ceiling-floor-drift | recovery."},
    "workout_type": {"type": "string", "enum": ["POWER", "HR_ZONE", "ROUTE"], "description": "Maps to .ytw workoutType."},
    "variant": {"type": "string", "enum": ["STANDARD", "RAMP_TEST"], "description": "Maps to .ytw variant. Default STANDARD."},
    "difficulty": {"type": "integer", "minimum": 1, "maximum": 5, "description": "Subjective difficulty 1-5. Maps to .ytw difficulty."},
    "primary_locale": {"type": "string", "default": "en", "description": "Authoring locale. Maps to .ytw primaryLocale."},
    "physiology_focus": {"type": "array", "items": {"type": "string"}, "description": "Adaptation tags (e.g. aerobic-endurance, fractional-utilization, neuromuscular)."},
    "discipline_tags": {"type": "array", "items": {"type": "string", "enum": ["road", "tt", "tri", "gravel", "mtb", "cx", "indoor"]}, "description": "Riding disciplines this workout suits."},
    "cadence_focus": {"type": "string", "enum": ["standard", "high", "low", "pyramid", "mixed", "standing"], "description": "Cadence variant per REQ-0027. Semantics: data/training-principles/hr-zone-cadence.md."},
    "requires_power_meter": {"type": "boolean", "description": "True for POWER; false-ish for HR_ZONE (HRM required instead)."},
    "requires_hrm": {"type": "boolean", "description": "True for HR_ZONE workouts. Drives the in-app 'LINK HRM' START-button state."},
    "repeatable": {"type": "boolean", "description": "True when the body extends cleanly under the app's runtime repeat-N feature (FEAT-0048) — i.e. body's last-interval + first-interval concatenate to a recovery gap matching within-body cadence. False when the body's end/start would create an asymmetric inter-iteration gap; rider should pick a longer pre-built variant instead of relying on runtime repeat. Default: true."},
    "plan_recipe_affiliation": {"type": "string", "description": "Optional plan-recipe ID this workout belongs to (e.g. 'sweet-spot-base-build-4w' for the 4-week base build recipe)."},
    "warmup": {"$ref": "#/definitions/block"},
    "intervals": {
      "type": "array",
      "items": {"oneOf": [{"$ref": "#/definitions/block"}, {"$ref": "#/definitions/repeat"}]},
      "description": "Ordered work blocks between warmup and cooldown. Flat blocks or repeat groups."
    },
    "cooldown": {"$ref": "#/definitions/block"}
  },
  "definitions": {
    "block": {
      "type": "object",
      "required": ["duration_seconds", "zone", "label"],
      "additionalProperties": false,
      "properties": {
        "id": {"type": "string", "description": "Stable kebab-case ID. Required for any block referenced in strings.labels or strings.cues."},
        "duration_seconds": {"type": "integer", "minimum": 1},
        "target_power_percent": {"type": "integer", "description": "Target wattage as % of FTP. Required for POWER blocks."},
        "target_power_end_percent": {"type": "integer", "description": "Optional ramp end. Linear interpolation from target_power_percent."},
        "target_hr_zone": {"type": "integer", "minimum": 1, "maximum": 5, "description": "Required for HR_ZONE blocks."},
        "zone": {"type": "string", "enum": ["Z1", "Z2", "Z3", "Z4", "Z5"], "description": "Visual intensity-zone token."},
        "interval_type": {"type": "string", "enum": ["WARMUP", "COOLDOWN", "INTERVAL"], "description": "Default INTERVAL. WARMUP/COOLDOWN are excluded from work-only summaries."},
        "label": {"type": "string"},
        "cadence_target": {"type": "integer", "description": "Optional cadence target in RPM. Maps to .ytw cadenceTarget."},
        "cues": {"type": "array", "items": {"$ref": "#/definitions/cue"}}
      }
    },
    "repeat": {
      "type": "object",
      "required": ["repeat", "intervals"],
      "additionalProperties": false,
      "properties": {
        "repeat": {"type": "integer", "minimum": 1, "description": "Number of times the inner sequence repeats."},
        "intervals": {
          "type": "array",
          "minItems": 1,
          "items": {"oneOf": [{"$ref": "#/definitions/block"}, {"$ref": "#/definitions/repeat"}]},
          "description": "Inner sequence. Plain blocks or nested repeat groups (per workout-schema.html: 'Repeat groups can nest')."
        }
      }
    },
    "cue": {
      "type": "object",
      "required": ["offset_seconds", "text"],
      "additionalProperties": false,
      "properties": {
        "offset_seconds": {"type": "integer", "minimum": 0},
        "text": {"type": "string"},
        "duration_seconds": {"type": "integer", "minimum": 1, "description": "How long to leave the cue on screen. Default 5."}
      }
    }
  }
}
