"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.acceptInviteCallable = void 0;
const functions = __importStar(require("firebase-functions"));
const admin = __importStar(require("firebase-admin"));
/**
 * HTTPS callable that mediates invite acceptance.
 *
 * Issue #9 fix: clients are no longer allowed to read invites directly, because
 * the 6-character document ID was enumerable. The invite is looked up by code
 * server-side, validated, and accepted atomically here.
 *
 * Request body: { code: string, recoveryPhrase?: string }
 * - code: the 6-character invite code the partner shared.
 * - recoveryPhrase: required if the invite was created with a wrapped couple key.
 *
 * Response: { coupleId: string }
 *
 * Operations (all via Admin SDK, so Firestore rules are bypassed):
 *  1. Verify caller is authenticated and not already paired.
 *  2. Look up the invite document by code.
 *  3. Validate status == 'pending' and not expired.
 *  4. Prevent self-acceptance.
 *  5. Create the couple document with the wrapped couple key from the invite.
 *  6. Update both user documents with the new coupleId.
 *  7. Mark the invite as accepted.
 */
exports.acceptInviteCallable = functions.https.onCall(async (data, context) => {
    var _a, _b, _c;
    const callerId = (_a = context.auth) === null || _a === void 0 ? void 0 : _a.uid;
    if (!callerId) {
        throw new functions.https.HttpsError('unauthenticated', 'Must be signed in.');
    }
    const code = data === null || data === void 0 ? void 0 : data.code;
    if (!code || typeof code !== 'string') {
        throw new functions.https.HttpsError('invalid-argument', 'code is required.');
    }
    const recoveryPhrase = data === null || data === void 0 ? void 0 : data.recoveryPhrase;
    if (recoveryPhrase !== undefined && typeof recoveryPhrase !== 'string') {
        throw new functions.https.HttpsError('invalid-argument', 'recoveryPhrase must be a string.');
    }
    const db = admin.firestore();
    // Caller must not already be paired.
    const callerDoc = await db.collection('users').doc(callerId).get();
    if (callerDoc.exists && ((_b = callerDoc.data()) === null || _b === void 0 ? void 0 : _b.coupleId) != null) {
        throw new functions.https.HttpsError('failed-precondition', 'Caller is already paired.');
    }
    const inviteRef = db.collection('invites').doc(code);
    const inviteDoc = await inviteRef.get();
    if (!inviteDoc.exists) {
        throw new functions.https.HttpsError('not-found', 'Invite not found.');
    }
    const invite = (_c = inviteDoc.data()) !== null && _c !== void 0 ? _c : {};
    const inviterUserId = invite.inviterUserId;
    const status = invite.status;
    const expiresAt = invite.expiresAt;
    const wrappedCoupleKey = invite.wrappedCoupleKey;
    const kdfSalt = invite.kdfSalt;
    const kdfParams = invite.kdfParams;
    if (status !== 'pending') {
        throw new functions.https.HttpsError('failed-precondition', 'Invite has already been used.');
    }
    const now = admin.firestore.Timestamp.now();
    if (expiresAt != null && expiresAt.toMillis() <= now.toMillis()) {
        throw new functions.https.HttpsError('failed-precondition', 'Invite has expired.');
    }
    if (!inviterUserId) {
        throw new functions.https.HttpsError('failed-precondition', 'Invite is missing inviterUserId.');
    }
    if (inviterUserId === callerId) {
        throw new functions.https.HttpsError('permission-denied', 'Cannot accept your own invite.');
    }
    // Recovery phrase is required whenever the invite carries a wrapped key.
    if (wrappedCoupleKey != null && (!recoveryPhrase || recoveryPhrase.length === 0)) {
        throw new functions.https.HttpsError('invalid-argument', 'recoveryPhrase is required for this invite.');
    }
    const coupleId = db.collection('couples').doc().id;
    const coupleRef = db.collection('couples').doc(coupleId);
    const batch = db.batch();
    batch.set(coupleRef, {
        id: coupleId,
        userIds: [inviterUserId, callerId],
        inviteCode: code,
        createdAt: admin.firestore.FieldValue.serverTimestamp(),
        streakCount: 0,
        encryptionVersion: 2,
        wrappedCoupleKey: wrappedCoupleKey !== null && wrappedCoupleKey !== void 0 ? wrappedCoupleKey : null,
        kdfSalt: kdfSalt !== null && kdfSalt !== void 0 ? kdfSalt : null,
        kdfParams: kdfParams !== null && kdfParams !== void 0 ? kdfParams : null,
    });
    batch.update(db.collection('users').doc(inviterUserId), { coupleId });
    batch.update(db.collection('users').doc(callerId), { coupleId });
    batch.update(inviteRef, {
        status: 'accepted',
        acceptedByUserId: callerId,
        acceptedAt: admin.firestore.FieldValue.serverTimestamp(),
        coupleId,
    });
    await batch.commit();
    console.log(`[acceptInviteCallable] ${callerId} accepted invite ${code}; created couple ${coupleId}`);
    return {
        coupleId,
        inviterUserId,
        wrappedCoupleKey: wrappedCoupleKey !== null && wrappedCoupleKey !== void 0 ? wrappedCoupleKey : null,
        kdfSalt: kdfSalt !== null && kdfSalt !== void 0 ? kdfSalt : null,
        kdfParams: kdfParams !== null && kdfParams !== void 0 ? kdfParams : null,
    };
});
//# sourceMappingURL=acceptInviteCallable.js.map