================================================================================
        TAG App - Group Audio & Video Rooms (Conference Calls) Backend
                        Developer Documentation
================================================================================

1. INTRODUCTION
   This document describes the backend for group audio/video rooms (conference
   calls) in TAG — up to 5 participants per room. Unlike direct (1:1) calls,
   which use peer-to-peer WebRTC signaled over Firestore, group rooms use
   Metered.ca's SFU (Selective Forwarding Unit) product. The PHP/MySQL layer
   only manages room lifecycle and membership and mints Metered join tokens;
   Metered's own client SDK handles all media relay and signaling once a
   client has a token — Firestore is NOT used for rooms.

2. SYSTEM OVERVIEW
   - PHP/MySQL manages room lifecycle: active / ended, and membership via
     room_participants (joined_at / left_at).
   - Metered.ca (same vendor/domain as the 1:1-call TURN integration, but a
     separate product and credential) hosts the actual room and issues
     per-participant join tokens.
   - Joining is either by direct invite (host picks from contacts, server
     pushes a notification) or by sharing the room's UUID as a join code —
     there is no public/browsable room directory.
   - A cron sweep (scripts/call_sweep.php) auto-ends any room left 'active'
     with zero participants still present, so an abandoned room doesn't
     linger forever.

3. DATABASE
   Tables: `rooms` and `room_participants`.

   Columns in `rooms`:
   - id                CHAR(36) PK (UUID v4) — also used as Metered's
                        roomName AND as the shareable join code
   - host_user_id       BIGINT UNSIGNED -> users.id
   - title              VARCHAR(150)
   - room_type          ENUM('audio','video') DEFAULT 'video'
   - status             ENUM('active','ended') DEFAULT 'active'
   - max_participants   INT UNSIGNED DEFAULT 5 (hard server-side cap)
   - metered_room_id    VARCHAR(255) NULL — Metered's internal _id, kept for
                        debugging/reference only, not used by clients
   - created_at, updated_at TIMESTAMPs

   Columns in `room_participants`:
   - id           BIGINT UNSIGNED AUTO_INCREMENT PK
   - room_id      CHAR(36) (FK to rooms.id)
   - user_id      BIGINT UNSIGNED (FK to users.id)
   - joined_at    TIMESTAMP
   - left_at      TIMESTAMP NULL
   UNIQUE KEY on (room_id, user_id, left_at)

4. FILE STRUCTURE
   App/
   ├── Controllers/
   │   └── RoomController.php
   ├── Models/
   │   └── Room.php
   ├── Services/
   │   └── MeteredVideoService.php     (Metered create-room / generate-token)
   ├── Helpers/
   │   └── UuidGenerator.php
   scripts/
   └── call_sweep.php                  (also sweeps abandoned rooms)
   routes/api.php

