{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://placemoir.app/schemas/library-v1.json",
  "title": "Placemoir library",
  "description": "library.json inside a .placemoirarchive, or on its own. Format version 1. Spec: https://placemoir.app/archive-format.html",
  "type": "object",
  "required": [
    "format",
    "formatVersion"
  ],
  "properties": {
    "format": {
      "const": "placemoir-library"
    },
    "formatVersion": {
      "type": "integer",
      "minimum": 1,
      "description": "1 for this spec. Placemoir refuses versions newer than it knows."
    },
    "exportedAt": {
      "$ref": "#/$defs/date"
    },
    "generator": {
      "type": "string",
      "description": "What wrote the file, e.g. \"Placemoir 1.0 (3) for iOS\" or \"Day One converter\"."
    },
    "places": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/place"
      }
    }
  },
  "$defs": {
    "id": {
      "type": "string",
      "minLength": 1,
      "description": "Any stable string, unique among items of its kind in the file. Placemoir writes UUIDs; other strings are mapped to a UUID the same way every time, so importing the same file twice adds nothing new."
    },
    "date": {
      "type": "string",
      "description": "ISO 8601: 2026-11-01T18:30:00Z, 2026-11-01T18:30:00.250+01:00, 2026-11-01T18:30:00 (local time), or 2026-11-01 (midday local time).",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?(Z|[+-]\\d{2}:?\\d{2})?)?$"
    },
    "place": {
      "type": "object",
      "required": [
        "name"
      ],
      "properties": {
        "id": {
          "$ref": "#/$defs/id"
        },
        "name": {
          "type": "string",
          "minLength": 1,
          "pattern": "\\S"
        },
        "category": {
          "enum": [
            "restaurant",
            "cafe",
            "bar",
            "hotel",
            "park",
            "museum",
            "attraction",
            "other"
          ],
          "description": "Write one of these, in lowercase. Placemoir also accepts other case, and imports unknown values as other."
        },
        "address": {
          "type": "string",
          "description": "Street address."
        },
        "locality": {
          "type": "string",
          "description": "City or neighbourhood, e.g. \"Paris 11e\"."
        },
        "country": {
          "type": "string"
        },
        "latitude": {
          "type": "number",
          "minimum": -90,
          "maximum": 90
        },
        "longitude": {
          "type": "number",
          "minimum": -180,
          "maximum": 180
        },
        "wantToGo": {
          "type": "boolean",
          "description": "On the Want to go list. Defaults to true for a place with no visits, false otherwise."
        },
        "notes": {
          "type": "string",
          "description": "Notes about the place itself, such as why you saved it."
        },
        "createdAt": {
          "$ref": "#/$defs/date"
        },
        "visits": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/visit"
          }
        }
      },
      "dependentRequired": {
        "latitude": [
          "longitude"
        ],
        "longitude": [
          "latitude"
        ]
      }
    },
    "visit": {
      "type": "object",
      "required": [
        "date"
      ],
      "properties": {
        "id": {
          "$ref": "#/$defs/id"
        },
        "date": {
          "$ref": "#/$defs/date"
        },
        "endDate": {
          "$ref": "#/$defs/date",
          "description": "Check-out, for stays. Not before date."
        },
        "rating": {
          "type": "number",
          "minimum": 1,
          "maximum": 5,
          "description": "Stars in half steps. Other values round to the nearest half."
        },
        "goBack": {
          "enum": [
            "yes",
            "maybe",
            "no"
          ],
          "description": "Would you go back? Write it in lowercase."
        },
        "notes": {
          "type": "string"
        },
        "tags": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "companions": {
          "type": "string",
          "description": "Who you were with, as free text."
        },
        "weather": {
          "type": "object",
          "properties": {
            "summary": {
              "type": "string",
              "examples": [
                "Partly cloudy"
              ]
            },
            "symbol": {
              "type": "string",
              "description": "An SF Symbols name.",
              "examples": [
                "cloud.sun"
              ]
            },
            "temperatureCelsius": {
              "type": "number"
            }
          }
        },
        "isDraft": {
          "type": "boolean",
          "description": "Unfinished entry, shown in Drafts."
        },
        "createdAt": {
          "$ref": "#/$defs/date"
        },
        "updatedAt": {
          "$ref": "#/$defs/date"
        },
        "attachments": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/attachment"
          }
        }
      }
    },
    "attachment": {
      "type": "object",
      "required": [
        "path"
      ],
      "properties": {
        "id": {
          "$ref": "#/$defs/id"
        },
        "kind": {
          "enum": [
            "photo",
            "audio",
            "pdf",
            "file"
          ],
          "description": "Write it in lowercase. Guessed from the file name when missing."
        },
        "path": {
          "type": "string",
          "description": "Where the file is in the archive, relative to library.json, with / between folders, e.g. media/lisbon/tasca.jpg. No .. segments.",
          "minLength": 1
        },
        "filename": {
          "type": "string",
          "description": "Name shown in the app. Defaults to the last part of path."
        },
        "caption": {
          "type": "string"
        },
        "duration": {
          "type": "number",
          "minimum": 0,
          "description": "Seconds, for audio."
        },
        "createdAt": {
          "$ref": "#/$defs/date"
        }
      }
    }
  }
}
