{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://labconstrictor.cellmig.org/schema/labconstrictor-app.schema.json",
  "title": "labconstrictor-app.yaml",
  "description": "Manifest that an app keeps in the root of its own repository so that it can be listed in the LabConstrictor App Centre. Edit it with schema support: add `# yaml-language-server: $schema=https://labconstrictor.cellmig.org/schema/labconstrictor-app.schema.json` as the first line.",
  "type": "object",
  "additionalProperties": false,
  "required": ["schema", "name", "summary", "kind", "distribution"],
  "properties": {
    "schema": {"const": 1, "description": "Manifest format version."},
    "name": {"type": "string", "minLength": 2, "maxLength": 60, "description": "Display name, as users know the app."},
    "summary": {"type": "string", "minLength": 20, "maxLength": 200, "description": "One sentence shown on the app card. Plain text."},
    "description": {"type": "string", "maxLength": 2000, "description": "A few plain-text paragraphs shown on the app page; separate paragraphs with a blank line. No HTML or Markdown."},
    "quickstart": {
      "type": "array", "minItems": 1, "maxItems": 6, "items": {"type": "string", "minLength": 5, "maxLength": 200},
      "description": "Up to six short plain-text steps for a first run, shown on the app page. Link to fuller documentation with docs_url."
    },
    "kind": {"enum": ["analysis", "segmentation", "simulation", "game", "training", "utility"], "description": "What the app is for; drives the filter on the App Centre."},
    "hosts": {
      "type": "array", "uniqueItems": true,
      "items": {"enum": ["notebook", "napari", "fiji", "qupath"]},
      "description": "Where the app's tools can be used besides its own window (the LabConstrictor-Tools bridge)."
    },
    "host_requirements": {
      "type": "object", "additionalProperties": false,
      "description": "The oldest LabConstrictor plugin of each host the app needs. labconstrictor-tools register copies it into the app's registry entry; a host whose plugin is older lists the app with a sentence saying which plugin to update (or refuses it with required: true). The notebook needs no plugin.",
      "properties": {
        "napari": {"$ref": "#/$defs/hostRequirement"},
        "fiji": {"$ref": "#/$defs/hostRequirement"},
        "qupath": {"$ref": "#/$defs/hostRequirement"}
      }
    },
    "distribution": {
      "type": "object", "additionalProperties": false,
      "anyOf": [{"required": ["installer"]}, {"required": ["colab"]}, {"required": ["pip"]}],
      "dependentRequired": {"platforms": ["installer"]},
      "description": "How people get the app. At least one route is required.",
      "properties": {
        "installer": {"const": true, "description": "The latest GitHub release carries LabConstrictor installers (Name-version-OS-arch.ext)."},
        "colab": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/link"}, "description": "Google Colab notebooks."},
        "platforms": {
          "type": "array", "minItems": 1, "uniqueItems": true, "items": {"enum": ["windows-x86_64", "windows-arm64", "macos-arm64", "macos-x86_64", "linux-x86_64", "linux-arm64"]},
          "description": "The systems you support. Only these installers are offered; leave it out to offer every installer of your release."
        },
        "pip": {"type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$(?!\\n)", "description": "Name of the package on PyPI."}
      }
    },
    "authors": {"type": "array", "items": {"$ref": "#/$defs/author"}},
    "license": {"type": "string", "minLength": 1, "description": "SPDX identifier, for example MIT."},
    "inputs": {"$ref": "#/$defs/facts", "description": "What the app accepts, as a short list (file formats, dimensions, units it needs). A summary, not README copy; use docs_url for the rest."},
    "outputs": {"$ref": "#/$defs/facts", "description": "What the app produces, as a short list (images, masks, tables, plots). A summary, not README copy; use docs_url for the rest."},
    "limitations": {"$ref": "#/$defs/facts", "description": "What the app cannot do or where it may be unreliable, as a short list."},
    "status": {"enum": ["Experimental", "Available", "Deprecated"], "description": "How mature the app is, in the same words the Ecosystem page uses for projects."},
    "categories": {"type": "array", "uniqueItems": true, "maxItems": 6, "items": {"type": "string", "maxLength": 30}, "description": "Words a scientist would search for: tasks, synonyms, methods, tools."},
    "doi": {"type": "string", "pattern": "^10\\.\\d{4,9}/\\S+$(?!\\n)", "description": "DOI of the paper to cite (not a software archive)."},
    "homepage": {"$ref": "#/$defs/https"},
    "docs_url": {"$ref": "#/$defs/https"},
    "icon": {"$ref": "#/$defs/repoPath", "description": "Square PNG in the repository (SVG is not accepted: it can carry scripts)."},
    "screenshots": {
      "type": "array", "minItems": 1, "maxItems": 6,
      "items": {
        "type": "object", "additionalProperties": false, "required": ["path", "caption"],
        "properties": {"path": {"$ref": "#/$defs/repoPath"}, "caption": {"type": "string", "minLength": 3, "maxLength": 140}}
      },
      "description": "Images in the repository; captions double as alt text."
    }
  },
  "$defs": {
    "hostRequirement": {
      "type": "object", "additionalProperties": false,
      "anyOf": [{"required": ["min_version"]}, {"required": ["features"]}],
      "properties": {
        "min_version": {"type": "string", "pattern": "^v?[0-9]+(\\.[0-9]+)*([-_.]?(a|alpha|b|beta|c|rc|pre|preview)[-_.]?[0-9]*)?([-_.]?(post|rev|r)[-_.]?[0-9]*)?([-_.]?dev[-_.]?[0-9]*)?$", "description": "A release tag of the plugin, with or without v (0.1.0-alpha.6, v0.1.0, 0.2.0rc1). Quote it if YAML could read it as a number (\"0.2\")."},
        "features": {"type": "array", "uniqueItems": true, "minItems": 1, "items": {"type": "string", "pattern": "^[a-z][a-z0-9_]*$"}, "description": "Host feature ids the app needs (LabConstrictor-Tools docs/HOST_FEATURES.md), for example shapes_owner_v1."},
        "why": {"type": "string", "minLength": 3, "maxLength": 160, "pattern": "^[^\\n]+$", "description": "One line shown to the person, saying what the newer plugin brings."},
        "required": {"type": "boolean", "description": "true: an older plugin refuses the app's tools instead of warning. Use it only when the tools really fail."}
      }
    },
    "facts": {"type": "array", "minItems": 1, "maxItems": 6, "items": {"type": "string", "minLength": 5, "maxLength": 200}},
    "https": {"type": "string", "pattern": "^https://[^\\s/@]+(/[^\\s]*)?$(?!\\n)"},
    "repoPath": {"type": "string", "pattern": "^(?!/)(?!.*\\.\\.)[A-Za-z0-9_./ -]+\\.(png|jpg|jpeg|gif)$(?!\\n)", "description": "Path inside the repository, no leading slash."},
    "link": {
      "type": "object", "additionalProperties": false, "required": ["label", "url"],
      "properties": {"label": {"type": "string", "minLength": 2, "maxLength": 60}, "url": {"type": "string", "pattern": "^https://colab\\.research\\.google\\.com/[^\\s]+$(?!\\n)"}}
    },
    "author": {
      "type": "object", "additionalProperties": false, "required": ["name"],
      "properties": {"name": {"type": "string", "minLength": 2}, "orcid": {"type": "string", "pattern": "^\\d{4}-\\d{4}-\\d{4}-\\d{3}[\\dX]$(?!\\n)"}}
    }
  }
}
