# Post Metadata Schema
#
# Canonical contract for any content item (blog, article, page, etc.).
# Consumed by:
#   - ContentIndexer:  validates *.ncl files, builds in-memory index
#   - TypeDialog:      generates web/CLI/TUI forms from this schema
#   - DB migration:    struct maps directly to posts table
#
# Usage — preferred (make_post factory):
#
#   let schema = import "site/nickel/content/metadata/post_metadata.ncl" in
#   schema.make_post {
#     title    = "My Rust Post",
#     slug     = "my-rust-post",
#     date     = "2024-01-15",
#     category = "rust",
#   }
#
# The factory merges site defaults (Layer 1) with the provided overrides
# (Layer 2) then applies PostMetadata as a contract for type checking.

let c = import "contracts.ncl" in
let d = import "defaults.ncl" in
let g = import "graph_contracts.ncl" in

{
  PostMetadata = {
    # --- Identity ---

    id
      | String
      | c.NonEmptyString
      | doc "Unique stable identifier. Inferred from filename stem when absent. Never changes after publication."
      | optional,

    title
      | String
      | c.NonEmptyString
      | doc "Post title shown in listings, headings, and browser tab",

    slug
      | String
      | c.SlugContract
      | doc "URL path segment: lowercase, hyphens only. E.g. 'my-rust-post-2024'",

    subtitle
      | String
      | doc "Secondary heading displayed under the title in the post view"
      | optional,

    excerpt
      | String
      | doc "Short summary (1-3 sentences) for index listings and SEO meta description"
      | optional,

    # --- Content file reference ---

    content_file
      | String
      | doc "Filename of the content body relative to this *.ncl file. Inferred from slug when absent."
      | optional,

    # --- Publication ---

    author
      | String
      | c.NonEmptyString
      | doc "Author display name shown in post header and listings"
      | default = "Jesús Pérez Lorenzo",

    date
      | String
      | c.DateContract
      | doc "Original publication date in YYYY-MM-DD format",

    updated_at
      | String
      | c.DateContract
      | doc "Date of last significant update in YYYY-MM-DD format"
      | optional,

    published
      | Bool
      | doc "False hides the post from all index listings and the API"
      | default = true,

    featured
      | Bool
      | doc "Pins to the top of the category listing and enables featured styling"
      | default = false,

    draft
      | Bool
      | doc "Draft posts are excluded from the index and not served by /api/content-render"
      | default = false,

    # --- Taxonomy ---

    category
      | String
      | c.NonEmptyString
      | doc "Primary category slug — must match the parent directory name (e.g. 'rust', 'devops')",

    tags
      | Array String
      | doc "Secondary classification tags for filtering and related-content discovery"
      | default = [],

    # --- Display hints ---

    read_time
      | String
      | c.ReadTimeContract
      | doc "Estimated reading time for the listing card. E.g. '8 min read'. Auto-derived from word count when absent."
      | optional,

    sort_order
      | Number
      | doc "Manual position within the category listing. Lower values appear first. 0 = no preference."
      | default = 0,

    # --- SEO ---

    image_url
      | String
      | doc "Open Graph and social share image. Absolute URL or root-relative path."
      | optional,

    thumbnail
      | String
      | doc "Card thumbnail image. Absolute URL or root-relative path. Falls back to image_url when absent."
      | optional,

    # --- Multilingual ---

    translations
      | { _ : c.TranslationEntry }
      | doc "Map of language-code → { id, slug } for the translation. E.g. { es = { id = 'my-post', slug = 'mi-post-en-rust' } }."
      | default = {},

    # --- Knowledge graph ---

    graph
      | g.GraphMeta
      | doc "Explicit relationships to other content items and on+re ontology nodes. Consumed by the content-graph feature. Ignored when the feature is not active."
      | default = {},
  },

  # Factory function — Layer 1 (defaults) & Layer 2 (overrides) | contract.
  # Not exported to JSON; accessible only via Nickel import.
  make_post
    | not_exported
    | doc "Create a validated post metadata record. Provide at minimum: title, slug, date, category."
    = fun overrides =>
        d & overrides | PostMetadata,
}