5. API ENDPOINTS (Base URL: https://tag.nsamaandcompany.com/tag_api/api/v1)
   All endpoints require a JWT bearer token and return the standard TAG
   response envelope: { "status": true|false, "message": "...", "data": {...} }

   POST /rooms
     Create a room. Host is auto-added as a participant server-side, but the
     host still must call POST /rooms/{roomId}/join afterward to receive
     their own Metered token — create() does not return a token.
     Body:   { "title": "...", "max_participants": 5, "room_type": "video"|"audio" }
     Success: { "room_id", "title", "room_type" }
     Errors: 502 if Metered room creation fails (nothing is persisted locally
             in that case — safe to retry).

   GET /rooms
     List active rooms with live participant counts. (Not used for room
     discovery by design — no public directory — mainly a debug/admin view.)

   GET /rooms/{roomId}
     Get a single room's details + participant_count.
     Errors: 404 if not found.

   POST /rooms/{roomId}/join
     Join a room (host or invitee) — this is the endpoint that actually
     mints Metered credentials. Call it right after create() as the host,
     or after receiving an invite / entering a shared room_id as a guest.
     Success: { "room_id", "metered_token" }
       - metered_token is handed directly to Metered's client SDK to connect
         audio/video — the API layer is not involved after this point.
     Errors: 404 (not found/ended), 403 (room full — checked against
             max_participants, host is exempt from the cap),
             502 (Metered token generation failed).

   POST /rooms/{roomId}/invite
     Host-only. Invite specific contacts by user ID; sends each an FCM push
     (type: "room_invite") so their app can prompt to join. Blocked users
     (either direction) are silently skipped, not errored.
     Body:   { "user_ids": [1, 2, 3] }
     Success: generic "Invites sent" (no per-user delivery status returned).
     Errors: 403 (not host), 404 (room not found/ended), 400 (empty user_ids).

   POST /rooms/{roomId}/leave
     Leave a room. If the host leaves, the room is immediately ended for
     everyone (status -> 'ended') — there is no host-transfer.
     Success: generic "Left room".
     Errors: 404 (room not found).

6. PUSH NOTIFICATION PAYLOAD (room_invite)
   Data-only FCM push, same delivery mechanism as direct-call pushes:
   {
     "type": "room_invite",
     "room_id": "<uuid>",
     "host_id": "<inviter user id>",
     "host_name": "<inviter display name, contact-aliased for this invitee>",
     "host_avatar_url": "<url>",
     "room_type": "audio"|"video",
     "title": "<room title>"
   }
   The app should show an "invited to a call" prompt; tapping it should call
   POST /rooms/{room_id}/join directly (no separate accept/decline endpoint
   exists in v1 — joining IS accepting, ignoring the push IS declining).

7. JOINING BY SHARED CODE
   The room_id (UUID) returned from POST /rooms doubles as the join code.
   Sharing it (e.g. via TagBook chat) lets any other user call
   POST /rooms/{room_id}/join directly, subject to the same full/ended
   checks as an invited join — there is no separate "redeem code" endpoint.

8. FLUTTER INTEGRATION NOTES
   - Do NOT use flutter_webrtc/cloud_firestore for rooms — that stack is
     only for 1:1 direct calls. For rooms, integrate Metered's Flutter/
     client SDK, which consumes the metered_token from POST .../join
     directly to connect.
   - Flow:
       1. Host: POST /rooms -> room_id
       2. Host: POST /rooms/{room_id}/join -> metered_token -> connect via
          Metered SDK.
       3. Host: POST /rooms/{room_id}/invite with contact user_ids (and/or
          share room_id as a code through chat).
       4. Invitee: on push tap (or manual code entry), POST
          /rooms/{room_id}/join -> metered_token -> connect via Metered SDK.
       5. Any participant: POST /rooms/{room_id}/leave when they exit the
          call UI.
   - For audio-only rooms, Metered's room is created with
     audioOnlyRoom=true / joinVideoOn=false server-side — the client should
     still respect room_type to decide whether to render a camera preview.
   - Enforce the 5-participant cap in the UI (e.g. grey out "invite" once
     5 are present) — the server also rejects joins past this cap with 403,
     but the host is exempt from that check.

9. TESTING
   API testing with Postman/cURL:
   - Obtain JWT via /auth/login.
   - POST /rooms with {"title":"Test","max_participants":5,"room_type":"video"}
     -> room_id.
   - POST /rooms/{room_id}/join as host -> metered_token.
   - POST /rooms/{room_id}/invite with {"user_ids":[2,3]} -> pushes sent.
   - As invitee, POST /rooms/{room_id}/join -> metered_token.
   - POST /rooms/{room_id}/leave as host -> room status becomes 'ended';
     a subsequent join by anyone else returns 404.

   Integration test:
   - 3+ real devices, mixed audio/video rooms, verify Metered SDK connects
     using the returned tokens and all participants can see/hear each other.
   - Kill the app on a non-host participant without leaving() — confirm the
     call_sweep.php cron does NOT end the room (host still present); kill
     ALL participants' apps without leaving — confirm the sweep ends the
     room on its next run.

10. KNOWN LIMITATIONS / NOT YET IMPLEMENTED
    - No accept/decline tracking for invites — it's fire-and-forget push.
    - No in-call event stream from Metered back to our backend (e.g. no
      webhook wiring for participant-joined/left from Metered's side yet) —
      room_participants rows are only as accurate as clients calling
      join()/leave() themselves.
    - No host-transfer if the host leaves; room simply ends.

================================================================================
                         End of Documentation
================================================================================
