{
  "name": "Gmail-Calendar",
  "nodes": [
    {
      "parameters": {},
      "id": "cal-trigger",
      "name": "Workflow Input",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "typeVersion": 1,
      "position": [
        240,
        300
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": false
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "cal-1",
                    "leftValue": "={{ $json.calendar_action || $json.action }}",
                    "rightValue": "check_today",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ]
              },
              "renameOutput": true,
              "outputKey": "Check Today"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": false
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "cal-2",
                    "leftValue": "={{ $json.calendar_action || $json.action }}",
                    "rightValue": "check_next_meeting",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ]
              },
              "renameOutput": true,
              "outputKey": "Next Meeting"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": false
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "cal-3",
                    "leftValue": "={{ $json.calendar_action || $json.action }}",
                    "rightValue": "check_24h",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ]
              },
              "renameOutput": true,
              "outputKey": "Check 24h"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": false
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "cal-4",
                    "leftValue": "={{ $json.calendar_action || $json.action }}",
                    "rightValue": "availability",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ]
              },
              "renameOutput": true,
              "outputKey": "Availability"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": false
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "cal-5",
                    "leftValue": "={{ $json.calendar_action || $json.action }}",
                    "rightValue": "detect_meeting_worthy",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ]
              },
              "renameOutput": true,
              "outputKey": "Detect Meeting"
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra"
        }
      },
      "id": "cal-router",
      "name": "Action Router",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3,
      "position": [
        460,
        300
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT * FROM calendar_events_cache WHERE event_date = CURRENT_DATE AND cache_expires_at > NOW() ORDER BY start_time ASC",
        "options": {}
      },
      "id": "check-cache-today",
      "name": "Check Today Cache",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        700,
        140
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "cache-check",
              "leftValue": "={{ $json.event_id }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "exists"
              }
            }
          ],
          "combinator": "and"
        }
      },
      "id": "cache-hit",
      "name": "Cache Hit?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        920,
        140
      ]
    },
    {
      "parameters": {
        "resource": "event",
        "operation": "getAll",
        "calendar": {
          "__rl": true,
          "value": "primary",
          "mode": "list"
        },
        "returnAll": false,
        "limit": 50,
        "options": {
          "timeMin": "={{ $now.startOf('day').toISO() }}",
          "timeMax": "={{ $now.endOf('day').toISO() }}",
          "singleEvents": true,
          "orderBy": "startTime"
        }
      },
      "id": "get-today-events",
      "name": "Get Today Events",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1.2,
      "position": [
        1140,
        200
      ],
      "credentials": {
        "googleCalendarOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const events = $input.all();\nconst results = [];\nfor (const item of events) {\n  const e = item.json;\n  results.push({\n    json: {\n      event_id: e.id || '',\n      event_date: new Date().toISOString().split('T')[0],\n      summary: e.summary || '',\n      start_time: e.start?.dateTime || e.start?.date || '',\n      end_time: e.end?.dateTime || e.end?.date || '',\n      attendees: (e.attendees || []).map(a => a.email).join(','),\n      location: e.location || '',\n      calendar_id: 'primary'\n    }\n  });\n}\nreturn results.length > 0 ? results : [{ json: { message: 'No events today' } }];"
      },
      "id": "normalize-events",
      "name": "Normalize Events",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1360,
        200
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO calendar_events_cache (event_id, event_date, summary, start_time, end_time, attendees, location, calendar_id, cached_at, cache_expires_at) VALUES ($1, $2::date, $3, $4::timestamptz, $5::timestamptz, $6, $7, 'primary', NOW(), NOW() + INTERVAL '1 hour') ON CONFLICT (event_id) DO UPDATE SET summary = $3, start_time = $4::timestamptz, end_time = $5::timestamptz, attendees = $6, cached_at = NOW(), cache_expires_at = NOW() + INTERVAL '1 hour'",
        "options": {
          "queryParameters": "={{ $json.event_id }},={{ $json.event_date }},={{ $json.summary }},={{ $json.start_time }},={{ $json.end_time }},={{ $json.attendees }},={{ $json.location }}"
        }
      },
      "id": "update-cache",
      "name": "Update Cache",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        1580,
        200
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "resource": "event",
        "operation": "getAll",
        "calendar": {
          "__rl": true,
          "value": "primary",
          "mode": "list"
        },
        "returnAll": false,
        "limit": 50,
        "options": {
          "timeMin": "={{ $now.toISO() }}",
          "timeMax": "={{ $now.plus({ hours: 24 }).toISO() }}",
          "singleEvents": true,
          "orderBy": "startTime"
        }
      },
      "id": "get-24h-events",
      "name": "Get 24h Events",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1.2,
      "position": [
        700,
        360
      ],
      "credentials": {
        "googleCalendarOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const events = $input.all().map(i => i.json);\nconst now = new Date();\n// Find next event\nconst upcoming = events.filter(e => new Date(e.start?.dateTime || e.start?.date) > now);\nif (upcoming.length === 0) return [{ json: { message: 'No upcoming meetings in next 24h' } }];\nconst next = upcoming[0];\nreturn [{ json: { next_meeting_summary: next.summary || '', next_meeting_start: next.start?.dateTime || next.start?.date, next_meeting_end: next.end?.dateTime || next.end?.date, next_meeting_attendees: (next.attendees || []).map(a => a.email).join(','), next_meeting_location: next.location || '', total_upcoming: upcoming.length } }];"
      },
      "id": "find-next",
      "name": "Find Next Meeting",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        920,
        320
      ]
    },
    {
      "parameters": {
        "jsCode": "const events = $input.all().map(i => i.json);\nconst today = new Date();\nconst startOfDay = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 8, 0);\nconst endOfDay = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 18, 0);\n\n// Build busy slots\nconst busy = events.filter(e => e.start?.dateTime).map(e => ({\n  start: new Date(e.start.dateTime),\n  end: new Date(e.end.dateTime)\n})).sort((a, b) => a.start - b.start);\n\n// Find free slots (minimum 30 min)\nconst free = [];\nlet cursor = startOfDay;\nfor (const slot of busy) {\n  if (slot.start > cursor) {\n    const gap = (slot.start - cursor) / 60000;\n    if (gap >= 30) free.push({ from: cursor.toISOString(), to: slot.start.toISOString(), minutes: Math.round(gap) });\n  }\n  if (slot.end > cursor) cursor = slot.end;\n}\nif (endOfDay > cursor) {\n  const gap = (endOfDay - cursor) / 60000;\n  if (gap >= 30) free.push({ from: cursor.toISOString(), to: endOfDay.toISOString(), minutes: Math.round(gap) });\n}\n\nreturn [{ json: { availability_windows: free, total_free_slots: free.length, total_free_minutes: free.reduce((s, f) => s + f.minutes, 0) } }];"
      },
      "id": "find-availability",
      "name": "Find Availability",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        920,
        480
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://super-agent-production.up.railway.app/chat",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Token",
              "value": "={{ $env.SUPER_AGENT_PASSWORD }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ message: 'Does this email warrant creating a calendar event? From: ' + ($json.sender || '') + ', Subject: ' + ($json.subject || '') + ', Snippet: ' + ($json.snippet || '') + '. Reply ONLY with JSON: {\"create_event\": true/false, \"suggested_title\": \"...\", \"suggested_date\": \"YYYY-MM-DD\", \"suggested_time\": \"HH:MM\", \"reason\": \"...\"}', session_id: 'gmail-calendar' }) }}",
        "options": {
          "timeout": 30000
        }
      },
      "id": "detect-meeting",
      "name": "Detect Meeting-Worthy",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        700,
        600
      ]
    },
    {
      "parameters": {
        "jsCode": "const raw = $input.first().json.response || '';\nlet parsed;\ntry {\n  const match = raw.match(/\\{[\\s\\S]*\\}/);\n  parsed = JSON.parse(match ? match[0] : raw);\n} catch (e) {\n  parsed = { create_event: false, reason: 'Could not parse AI response' };\n}\nreturn [{ json: parsed }];"
      },
      "id": "parse-meeting-response",
      "name": "Parse Meeting Response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        920,
        600
      ]
    }
  ],
  "connections": {
    "Workflow Input": {
      "main": [
        [
          {
            "node": "Action Router",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Action Router": {
      "main": [
        [
          {
            "node": "Check Today Cache",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Get 24h Events",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Get 24h Events",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Get 24h Events",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Detect Meeting-Worthy",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Today Cache": {
      "main": [
        [
          {
            "node": "Cache Hit?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cache Hit?": {
      "main": [
        [],
        [
          {
            "node": "Get Today Events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Today Events": {
      "main": [
        [
          {
            "node": "Normalize Events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Events": {
      "main": [
        [
          {
            "node": "Update Cache",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get 24h Events": {
      "main": [
        [
          {
            "node": "Find Next Meeting",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Find Availability",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Detect Meeting-Worthy": {
      "main": [
        [
          {
            "node": "Parse Meeting Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "saveManualExecutions": true,
    "saveExecutionProgress": true
  },
  "meta": {
    "templateCredsSetupCompleted": false,
    "description": "Gmail Calendar Intelligence sub-workflow: checks today's events, finds next meeting, calculates availability, detects meeting-worthy emails using AI."
  },
  "tags": []
}