{
  "name": "Crown Storage \u2014 Check Availability",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "check-availability",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "wh-1",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        240,
        300
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "const body = $input.first().json.body || {};\nconst tz = 'Europe/Prague';\nconst now = DateTime.now().setZone(tz);\n\nlet timeMin, timeMax, preferred_date;\n\nif (body.preferred_date) {\n  preferred_date = body.preferred_date;\n  const d = DateTime.fromISO(body.preferred_date, { zone: tz });\n  timeMin = d.startOf('day').toISO();\n  timeMax = d.endOf('day').toISO();\n} else {\n  preferred_date = null;\n  timeMin = now.toISO();\n  timeMax = now.plus({ days: 4 }).startOf('day').toISO();\n}\n\nreturn [{ json: { timeMin, timeMax, preferred_date } }];"
      },
      "id": "prep-1",
      "name": "Prepare Range",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        460,
        300
      ]
    },
    {
      "parameters": {
        "resource": "event",
        "operation": "getAll",
        "calendar": {
          "__rl": true,
          "value": "primary",
          "mode": "list",
          "cachedResultName": "primary"
        },
        "returnAll": true,
        "timeMin": "={{ $json.timeMin }}",
        "timeMax": "={{ $json.timeMax }}",
        "options": {
          "singleEvents": true,
          "orderBy": "startTime"
        }
      },
      "id": "cal-1",
      "name": "Get Calendar Events",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1.3,
      "position": [
        680,
        300
      ],
      "alwaysOutputData": true,
      "credentials": {
        "googleCalendarOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "try {\n  const TZ = 'Europe/Prague';\n  const businessHoursStart = 9;\n  const businessHoursEnd = 17;\n  const slotMinutes = 30;\n  const maxSlots = 6;\n\n  const range = $('Prepare Range').first().json;\n\n  const events = $input.all().map(i => i.json);\n  const busy = events\n    .filter(e => e.start && e.end && (e.start.dateTime || e.start.date))\n    .map(e => ({\n      start: DateTime.fromISO(e.start.dateTime || e.start.date),\n      end: DateTime.fromISO(e.end.dateTime || e.end.date)\n    }));\n\n  const slots = [];\n  const rangeStart = DateTime.fromISO(range.timeMin).setZone(TZ);\n  const rangeEnd = DateTime.fromISO(range.timeMax).setZone(TZ);\n\n  let cursor = rangeStart.set({ hour: businessHoursStart, minute: 0, second: 0, millisecond: 0 });\n  if (cursor < rangeStart) {\n    cursor = rangeStart.startOf('minute');\n    const cm = cursor.minute;\n    cursor = cursor.set({ minute: cm < 30 ? 30 : 0, second: 0, millisecond: 0 });\n    if (cm >= 30) cursor = cursor.plus({ hours: 1 });\n  }\n\n  while (cursor < rangeEnd && slots.length < maxSlots) {\n    const dow = cursor.weekday;\n    const hour = cursor.hour;\n\n    if (dow === 6 || dow === 7) {\n      cursor = cursor.plus({ days: 1 }).set({ hour: businessHoursStart, minute: 0, second: 0, millisecond: 0 });\n      continue;\n    }\n    if (hour >= businessHoursEnd) {\n      cursor = cursor.plus({ days: 1 }).set({ hour: businessHoursStart, minute: 0, second: 0, millisecond: 0 });\n      continue;\n    }\n    if (hour < businessHoursStart) {\n      cursor = cursor.set({ hour: businessHoursStart, minute: 0, second: 0, millisecond: 0 });\n      continue;\n    }\n\n    const slotStart = cursor;\n    const slotEnd = cursor.plus({ minutes: slotMinutes });\n    const overlaps = busy.some(b => slotStart < b.end && slotEnd > b.start);\n\n    if (!overlaps) {\n      slots.push({\n        date: slotStart.toFormat('yyyy-MM-dd'),\n        time: slotStart.toFormat('h:mm a'),\n        day: slotStart.toFormat('cccc')\n      });\n    }\n\n    cursor = cursor.plus({ minutes: slotMinutes });\n  }\n\n  return [{ json: { available_slots: slots } }];\n} catch (err) {\n  return [{ json: { available_slots: [], error: String(err) } }];\n}"
      },
      "id": "gen-1",
      "name": "Generate Slots",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        900,
        300
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ available_slots: $json.available_slots }) }}",
        "options": {}
      },
      "id": "rok-1",
      "name": "Respond",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        1120,
        300
      ]
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Prepare Range",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Range": {
      "main": [
        [
          {
            "node": "Get Calendar Events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Calendar Events": {
      "main": [
        [
          {
            "node": "Generate Slots",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Slots": {
      "main": [
        [
          {
            "node": "Respond",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}