#import "@preview/fletcher:0.5.8" as fletcher: diagram, node, edge

#set page(width: auto, height: auto, margin: 1.5em)
#set text(font: "Fira Sans", size: 9pt, fill: rgb("#1a1a2e"))

// === Color Palette ===
#let c-edge     = rgb("#e8f4f8")
#let c-gateway  = rgb("#fce4ec")
#let c-api      = rgb("#e8eaf6")
#let c-session  = rgb("#e0f2f1")
#let c-client   = rgb("#fff3e0")
#let c-infra    = rgb("#f3e5f5")
#let c-bus      = rgb("#fff9c4")
#let c-future   = rgb("#f5f5f5")

#let s-main = rgb("#37474f") + 0.8pt
#let s-data = rgb("#7b1fa2") + 0.8pt
#let s-bus  = rgb("#e65100") + 0.8pt
#let s-dim  = rgb("#bbb") + 0.6pt

#let label-sm(body) = text(size: 7pt, body)
#let label-xs(body) = text(size: 6.5pt, fill: rgb("#666"), body)
#let section(title) = {
  v(2em)
  align(center, text(size: 13pt, weight: "bold", fill: rgb("#37474f"), title))
  v(0.6em)
}

// ============================================================
// TITLE
// ============================================================
#align(center)[
  #text(size: 18pt, weight: "bold")[ajet-chat]
  #v(0.2em)
  #text(size: 9pt, fill: rgb("#666"))[System Architecture — Clojure · http-kit · PostgreSQL · Datastar · MinIO]
]

// ============================================================
// 1. SYSTEM OVERVIEW
// ============================================================
#section[1 · System Overview]

