{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "urn:akari-video:schema:asset-meta:v1",
  "title": "AKARI Video Asset Metadata v1",
  "description": "AKARI Video 素材ライブラリの meta.json 契約 v1。",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "id",
    "category",
    "title",
    "description",
    "when_to_use",
    "tags",
    "knobs",
    "ai_usage",
    "requires",
    "provenance",
    "author",
    "license",
    "tier"
  ],
  "properties": {
    "model3d": {
      "type": "object", "additionalProperties": false, "required": ["unit", "up", "front", "ground", "size_cm"],
      "properties": {
        "unit": { "enum": ["mm", "cm", "m"] },
        "up": { "type": "string" }, "front": { "type": "string" },
        "ground": { "enum": ["bottom-center", "origin", "center"] },
        "size_cm": { "type": "array", "items": { "type": "number", "exclusiveMinimum": 0 }, "minItems": 3, "maxItems": 3 },
        "screen": { "type": "object", "additionalProperties": false, "required": ["material", "aspect"], "properties": { "material": { "type": "string" }, "aspect": { "type": "number", "exclusiveMinimum": 0 } } },
        "skeleton": { "const": "vrm-humanoid" },
        "poses": { "type": "array", "items": { "type": "string" }, "uniqueItems": true }
      }
    },
    "world": {
      "type": "object", "additionalProperties": false, "required": ["kind", "camera_height_cm", "files"],
      "properties": {
        "kind": { "const": "hdri" },
        "camera_height_cm": { "type": "number", "exclusiveMinimum": 0 },
        "files": { "type": "object", "additionalProperties": false, "required": ["light", "background"], "properties": { "light": { "type": "string", "minLength": 1 }, "background": { "type": "string", "minLength": 1 } } }
      }
    },
    "id": {
      "type": "string",
      "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
    },
    "category": {
      "type": "string",
      "$comment": "2026-07-29 に主題（3d/motion/telop/thumbnail…）から配布物の形へ切り替えた。overlay=時間を持つ HTML 断片 / still=時間を持たない HTML シート / scene3d=glTF+Three.js またはベイクレシピ / audio=音声トラックに載るバイナリ / broll=映像トラックに載る実写バイナリ / font=書体バイナリ / textstyle=字幕スタイルの preset.json（akari-textstyle）+ meta.json。主題は tags に逃がす。",
      "enum": ["overlay", "still", "scene3d", "audio", "broll", "font", "textstyle"]
    },
    "title": {
      "type": "string",
      "minLength": 1,
      "pattern": "\\S"
    },
    "aliases": {
      "type": "array",
      "items": { "type": "string", "minLength": 1 },
      "uniqueItems": true
    },
    "description": {
      "type": "string",
      "minLength": 1,
      "pattern": "\\S"
    },
    "when_to_use": {
      "type": "string",
      "minLength": 1,
      "pattern": "\\S"
    },
    "i18n": {
      "description": "任意の翻訳重ね書き。言語キーは主言語とし ja は置かず、基底の欄は上書きしない。表示は ja なら基底、それ以外は UI 言語 → en → 基底の順に欄ごとに解決し、題名の欠損時だけ id に落とす。",
      "type": "object",
      "propertyNames": { "pattern": "^[a-z]{2,3}$", "not": { "const": "ja" } },
      "additionalProperties": {
        "type": "object", "additionalProperties": false,
        "properties": {
          "title": { "type": "string", "minLength": 1, "pattern": "\\S" },
          "aliases": { "type": "array", "items": { "type": "string", "minLength": 1 }, "uniqueItems": true },
          "description": { "type": "string", "minLength": 1, "pattern": "\\S" },
          "when_to_use": { "type": "string", "minLength": 1, "pattern": "\\S" },
          "knobs": { "type": "object", "additionalProperties": { "type": "string", "minLength": 1, "pattern": "\\S" } },
          "groups": { "type": "object", "additionalProperties": { "type": "string", "minLength": 1, "pattern": "\\S" } }
        }
      }
    },
    "tags": {
      "type": "array",
      "uniqueItems": true,
      "items": {
        "type": "string",
        "minLength": 1,
        "pattern": "\\S"
      }
    },
    "knobs": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/knob"
      }
    },
    "ai_usage": {
      "type": "string",
      "minLength": 1,
      "pattern": "\\S"
    },
    "requires": {
      "type": "array",
      "uniqueItems": true,
      "items": {
        "type": "string",
        "minLength": 1,
        "pattern": "\\S"
      }
    },
    "provenance": {
      "$ref": "#/$defs/provenance"
    },
    "author": {
      "type": "string",
      "minLength": 1,
      "pattern": "\\S"
    },
    "license": {
      "$ref": "#/$defs/license"
    },
    "tier": {
      "enum": ["free", "pro"],
      "description": "素材の取得階層。pro は all-access-pass の権利が必要。"
    },
    "price": {
      "deprecated": true,
      "description": "廃止予定の旧価格。tier があれば無視し、次版で削除する。",
      "oneOf": [
        { "type": "null" },
        { "type": "number", "minimum": 0 }
      ]
    },
    "matched_by": {
      "$comment": "drop-folder タイトル正規化照合の来歴フィールド（2026-07-22 導入）。将来の照合経路追加時は enum を拡張する。",
      "enum": ["title-normalized"]
    },
    "version": {
      "$comment": "素材の版（2026-07-30 導入。data-contract-versioning 契約 §3 の宿題を果たすもの）。初版は 1。ツマミの削除・改名・type/unit 変更・範囲の縮小、クラス構造やスロットの変更は破壊的変更として bump する。ラベル・説明・tags・ai_usage の更新では上げない。harness/knob-diff.mjs が bump 漏れを検出する。",
      "type": "integer",
      "minimum": 1
    },
    "min_app_version": {
      "$comment": "この素材が要求する AKARI Video の最低版（任意）。古い版で開いたら推測せず正直に止まるための宣言（versioning 契約 原則 3 と同型）。特定版を要求しない素材では省略する。",
      "type": "string",
      "pattern": "^\\d+\\.\\d+\\.\\d+$"
    },
    "min_overlay_runtime_version": {
      "$comment": "この素材のライブ編集機能が要求する @akari-video/overlay-runtime の最低版（任意。2026-08-06 層ミラー data-mirror 規約の導入で新設）。min_app_version と同じ哲学 — 満たさない runtime では該当機能を推測で動かさない。初期表示だけなら未満でも正しく出る素材（例: 全層同一テキスト焼き込み出荷のミラー素材）はその旨を ai_usage / provenance に書く。",
      "type": "string",
      "pattern": "^\\d+\\.\\d+\\.\\d+$"
    },
    "remote": {
      "type": "boolean",
      "description": "true の場合、素材ディレクトリに実体ファイルを持たず、akari-r2 の source が実体を指す。既存 catalog の external + remote は validator が旧形式として互換扱いする。"
    },
    "source": {
      "oneOf": [
        { "$ref": "#/$defs/externalSource" },
        { "$ref": "#/$defs/akariR2Source" }
      ]
    },
    "motion_presets": {
      "$comment": "scene3d のライブ経路（glb 内蔵の名前付きアニメーションクリップ）の機械可読な一覧（任意。2026-08-06 laptop-live-asset タスクで新設）。宣言 JSON の animationClip にそのまま渡せる clip 名を人間可読なラベル・用途メモと対にする。クリップを持たない素材・ベイクレシピ（category=scene3d でも動きが camera_move/motion ツマミ側の素材）では省略する。",
      "type": "array",
      "minItems": 1,
      "items": {
        "$ref": "#/$defs/motionPreset"
      }
    }
  },
  "allOf": [
    {
      "if": { "properties": { "remote": { "const": true } }, "required": ["remote"] },
      "then": { "required": ["source"] },
      "$comment": "remote: true の akari-r2 必須と公開 catalog の既存 external エントリの後方互換は bin/validate-asset.mjs が判定する。JSON Schema では経路による例外を表せない。"
    }
  ],
  "$defs": {
    "knob": {
      "type": "object",
      "description": "型固有の追加属性は将来の契約で定義する。v0 では既知属性だけを型検証する。バインド先は cssVar（オーバーレイ素材）か param（3D ベイクレシピ、2026-07-14 追記）のどちらか一方を必須とする。",
      "required": ["type", "group"],
      "oneOf": [
        { "required": ["cssVar"], "not": { "required": ["param"] } },
        { "required": ["param"], "not": { "required": ["cssVar"] } }
      ],
      "allOf": [
        { "if": { "properties": { "type": { "const": "slider" } }, "required": ["type"] }, "then": { "properties": { "default": { "type": "number" } } } },
        { "if": { "properties": { "type": { "enum": ["text", "color", "dropdown", "checkbox", "media"] } }, "required": ["type"] }, "then": { "properties": { "default": { "type": "string" } } } }
      ],
      "properties": {
        "cssVar": {
          "type": "string",
          "pattern": "^--[A-Za-z_][A-Za-z0-9_-]*$"
        },
        "param": {
          "type": "string",
          "pattern": "^[a-z_][a-z0-9_]*$"
        },
        "type": {
          "type": "string",
          "enum": ["text", "color", "slider", "dropdown", "checkbox", "media"]
        },
        "group": {
          "type": "string",
          "minLength": 1,
          "pattern": "\\S"
        },
        "label": {
          "type": "string",
          "minLength": 1,
          "pattern": "\\S"
        },
        "min": {
          "type": "number"
        },
        "max": {
          "type": "number"
        },
        "unit": {
          "type": "string"
        },
        "default": {
          "type": ["number", "string"]
        }
      }
    },
    "provenance": {
      "type": "object",
      "additionalProperties": false,
      "required": ["origin", "generator"],
      "properties": {
        "origin": {
          "type": "string",
          "minLength": 1,
          "pattern": "\\S"
        },
        "generator": {
          "oneOf": [
            { "type": "null" },
            { "type": "string", "minLength": 1, "pattern": "\\S" }
          ]
        }
      }
    },
    "license": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "spdx",
        "scope",
        "attribution_required",
        "ai_training_allowed"
      ],
      "properties": {
        "spdx": {
          "type": "string",
          "minLength": 1,
          "pattern": "\\S"
        },
        "scope": {
          "type": "string",
          "minLength": 1,
          "pattern": "\\S"
        },
        "commercial": {
          "enum": ["allowed", "prohibited", "unknown"]
        },
        "attributionRequired": {
          "type": ["boolean", "null"]
        },
        "attribution_required": {
          "type": "boolean"
        },
        "ai_training_allowed": {
          "type": "boolean"
        }
      }
    },
    "externalSource": {
      "type": "object",
      "description": "外部取得元（既存 v0）の情報。",
      "additionalProperties": false,
      "required": ["url", "acquisition", "license_at_source", "attribution_required"],
      "properties": {
        "url": {
          "type": "string",
          "format": "uri",
          "pattern": "^https?://"
        },
        "acquisition": {
          "type": "string",
          "enum": ["direct", "login", "purchase"]
        },
        "license_at_source": {
          "type": "string",
          "minLength": 1,
          "pattern": "\\S"
        },
        "attribution_required": {
          "type": "boolean"
        },
        "preview_url": {
          "type": "string",
          "format": "uri",
          "pattern": "^https?://"
        }
      }
    },
    "akariR2Source": {
      "type": "object",
      "description": "AKARI 管理 R2 上の画像とプレビュー。",
      "additionalProperties": false,
      "required": ["image", "preview"],
      "properties": {
        "image": {
          "type": "string",
          "pattern": "^(?:https?://[^\\s]+|[A-Za-z0-9][A-Za-z0-9._/-]*)$"
        },
        "preview": {
          "type": "string",
          "minLength": 1,
          "pattern": "\\S"
        },
        "width": { "type": "integer", "minimum": 1 },
        "height": { "type": "integer", "minimum": 1 },
        "bytes": { "type": "integer", "minimum": 1 }
      }
    },
    "motionPreset": {
      "type": "object",
      "description": "motion_presets の 1 要素。glb 内蔵の名前付きクリップ 1 本を指す（2026-08-06 追記・任意フィールド）。",
      "additionalProperties": false,
      "required": ["clip", "label", "note"],
      "properties": {
        "clip": {
          "type": "string",
          "minLength": 1,
          "pattern": "\\S",
          "description": "宣言 JSON の animationClip にそのまま渡す glb 内のクリップ名（大小文字を含め完全一致）。"
        },
        "label": {
          "type": "string",
          "minLength": 1,
          "pattern": "\\S",
          "description": "人間向けの日本語ラベル。"
        },
        "note": {
          "type": "string",
          "minLength": 1,
          "pattern": "\\S",
          "description": "どういう動きか・向く用途を一言で。"
        }
      }
    }
  }
}
