# seeds/cms/domain.yaml
#
# Headless CMS with Media Library — ADL Domain Definition
# ADL version: 0.3.1
#
# Resources:  MediaItem, Category, Article, SiteSettings
# Relations:  Article belongsTo Category
#
# ADL features exercised:
#   Phase 1      — CRUD generation, REST route mapping, RBAC permissions
#   Phase 6      — belongsTo, ?include=
#   Phase 7      — filter, sort, paginate, search, count
#   Phase 8      — Rich types: media, svg, blocks, icon, font, color, url, slug
#   Phase 9      — $merge (Timestamp, SoftDelete), $ref objects/enums/blocks
#   Phase 10     — UUID primary keys, custom indexes
#   Phase 12     — Soft delete on Article, audit trail on Article
#   Phase 14     — Field-level permissions (status, scheduledFor, seo)
#
# New features (first exercised in POC-04):
#   media type     — Featured images, gallery arrays, file attachments
#   svg type       — Brand logo with sanitization
#   blocks type    — Article body (paragraph, heading, image, code, etc.)
#   custom blocks  — Callout and PromoCard domain-level block types
#   icon type      — Category icon reference
#   font type      — SiteSettings typography
#   singleton      — SiteSettings (disable_create, disable_delete, disable_list)
#   media library  — Upload action, references tracking, orphan detection
#   SVG sanitize   — Script injection stripped on write
#   scheduled pub  — scheduledFor field + cross-field validation