#align(center)[
  #diagram(
    spacing: (3.5em, 2.4em),
    node-stroke: s-main,
    node-corner-radius: 4pt,

    // External
    node((2.5, 0.0), [*Internet*], stroke: none, fill: none),
    node((-0.5, 0.0), [*Webhooks*\ #label-xs[git · CI · monitoring]], fill: c-client),

    // nginx
    node((2.5, 1.0), [*nginx*\ #label-xs[TLS termination · prod only]], fill: c-edge, width: 16em),

    // Auth Gateway
    node((2.5, 2.2), [*Auth Gateway*\ #label-xs[http-kit · session/token validation · rate limit · CORS · routing]], fill: c-gateway, width: 26em),

    // Services row
    node((-0.5, 4.0), [*API*\ #label-xs[stateless REST]\ #label-xs[reads / writes → PG]\ #label-xs[publishes → NATS]], fill: c-api),
    node((1.8, 4.0), [*Web SM*\ #label-xs[Hiccup + Datastar SSE]\ #label-xs[browser connections]], fill: c-session),
    node((3.8, 4.0), [*TUI SM*\ #label-xs[SSE + HTTP POST]\ #label-xs[terminal connections]], fill: c-session),
    node((5.2, 4.0), [*Mobile SM*\ #label-xs[(future)]], fill: c-future, stroke: s-dim),

    // Event bus
    node((2.5, 5.8), [*NATS*\ #label-xs[pub/sub event bus]\ #label-xs[community-scoped subjects]], fill: c-bus),

    // Data stores
    node((-0.5, 7.5), [*PostgreSQL*\ #label-xs[data store · FTS]\ #label-xs[Auth GW + API only]], fill: c-infra),
    node((-2.2, 4.0), [*MinIO*\ #label-xs[S3 files · avatars]], fill: c-infra),

    // Clients
    node((1.8, 8.0), [*Browsers*], stroke: none, fill: none),
    node((3.8, 8.0), [*Terminals*\ #label-xs[CLI + TUI]], stroke: none, fill: none),
    node((5.2, 8.0), [#text(fill: rgb("#aaa"))[*Phones*]], stroke: none, fill: none),

    // --- Edges ---
    // Internet → nginx → Auth Gateway
    edge((2.5, 0.0), (2.5, 1.0), "-|>"),
    edge((-0.5, 0.0), (2.5, 2.2), "-|>"),
    edge((2.5, 1.0), (2.5, 2.2), "-|>"),

    // Auth Gateway → services
    edge((2.5, 2.2), (-0.5, 4.0), "-|>", label: label-sm[`/api/*`], label-side: left),
    edge((2.5, 2.2), (1.8, 4.0), "-|>", label: label-sm[`/web`], label-side: left),
    edge((2.5, 2.2), (3.8, 4.0), "-|>", label: label-sm[`/ws/tui`], label-side: right),
    edge((2.5, 2.2), (5.2, 4.0), "-|>", stroke: s-dim),

    // API → PG (data reads/writes — only API + Auth GW touch PG)
    edge((-0.5, 4.0), (-0.5, 7.5), "<-|>", stroke: s-data,
      label: text(size: 7pt, fill: rgb("#7b1fa2"))[data], label-side: left),

    // API → NATS (publish events after writes)
    edge((-0.5, 4.0), (2.5, 5.8), "-|>", stroke: s-bus,
      label: text(size: 7pt, fill: rgb("#e65100"))[pub], label-side: left),

    // NATS → SMs (subscribe for events)
    edge((2.5, 5.8), (1.8, 4.0), "-|>", stroke: s-bus,
      label: text(size: 7pt, fill: rgb("#e65100"))[sub], label-side: right),
    edge((2.5, 5.8), (3.8, 4.0), "-|>", stroke: s-bus),
    edge((2.5, 5.8), (5.2, 4.0), "-|>", stroke: s-dim),

    // SMs → API (fetch full data after event notification)
    edge((1.8, 4.0), (-0.5, 4.0), "-|>", stroke: s-data, bend: 20deg,
      label: text(size: 7pt, fill: rgb("#7b1fa2"))[fetch], label-side: right),
    edge((3.8, 4.0), (-0.5, 4.0), "-|>", stroke: s-data, bend: 12deg),

    // API → MinIO (files)
    edge((-0.5, 4.0), (-2.2, 4.0), "-|>", stroke: s-data,
      label: text(size: 7pt, fill: rgb("#7b1fa2"))[files], label-side: left),

    // SMs → clients
    edge((1.8, 4.0), (1.8, 8.0), "-|>", label: label-sm[SSE], label-side: left),
    edge((3.8, 4.0), (3.8, 8.0), "-|>", label: label-sm[SSE], label-side: left),
    edge((5.2, 4.0), (5.2, 8.0), "-|>", stroke: s-dim),
  )
]

// Legend
#v(0.5em)
#align(center)[
  #rect(stroke: 0.5pt + rgb("#ccc"), radius: 4pt, inset: 0.6em)[
    #text(size: 7pt)[
      #box(rect(fill: c-gateway, width: 0.7em, height: 0.7em, radius: 2pt)) Auth
      #h(0.8em)
      #box(rect(fill: c-api, width: 0.7em, height: 0.7em, radius: 2pt)) API
      #h(0.8em)
      #box(rect(fill: c-session, width: 0.7em, height: 0.7em, radius: 2pt)) Session Mgr
      #h(0.8em)
      #box(rect(fill: c-infra, width: 0.7em, height: 0.7em, radius: 2pt)) Infrastructure
      #h(0.8em)
      #box(rect(fill: c-bus, width: 0.7em, height: 0.7em, radius: 2pt)) NATS
      #h(0.8em)
      #line(length: 1em, stroke: s-bus) pub/sub
      #h(0.6em)
      #line(length: 1em, stroke: s-data) data
    ]
  ]
]

// ============================================================
// 2. AUTH GATEWAY DETAIL
// ============================================================
#section[2 · Auth Gateway Flow]

#align(center)[
  #diagram(
    spacing: (3.5em, 2em),
    node-stroke: s-main,
    node-corner-radius: 4pt,

    // Incoming
    node((0, 0), [*Client Request*\ #label-xs[HTTPS / SSE]], fill: c-client),

    // Steps
    node((0, 1), [*TLS Termination*\ #label-xs[nginx (prod only)]], fill: c-edge),
    node((0, 2), [*Extract Token*\ #label-xs[cookie or Authorization header]], fill: c-gateway),
    node((0, 3), [*DB Lookup*\ #label-xs[sessions / api\_tokens table]], fill: c-gateway),

    // Branch
    node((-1.5, 4), [*401 Unauthorized*], fill: rgb("#ffcdd2"), stroke: rgb("#c62828") + 0.8pt),
    node((1.5, 4), [*Attach User Context*\ #label-xs[user\_id, role, community\_id]], fill: c-gateway),

    // Rate limit
    node((1.5, 5), [*Rate Limit Check*\ #label-xs[per-user / per-IP]], fill: c-gateway),

    // Route
    node((1.5, 6), [*Route to Service*], fill: c-gateway),

    // Targets
    node((0, 7), [*API*], fill: c-api),
    node((1.5, 7), [*Web SM*], fill: c-session),
    node((3, 7), [*TUI SM*], fill: c-session),

    // Edges
    edge((0, 0), (0, 1), "-|>"),
    edge((0, 1), (0, 2), "-|>"),
    edge((0, 2), (0, 3), "-|>"),
    edge((0, 3), (-1.5, 4), "-|>", label: label-sm[invalid], label-side: left),
    edge((0, 3), (1.5, 4), "-|>", label: label-sm[valid], label-side: right),
    edge((1.5, 4), (1.5, 5), "-|>"),
    edge((1.5, 5), (1.5, 6), "-|>"),
    edge((1.5, 6), (0, 7), "-|>", label: label-sm[`/api/*`], label-side: left),
    edge((1.5, 6), (1.5, 7), "-|>", label: label-sm[`/web`], label-side: left),
    edge((1.5, 6), (3, 7), "-|>", label: label-sm[`/ws/tui`], label-side: right),
  )
]

// ============================================================
// 3. REAL-TIME EVENT FLOW
// ============================================================
#section[3 · Real-Time Event Flow]

#align(center)[
  #diagram(
    spacing: (3.5em, 2.2em),
    node-stroke: s-main,
    node-corner-radius: 4pt,

    // Trigger
    node((0, 0), [*User sends message*\ #label-xs[POST /api/messages]], fill: c-client),

    // API
    node((0, 1), [*API*\ #label-xs[validate · write to DB]], fill: c-api),

    // Two steps: DB write then NATS publish
    node((-1, 2.2), [*PostgreSQL*\ #label-xs[INSERT message row]], fill: c-infra),
    node((1, 2.2), [*NATS*\ #label-xs[publish `chat.events.\{community\}`]], fill: c-bus),

    // Fan out — SMs subscribe to NATS
    node((-1.5, 3.8), [*Web SM*\ #label-xs[receives via NATS sub]], fill: c-session),
    node((0, 3.8), [*TUI SM*\ #label-xs[receives via NATS sub]], fill: c-session),
    node((1.5, 3.8), [*Mobile SM*\ #label-xs[NATS sub]], fill: c-future, stroke: s-dim),

    // Client delivery — SMs fetch full data from API
    node((-1.5, 5), [*Fetch full message*\ #label-xs[GET /api/messages/:id]], fill: c-api),

    node((-1.5, 6.2), [*Datastar SSE*\ #label-xs[push HTML fragment]], fill: c-session),
    node((0, 6.2), [*SSE*\ #label-xs[push JSON payload]], fill: c-session),

    // Clients
    node((-1.5, 7.4), [*Browser*\ #label-xs[DOM patched live]], fill: none, stroke: none),
    node((0, 7.4), [*Terminal*\ #label-xs[message rendered]], fill: none, stroke: none),

    // Edges
    edge((0, 0), (0, 1), "-|>"),
    edge((0, 1), (-1, 2.2), "-|>", stroke: s-data,
      label: text(size: 7pt, fill: rgb("#7b1fa2"))[INSERT], label-side: left),
    edge((0, 1), (1, 2.2), "-|>", stroke: s-bus,
      label: text(size: 7pt, fill: rgb("#e65100"))[publish], label-side: right),
    edge((1, 2.2), (-1.5, 3.8), "-|>", stroke: s-bus),
    edge((1, 2.2), (0, 3.8), "-|>", stroke: s-bus),
    edge((1, 2.2), (1.5, 3.8), "-|>", stroke: s-dim),
    edge((-1.5, 3.8), (-1.5, 5), "-|>"),
    edge((0, 3.8), (0, 6.2), "-|>"),
    edge((-1.5, 5), (-1.5, 6.2), "-|>"),
    edge((-1.5, 6.2), (-1.5, 7.4), "-|>"),
    edge((0, 6.2), (0, 7.4), "-|>"),
  )
]

// ============================================================
// 4. DATA MODEL
// ============================================================
#section[4 · Data Model]

#align(center)[
  #diagram(
    spacing: (4em, 2.5em),
    node-stroke: s-main,
    node-corner-radius: 3pt,

    // Core entities
    node((2.5, 1.0), [*communities*\ #label-xs[id · name · slug]], fill: rgb("#e1bee7")),
    node((0.3, 1.9), [*users*\ #label-xs[id · username · email\ display\_name · avatar]], fill: rgb("#bbdefb")),
    node((5.0, 0.7), [*channels*\ #label-xs[id · name · type\ public / private / voice]], fill: rgb("#c8e6c9")),

    // Junction
    node((2.6, 2.0), [*community\_members*\ #label-xs[role: owner / admin / member\ nickname · avatar override]], fill: rgb("#f0f4c3")),
    node((2.4, 0.1), [*channel\_members*\ #label-xs[joined\_at]], fill: rgb("#f0f4c3")),

    // Messages
    node((2.6, 4.1), [*messages*\ #label-xs[id · body\_md · parent\_id\ created\_at · edited\_at]], fill: rgb("#ffe0b2")),

    // Related
    node((0.4, 0.0), [*notifications*\ #label-xs[id · type · source\_id\ read: bool]], fill: rgb("#ffccbc")),
    node((5.2, 5.0), [*attachments*\ #label-xs[id · filename · storage\_key\ content\_type · size]], fill: rgb("#d1c4e9")),
    node((2.0, 6.3), [*reactions*\ #label-xs[emoji · user\_id\ PK: msg + user + emoji]], fill: rgb("#fff9c4")),
    node((4.1, 6.3), [*mentions*\ #label-xs[target\_type · target\_id]], fill: rgb("#ffccbc")),
    node((5.0, 2.7), [*webhooks*\ #label-xs[id · name · token\_hash\ channel\_id · avatar]], fill: rgb("#b2dfdb")),

    // Auth
    node((0.2, 3.9), [*sessions*\ #label-xs[token\_hash · expires\_at]], fill: rgb("#cfd8dc")),
    node((0.0, 8.0), [*api\_users*\ #label-xs[name · scopes]], fill: rgb("#cfd8dc")),
    node((2.6, 8.0), [*api\_tokens*\ #label-xs[token\_hash · scopes · expires]], fill: rgb("#cfd8dc")),

    // Relationships
    edge((2.5, 1.0), (5.0, 0.7), "-|>", label: label-sm[has], label-side: right),
    edge((2.5, 1.0), (2.6, 2.0), "-|>", label: label-sm[membership], label-side: left),
    edge((0.3, 1.9), (2.6, 2.0), "-|>"),
    edge((5.0, 0.7), (2.4, 0.1), "-|>"),
    edge((0.3, 1.9), (2.4, 0.1), "-|>"),
    edge((5.0, 0.7), (2.6, 4.1), "-|>"),
    edge((0.3, 1.9), (2.6, 4.1), "-|>", label: label-sm[author], label-side: left),
    edge((2.6, 4.1), (2.6, 4.1), "-|>", bend: 130deg, label: label-sm[thread], label-side: right),
    edge((2.6, 4.1), (5.2, 5.0), "-|>"),
    edge((2.6, 4.1), (2.0, 6.3), "-|>"),
    edge((2.6, 4.1), (4.1, 6.3), "-|>"),
    edge((0.3, 1.9), (0.4, 0.0), "-|>"),
    edge((0.3, 1.9), (2.0, 6.3), "-|>", bend: -10deg),
    edge((0.3, 1.9), (0.2, 3.9), "-|>", label: label-sm[owns], label-side: left),
    edge((0.0, 8.0), (2.6, 8.0), "-|>"),
    edge((5.0, 0.7), (5.0, 2.7), "-|>"),
  )
]

// ============================================================
// 5. PRESENCE & TYPING
// ============================================================
#section[5 · Presence & Typing Indicators]

#align(center)[
  #diagram(
    spacing: (3.5em, 2em),
    node-stroke: s-main,
    node-corner-radius: 4pt,

    // Heartbeat flow
    node((-1, 0), [*Heartbeat (60s)*], stroke: none, fill: none),
    node((2.5, 0), [*Typing*], stroke: none, fill: none),

    // Heartbeat
    node((-1, 1), [*Client*\ #label-xs[POST /api/heartbeat]], fill: c-client),
    node((-1, 2), [*API*\ #label-xs[instant 200 response]], fill: c-api),
    node((-1, 3.2), [*PostgreSQL*\ #label-xs[UPDATE last\_seen\_at]], fill: c-infra),
    node((-1, 4.0), [*NATS*\ #label-xs[API publishes presence event]], fill: c-bus),
    node((-1, 5.2), [*Session Managers*\ #label-xs[receive via NATS sub]\ #label-xs[buffer → flush 1x/min per client]\ #label-xs[filter: relevant users only]\ #label-xs[diff: status changes only]], fill: c-session),
    node((-1, 6.8), [*Clients*\ #label-xs[batched presence snapshot]], fill: none, stroke: none),

    // Typing — SM publishes directly to NATS, not via API
    node((2.5, 1), [*Keypress in input*], fill: c-client),
    node((2.5, 2), [*Session Manager*\ #label-xs[receives via HTTP POST]], fill: c-session),
    node((2.5, 3.2), [*NATS*\ #label-xs[SM publishes typing event]\ #label-xs[no API round-trip]], fill: c-bus),
    node((2.5, 4.5), [*Other SMs*\ #label-xs[receive via NATS sub]\ #label-xs[filter: same channel/DM]\ #label-xs[deliver immediately]], fill: c-session),
    node((2.5, 5.8), [*Clients*\ #label-xs[show "user is typing..."]], fill: none, stroke: none),
    node((2.5, 6.8), [*Auto-expire*\ #label-xs[15s no keypress → hide]\ #label-xs[typing:stop on send or clear input]], fill: rgb("#fff3e0")),

    // Heartbeat edges
    edge((-1, 1), (-1, 2), "-|>"),
    edge((-1, 2), (-1, 3.2), "-|>", stroke: s-data),
    edge((-1, 3.2), (-1, 4.0), "-|>", stroke: s-bus),
    edge((-1, 4.0), (-1, 5.2), "-|>", stroke: s-bus),
    edge((-1, 5.2), (-1, 6.8), "-|>"),

    // Typing edges
    edge((2.5, 1), (2.5, 2), "-|>"),
    edge((2.5, 2), (2.5, 3.2), "-|>", stroke: s-bus),
    edge((2.5, 3.2), (2.5, 4.5), "-|>", stroke: s-bus),
    edge((2.5, 4.5), (2.5, 5.8), "-|>"),
  )
]

// ============================================================
// 6. CLI / TUI CLIENT
// ============================================================
#section[6 · CLI / TUI Client]

#align(center)[
  #diagram(
    spacing: (3.5em, 2em),
    node-stroke: s-main,
    node-corner-radius: 4pt,

    // Entry
    node((1, 0), [*`ajet-chat`*\ #label-xs[single binary · babashka/bbin]], fill: c-client),

    // Branch
    node((0, 1.5), [*CLI Mode*\ #label-xs[`ajet-chat <cmd> [args]`]\ #label-xs[one-shot · stateless]], fill: rgb("#e3f2fd")),
    node((2, 1.5), [*TUI Mode*\ #label-xs[`ajet-chat` (no args)]\ #label-xs[interactive · clojure-tui]], fill: rgb("#e8f5e9")),

    // CLI details
    node((-0.5, 3), [*Commands*\ #label-xs[send · read · channels\ notifications · status\ /help · /ban · /kick]], fill: rgb("#e3f2fd")),
    node((0, 4.5), [*API Client*\ #label-xs[HTTP → Auth GW → API]], fill: c-api),

    // TUI details
    node((2, 3), [*UI Layout*\ #label-xs[channel sidebar\ message list · input\ presence · typing]], fill: rgb("#e8f5e9")),
    node((3, 3), [*timg*\ #label-xs[inline images\ optional dep]], fill: rgb("#fff3e0")),
    node((2, 4.5), [*TUI SM Connection*\ #label-xs[SSE for live updates]], fill: c-session),

    // Shared
    node((1, 6), [*Shared Components*\ #label-xs[API client · auth/config (~/.config/ajet-chat/)\ message formatting · notification model]], fill: rgb("#f5f5f5"), width: 20em),

    // Edges
    edge((1, 0), (0, 1.5), "-|>", label: label-sm[args], label-side: left),
    edge((1, 0), (2, 1.5), "-|>", label: label-sm[no args], label-side: right),
    edge((0, 1.5), (-0.5, 3), "-|>"),
    edge((-0.5, 3), (0, 4.5), "-|>"),
    edge((2, 1.5), (2, 3), "-|>"),
    edge((2, 3), (2, 4.5), "-|>"),
    edge((0, 4.5), (1, 6), "-|>"),
    edge((2, 4.5), (1, 6), "-|>"),
  )
]

// ============================================================
// 7. DEPLOYMENT
// ============================================================
#section[7 · Deployment]

#align(center)[
  #diagram(
    spacing: (3em, 2em),
    node-stroke: s-main,
    node-corner-radius: 4pt,

    // Dev
    node((-1, 0), [*Development*], stroke: none, fill: none),
    node((3, 0), [*Production*], stroke: none, fill: none),

    // Dev stack
    node((-1, 1), [*docker-compose.dev.yml*\ #label-xs[infrastructure only]], fill: c-edge),
    node((-2.5, 2.5), [*PostgreSQL*\ #label-xs[container]], fill: c-infra),
    node((-1, 2.5), [*NATS*\ #label-xs[container]], fill: c-bus),
    node((0.5, 2.5), [*MinIO*\ #label-xs[container]], fill: c-infra),
    node((-1, 4), [*Clojure REPLs*\ #label-xs[local JVM processes]\ #label-xs[nrepl + cider-nrepl]\ #label-xs[start! / stop! / reset!]], fill: c-api, width: 14em),

    // Prod stack
    node((3.5, 1), [*docker-compose.yml*\ #label-xs[full stack]], fill: c-edge),
    node((2.5, 2.5), [*nginx*], fill: c-edge),
    node((2.5, 3.5), [*Auth GW*\ #label-xs[uberjar]], fill: c-gateway),
    node((2, 4.5), [*API*\ #label-xs[uberjar]], fill: c-api),
    node((3, 4.5), [*Web SM*\ #label-xs[uberjar]], fill: c-session),
    node((4, 4.5), [*TUI SM*\ #label-xs[uberjar]], fill: c-session),
    node((2, 5.8), [*PostgreSQL*], fill: c-infra),
    node((3, 5.8), [*NATS*], fill: c-bus),
    node((4, 5.8), [*MinIO*], fill: c-infra),

    // Dev edges
    edge((-1, 1), (-2.5, 2.5), "-|>"),
    edge((-1, 1), (-1, 2.5), "-|>"),
    edge((-1, 1), (0.5, 2.5), "-|>"),
    edge((-1, 4), (-2.5, 2.5), "<-|>", stroke: s-data, label: label-sm[JDBC], label-side: left),
    edge((-1, 4), (-1, 2.5), "<-|>", stroke: s-bus, label: label-sm[pub/sub], label-side: left),
    edge((-1, 4), (0.5, 2.5), "<-|>", stroke: s-data, label: label-sm[S3], label-side: right),

    // Prod edges
    edge((3.5, 1), (2.5, 2.5), "-|>"),
    edge((2.5, 2.5), (2.5, 3.5), "-|>"),
    edge((2.5, 3.5), (2, 4.5), "-|>"),
    edge((2.5, 3.5), (3, 4.5), "-|>"),
    edge((2.5, 3.5), (4, 4.5), "-|>"),
    edge((2, 4.5), (2, 5.8), "-|>", stroke: s-data),
    edge((2, 4.5), (3, 5.8), "-|>", stroke: s-bus),
    edge((2, 4.5), (4, 5.8), "-|>", stroke: s-data),
  )
]
