/**
 * Group creation service — execution layer shared by dashboard and CLI.
 *
 * Decision layers (dashboard handler / CLI subcommand) are responsible for
 * choosing `creatorLarkAppId`, resolving bot refs, deriving user_open_ids, etc.
 * This service only orchestrates the Lark API sequence:
 *
 *   1. createChat (bots + invited users)
 *   2. transferChatOwner (best-effort, skipped if invitee was rejected)
 *   3. send @-mention notify (best-effort, skipped if invitee was rejected)
 *
 * Partial failures (transfer/notify) are returned as `*Error` fields without
 * throwing — the chat already exists at that point and retrying would create
 * duplicate groups. Only createChat throwing surfaces as an exception.
 *
 * Lark open_id is app-scoped: `userOpenIds`, `transferOwnerTo`, and
 * `notifyOwnerOpenId` MUST be in `creatorLarkAppId`'s app scope. Enforcing
 * this is the decision layer's job — the service trusts its inputs.
 */
import { createChat, transferChatOwner, getChatOwner, getChatShareLink, addUsersToChatByUnionId } from './groups-store.js';
import { sendMessage } from '../im/lark/client.js';
import { bindOncall } from './oncall-store.js';
export async function createGroupWithBots(opts) {
    // Filter creator out of the bot invite list. createChat does this defensively
    // too, but doing it here makes the service contract explicit and keeps
    // invalidBotIds reporting stable across underlying API changes.
    const otherBots = opts.larkAppIds.filter(id => id !== opts.creatorLarkAppId);
    const r = await createChat(opts.creatorLarkAppId, {
        name: opts.name,
        botIds: otherBots,
        userIds: opts.userOpenIds ?? [],
    });
    // Fetch the shareable join link BEFORE transferring ownership: the creator bot
    // is the chat owner right after createChat, so it can always read the link. If
    // we did this after transfer and the tenant restricts "share group" to
    // owner/admin, the (now demoted) bot would get a permission error. Best-effort:
    // on failure the caller falls back to the member-only applink URL.
    let shareLink = null;
    let shareLinkError = null;
    {
        const sl = await getChatShareLink(opts.creatorLarkAppId, r.chatId);
        if (sl.ok)
            shareLink = sl.shareLink;
        else
            shareLinkError = sl.error;
    }
    // Pull bot owners into the chat by union_id (tenant-stable; the creator bot
    // adds them). Best-effort — failures surface as invalidOwnerUnionIds, the chat
    // still exists. The creator's own owner (if any) is harmless to re-add.
    let invalidOwnerUnionIds = [];
    if (opts.ownerUnionIds && opts.ownerUnionIds.length > 0) {
        const ar = await addUsersToChatByUnionId(opts.creatorLarkAppId, r.chatId, opts.ownerUnionIds);
        invalidOwnerUnionIds = ar.invalidUserIds;
    }
    let ownerTransferredTo = null;
    let transferError = null;
    if (opts.transferOwnerTo) {
        // Skip transfer if Feishu rejected the invite — transferring to a
        // non-member returns "user not in chat" anyway.
        if (r.invalidUserIds.includes(opts.transferOwnerTo)) {
            transferError = 'invitee_rejected';
        }
        else {
            const tr = await transferChatOwner(opts.creatorLarkAppId, r.chatId, opts.transferOwnerTo);
            if (tr.ok) {
                ownerTransferredTo = opts.transferOwnerTo;
            }
            else {
                // Lark occasionally ACKs the owner transfer slowly (504 Gateway Timeout
                // or transient network error) even though the write actually committed
                // server-side. Verify by reading back the current owner before
                // surfacing the error — if the chat is already owned by the target,
                // the transfer really did succeed and the warning would mislead.
                const currentOwner = await getChatOwner(opts.creatorLarkAppId, r.chatId);
                if (currentOwner === opts.transferOwnerTo) {
                    ownerTransferredTo = opts.transferOwnerTo;
                }
                else {
                    transferError = tr.error;
                }
            }
        }
    }
    let notifyMessageId = null;
    let notifyError = null;
    if (opts.notifyOwnerOpenId) {
        if (r.invalidUserIds.includes(opts.notifyOwnerOpenId)) {
            notifyError = 'invitee_rejected';
        }
        else {
            try {
                notifyMessageId = await sendMessage(opts.creatorLarkAppId, r.chatId, `<at user_id="${opts.notifyOwnerOpenId}"></at>`, 'text');
            }
            catch (e) {
                notifyError = e?.message ?? String(e);
            }
        }
    }
    const oncallBindings = [];
    const bindWorkingDir = opts.bindWorkingDir?.trim();
    if (bindWorkingDir) {
        // Bind the new chat for every bot that actually joined it. The creator is
        // an implicit member; Lark reports rejected invitees in invalidBotIds.
        const invalidBots = new Set(r.invalidBotIds);
        const targetBotIds = Array.from(new Set([opts.creatorLarkAppId, ...opts.larkAppIds]))
            .filter(id => !invalidBots.has(id));
        for (const larkAppId of targetBotIds) {
            try {
                const br = await bindOncall(larkAppId, r.chatId, bindWorkingDir);
                if (br.ok) {
                    oncallBindings.push({ larkAppId, ok: true, created: br.created });
                }
                else {
                    oncallBindings.push({ larkAppId, ok: false, error: br.reason });
                }
            }
            catch (e) {
                oncallBindings.push({ larkAppId, ok: false, error: e?.message ?? String(e) });
            }
        }
    }
    return {
        ok: true,
        chatId: r.chatId,
        creator: opts.creatorLarkAppId,
        invalidBotIds: r.invalidBotIds,
        invalidUserIds: r.invalidUserIds,
        invalidOwnerUnionIds,
        ownerTransferredTo,
        transferError,
        notifyMessageId,
        notifyError,
        shareLink,
        shareLinkError,
        oncallBindings,
    };
}
//# sourceMappingURL=group-creator.js.map