domain:
  name: cms
  version: "1.0.0"
  description: >
    Headless CMS demonstrating media library, SVG sanitization, block editor
    content, custom block types, singleton resources, icon/font/color types,
    scheduled publishing, and media reference tracking with orphan detection.

  api: {}

  # ─── Shared objects ────────────────────────────────────────────
  objects:
    Timestamp:
      createdAt: { type: datetime, autoNow: create }
      updatedAt: { type: datetime, autoNow: update }

    SoftDelete:
      deletedAt: { type: datetime }

    SeoMeta:
      metaTitle:       { type: string, maxLength: 60 }
      metaDescription: { type: text,   maxLength: 160 }
      ogTitle:         { type: string, maxLength: 60 }
      ogImage:         { type: media,  accept: ["image/*"] }
      noIndex:         { type: boolean, default: false }

    Typography:
      headingFont: { type: font }
      bodyFont:    { type: font }
      monoFont:    { type: font }

    BrandColors:
      primary:    { type: color }
      secondary:  { type: color }
      accent:     { type: color }
      background: { type: color }
      text:       { type: color }

  # ─── Shared enums ──────────────────────────────────────────────
  enums:
    ArticleStatus:
      values:
        draft:
          label: "Draft"
          description: "Work in progress, not visible to public"
          color: "#6b7280"
        review:
          label: "In Review"
          description: "Submitted for editorial review"
          color: "#f59e0b"
        scheduled:
          label: "Scheduled"
          description: "Approved, will auto-publish at scheduledFor date"
          color: "#8b5cf6"
        published:
          label: "Published"
          description: "Live and publicly visible"
          color: "#22c55e"
        archived:
          label: "Archived"
          description: "Removed from public listing"
          color: "#ef4444"

  # ─── Custom blocks ────────────────────────────────────────────
  blocks:
    Callout:
      label: "Callout"
      hasContent: true
      props:
        style:
          type: enum
          values: [info, warning, tip, danger]
          default: info
        title:
          type: string
          maxLength: 100
          required: false

    PromoCard:
      label: "Promo Card"
      hasContent: false
      props:
        heading:  { type: string,  required: true }
        subtext:  { type: text,    required: false }
        image:    { type: media,   accept: ["image/*"] }
        ctaText:  { type: string,  default: "Learn More" }
        ctaUrl:   { type: url,     required: true }
        theme:    { type: enum,    values: [light, dark], default: light }

  # ─── Shared properties ────────────────────────────────────────
  properties:
    slug:
      type: slug
      unique: true
      maxLength: 255

  # ─── Resources ─────────────────────────────────────────────────
  resources:

    # ╔══════════════════════════════════════════════════════════════╗
    # ║  MediaItem                                                   ║
    # ╚══════════════════════════════════════════════════════════════╝
    MediaItem:
      description: >
        Represents a single uploaded file in the domain media library.
        Created automatically on upload via POST /media/upload.
        Deletion is blocked if the item is referenced by any other resource
        (returns 409 CONFLICT).

      api:
        path: /media

      object:
        id:
          type: uuid
          primaryKey: true

        filename:
          type: string
          required: true
          immutable: true
          maxLength: 255

        mimeType:
          type: string
          required: true
          immutable: true
          maxLength: 100

        size:
          type: integer
          required: true
          immutable: true
          description: "File size in bytes"

        width:
          type: integer
          description: "Image width in pixels (images only)"

        height:
          type: integer
          description: "Image height in pixels (images only)"

        altText:
          type: string
          maxLength: 255

        caption:
          type: text

        folder:
          type: string
          maxLength: 255
          default: "/"

        uploadedById:
          type: string
          immutable: true
          maxLength: 100

        $merge:
          - { $ref: '#/objects/Timestamp' }

      indexes:
        - fields: [mimeType]
          description: "Filter media by type"
        - fields: [folder]
          description: "Browse media by folder"
        - fields: [uploadedById]
          description: "List media by uploader"

      features:
        search: true

      permissions:
        list:   [authenticated]
        read:   [authenticated]
        create: [authenticated]
        update: [owner, editor, admin]
        delete: [admin]

    # ╔══════════════════════════════════════════════════════════════╗
    # ║  Category                                                    ║
    # ╚══════════════════════════════════════════════════════════════╝
    Category:
      description: >
        Article taxonomy. Demonstrates icon type and caching.
        Admin-managed vocabulary.

      object:
        id:
          type: uuid
          primaryKey: true

        name:
          type: string
          required: true
          unique: true
          maxLength: 100

        slug:
          $ref: '#/properties/slug'
          from: name

        description:
          type: text
          maxLength: 500

        icon:
          type: icon
          description: "Icon reference string, e.g. 'lucide:tag'"

        color:
          type: color

        $merge:
          - { $ref: '#/objects/Timestamp' }

      permissions:
        list:   ["*"]
        read:   ["*"]
        create: [admin]
        update: [admin]
        delete: [admin]

      features:
        caching: true
        cacheTtlSeconds: 600

    # ╔══════════════════════════════════════════════════════════════╗
    # ║  Article                                                     ║
    # ╚══════════════════════════════════════════════════════════════╝
    Article:
      description: >
        Primary content resource. Demonstrates blocks body (with custom
        Callout and PromoCard blocks), media fields (featuredImage, gallery,
        attachments), virtual computed readingTimeMinutes, versioning,
        scheduled publishing, SEO component, soft delete, and audit.

      object:
        id:
          type: uuid
          primaryKey: true

        title:
          type: string
          required: true
          maxLength: 200

        slug:
          $ref: '#/properties/slug'
          from: title

        excerpt:
          type: text
          maxLength: 400

        categoryId:
          type: uuid

        authorId:
          type: string
          required: true
          immutable: true
          maxLength: 100

        authorName:
          type: string
          maxLength: 200

        status:
          $ref: '#/enums/ArticleStatus'
          default: draft

        scheduledFor:
          type: datetime
          description: "Future date for auto-publish when status is 'scheduled'"

        featuredImage:
          type: media
          accept: ["image/jpeg", "image/png", "image/webp"]
          maxSize: "10mb"

        gallery:
          type: array
          items:
            type: media
            accept: ["image/*"]
            maxSize: "10mb"
          maxItems: 20

        attachments:
          type: array
          items:
            type: media
            accept: ["application/pdf", ".docx", ".xlsx", ".zip"]
            maxSize: "50mb"
          maxItems: 10

        body:
          type: blocks
          required: true
          maxBlocks: 300
          allowedBlocks:
            - paragraph
            - heading
            - image
            - code
            - quote
            - list
            - table
            - embed
            - divider
            - Callout
            - PromoCard

        seo:
          type: component
          object: { $ref: '#/objects/SeoMeta' }

        $merge:
          - { $ref: '#/objects/Timestamp' }
          - { $ref: '#/objects/SoftDelete' }

      computed:
        readingTimeMinutes:
          type: integer
          formula: "ceil(wordCount(body) / 250)"
          stored: false
          description: "Virtual — estimated reading time based on body word count"

      relationships:
        category:
          type: belongsTo
          target: Category
          foreignKey: categoryId

      indexes:
        - fields: [status]
          description: "Filter articles by status"
        - fields: [authorId]
          description: "List articles by author"
        - fields: [scheduledFor]
          description: "Query scheduled articles"
        - fields: [categoryId]
          description: "List articles by category"
        - fields: [status, scheduledFor]
          description: "Composite: scheduled articles pending publish"

      features:
        softDelete: true
        audit: true
        versioning: true
        search: true
        export: true

      audit:
        track: [title, status, body, featuredImage, scheduledFor]
        exclude: [updatedAt, deletedAt]

      validation:
        scheduledForFuture:
          rule: "scheduledFor == null || scheduledFor > now()"
          message: "Scheduled publish date must be in the future"

        scheduledRequiresPublishStatus:
          rule: "scheduledFor == null || status == 'scheduled'"
          message: "Set status to 'scheduled' when providing a scheduled date"

      permissions:
        list:   ["*"]
        read:   ["*"]
        create: [authenticated]
        update: [owner, editor, admin]
        delete: [admin]

        fields:
          status:
            read:  ["*"]
            write: [editor, admin]

          scheduledFor:
            read:  ["*"]
            write: [editor, admin]

          seo:
            read:  [authenticated]
            write: [editor, admin]

    # ╔══════════════════════════════════════════════════════════════╗
    # ║  SiteSettings (Singleton)                                    ║
    # ╚══════════════════════════════════════════════════════════════╝
    SiteSettings:
      description: >
        Global site configuration. Singleton resource — one record, no list
        or create. Read by anyone via GET /site-settings/current, written
        by admin only via PATCH /site-settings/current.

      object:
        id:
          type: uuid
          primaryKey: true

        siteName:
          type: string
          required: true
          maxLength: 100

        tagline:
          type: string
          maxLength: 200

        baseUrl:
          type: url

        logo:
          type: svg
          maxSize: "100kb"
          sanitize: true

        favicon:
          type: media
          accept: ["image/png", "image/x-icon", "image/svg+xml"]
          maxSize: "1mb"

        typography:
          type: component
          object: { $ref: '#/objects/Typography' }

        brandColors:
          type: component
          object: { $ref: '#/objects/BrandColors' }

        defaultSeo:
          type: component
          object: { $ref: '#/objects/SeoMeta' }

        analyticsId:
          type: string
          maxLength: 50

        maintenanceMode:
          type: boolean
          default: false

        $merge:
          - { $ref: '#/objects/Timestamp' }

      api:
        path: /site-settings
        disable_list: true
        disable_create: true
        disable_delete: true
        custom_operations:
          - name: current
            method: GET
            path: /current
            permission: ["*"]

      permissions:
        read:   ["*"]
        update: [admin]
