{
  "openapi": "3.1.0",
  "info": {
    "title": "Imprezowe Pudełka — Public API",
    "version": "1.0.0",
    "description": "Publiczne, tylko-do-odczytu API serwisu Imprezowe Pudełka (catering pudełkowy, Kraków). Udostępnia menu boxów z cenami, aktualności, zajęte terminy oraz formularz kontaktowy. Składanie i opłacanie zamówień odbywa się przez interfejs WWW, nie przez API.",
    "contact": {
      "name": "Imprezowe Pudełka",
      "email": "kontakt@imprezowepudelka.pl",
      "url": "https://imprezowepudelka.pl"
    },
    "license": { "name": "Proprietary" }
  },
  "servers": [
    { "url": "https://imprezowepudelka.pl", "description": "Produkcja" }
  ],
  "externalDocs": {
    "description": "llms.txt — kontekst dla agentów AI",
    "url": "https://imprezowepudelka.pl/llms.txt"
  },
  "tags": [
    { "name": "catalog", "description": "Menu boxów cateringowych" },
    { "name": "availability", "description": "Dostępność terminów odbioru i dostawy" },
    { "name": "news", "description": "Aktualności" },
    { "name": "contact", "description": "Formularz kontaktowy" }
  ],
  "paths": {
    "/api/products": {
      "get": {
        "operationId": "listProducts",
        "tags": ["catalog"],
        "summary": "Lista aktywnych boxów z cenami",
        "description": "Zwraca wszystkie aktywne boxy cateringowe wraz z ceną (w groszach), opisem składu, kategorią i kolejnością wyświetlania. Bez uwierzytelniania.",
        "responses": {
          "200": {
            "description": "Lista produktów",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ProductList" }
              }
            }
          },
          "500": {
            "description": "Błąd serwera",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    },
    "/api/news": {
      "get": {
        "operationId": "listNews",
        "tags": ["news"],
        "summary": "Aktywne aktualności",
        "description": "Zwraca do 2 najnowszych aktywnych aktualności. Bez uwierzytelniania.",
        "responses": {
          "200": {
            "description": "Lista aktualności",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "news": { "type": "array", "items": { "$ref": "#/components/schemas/NewsItem" } }
                  },
                  "required": ["news"]
                }
              }
            }
          },
          "500": {
            "description": "Błąd serwera",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    },
    "/api/blocked-times": {
      "get": {
        "operationId": "getBlockedTimes",
        "tags": ["availability"],
        "summary": "Zajęte terminy odbioru/dostawy",
        "description": "Zwraca zablokowane (niedostępne) godziny pogrupowane wg daty. Pozwala agentowi sprawdzić wolne terminy przed sugerowaniem zamówienia.",
        "parameters": [
          {
            "name": "start",
            "in": "query",
            "required": false,
            "description": "Początek zakresu dat (YYYY-MM-DD)",
            "schema": { "type": "string", "format": "date" }
          },
          {
            "name": "end",
            "in": "query",
            "required": false,
            "description": "Koniec zakresu dat (YYYY-MM-DD)",
            "schema": { "type": "string", "format": "date" }
          },
          {
            "name": "deliveryMethod",
            "in": "query",
            "required": false,
            "description": "Filtr metody realizacji",
            "schema": { "type": "string", "enum": ["pickup", "delivery"] }
          }
        ],
        "responses": {
          "200": {
            "description": "Zajęte terminy pogrupowane wg daty",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": { "type": "boolean" },
                    "blocked": {
                      "type": "object",
                      "description": "Mapa data → lista zajętych godzin HH:MM",
                      "additionalProperties": {
                        "type": "array",
                        "items": { "type": "string", "example": "10:30" }
                      }
                    }
                  },
                  "required": ["ok", "blocked"]
                }
              }
            }
          },
          "500": {
            "description": "Błąd serwera",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    },
    "/api/contact": {
      "post": {
        "operationId": "sendContactMessage",
        "tags": ["contact"],
        "summary": "Wyślij zapytanie / prośbę o wycenę",
        "description": "Wysyła wiadomość z formularza kontaktowego. Wymaga ważnego tokenu Cloudflare Turnstile (ochrona antyspamowa) — uzyskiwanego w przeglądarce, dlatego endpoint nie jest przeznaczony do wywołań w pełni autonomicznych.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ContactRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Wiadomość wysłana",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "ok": { "type": "boolean" }, "id": { "type": "string" } }
                }
              }
            }
          },
          "400": {
            "description": "Błąd walidacji lub nieudana weryfikacja antyspamowa",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "501": {
            "description": "Usługa e-mail nieskonfigurowana",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Product": {
        "type": "object",
        "description": "Box cateringowy",
        "properties": {
          "id": { "type": "integer", "description": "Identyfikator produktu" },
          "sku": { "type": "string", "description": "Unikalny kod produktu", "example": "box-antipasti" },
          "name": { "type": "string", "example": "Box antipasti" },
          "price": { "type": "integer", "description": "Cena w groszach (449,00 zł = 44900)", "example": 44900 },
          "description": { "type": "string", "description": "Skład boxa" },
          "category": { "type": "string", "description": "Kategoria/-e oddzielone przecinkiem", "example": "wytrawny" },
          "image_url": { "type": "string", "example": "./img/menu/antipasti.webp" },
          "display_order": { "type": "integer" }
        },
        "required": ["id", "sku", "name", "price"]
      },
      "ProductList": {
        "type": "object",
        "properties": {
          "products": { "type": "array", "items": { "$ref": "#/components/schemas/Product" } }
        },
        "required": ["products"]
      },
      "NewsItem": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "title": { "type": "string" },
          "content": { "type": "string" },
          "image_url": { "type": "string" },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "ContactRequest": {
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "email": { "type": "string", "format": "email" },
          "phone": { "type": "string" },
          "topic": { "type": "string" },
          "message": { "type": "string", "minLength": 10 },
          "rodo": { "type": "boolean", "description": "Zgoda na przetwarzanie danych (RODO)" },
          "turnstileToken": { "type": "string", "description": "Token Cloudflare Turnstile" }
        },
        "required": ["name", "email", "message", "rodo", "turnstileToken"]
      },
      "Error": {
        "type": "object",
        "description": "Standardowa odpowiedź błędu",
        "properties": {
          "error": { "type": "string", "description": "Komunikat błędu" }
        },
        "required": ["error"]
      }
    }
  }
}
