/**
 * Permission Tools for Studiograph Agent
 *
 * User and per-folder access management backed by AuthService.
 * User management is workspace-owner-only; per-folder grants are gated
 * on folder-admin of the target folder (or workspace owner).
 */
import { Type as T } from 'typebox';
import { wrapToolResult } from './graph-tools.js';
import { isWorkspaceOwner } from '../../services/auth-service.js';
import { authUserToGitUser, ensurePersonForUser, getHandleToPersonRefMap, assertUserHandleUnique, BridgeError, } from '../../services/user-person-bridge.js';
export function createPermissionTools(authService, getCurrentUser, workspaceManager) {
    const getUser = getCurrentUser ?? (() => null);
    function ownerOnly() {
        const user = getUser();
        if (!user)
            return null;
        if (isWorkspaceOwner(user.role))
            return null;
        return 'Workspace owner access required.';
    }
    function canManageFolder(folder) {
        const user = getUser();
        if (!user)
            return null;
        if (isWorkspaceOwner(user.role))
            return null;
        if (authService.isFolderAdmin(user.id, folder))
            return null;
        return `Folder admin access required for "${folder}".`;
    }
    return [
        {
            name: 'permission_list_users',
            label: 'List Users',
            description: 'List all workspace users with their roles, folder access, and linked person entity (if any).',
            parameters: T.Object({}),
            execute: async (_toolCallId, _params) => {
                try {
                    const users = authService.listUsers();
                    const bridge = workspaceManager ? getHandleToPersonRefMap(workspaceManager) : null;
                    return wrapToolResult(users.map(u => ({
                        id: u.id,
                        email: u.email,
                        handle: u.handle,
                        displayName: u.displayName,
                        role: u.role,
                        folders: authService.getUserFolders(u.id),
                        linkedPersonRef: bridge?.get(u.handle) ?? null,
                    })));
                }
                catch (err) {
                    return wrapToolResult({ error: err.message });
                }
            },
        },
        {
            name: 'permission_create_user',
            label: 'Create User',
            description: 'Create a new workspace user (workspace owner only).',
            parameters: T.Object({
                email: T.String({ description: 'Email address' }),
                password: T.String({ description: 'Password (minimum 8 characters)' }),
                name: T.String({ description: 'Display name' }),
                role: T.Optional(T.String({ description: 'Role: owner or member. Defaults to member.' })),
            }),
            execute: async (_toolCallId, params) => {
                try {
                    const denied = ownerOnly();
                    if (denied)
                        return wrapToolResult({ error: denied });
                    if (params.role && params.role !== 'owner' && params.role !== 'member') {
                        return wrapToolResult({ error: `Invalid role "${params.role}" — must be "owner" or "member"` });
                    }
                    const role = (params.role === 'owner' ? 'owner' : 'member');
                    const user = authService.createUser(params.email, params.password, params.name, role);
                    // Bridge: email-match an existing person entity or auto-provision
                    // one. Non-fatal — if the team repo is unconfigured we still want
                    // user creation to succeed; the agent will get a warning in the
                    // tool result.
                    let bridgeWarning;
                    if (workspaceManager) {
                        try {
                            const r = await ensurePersonForUser(workspaceManager, authService, user, authUserToGitUser(user));
                            if (r.action === 'skipped')
                                bridgeWarning = `Person entity not auto-provisioned (${r.reason}). The user can link or create one from Settings → Profile.`;
                        }
                        catch (err) {
                            bridgeWarning = `Bridge failed: ${err?.message ?? err}`;
                        }
                    }
                    return wrapToolResult({ created: true, email: user.email, displayName: user.displayName, role: user.role, ...(bridgeWarning ? { warning: bridgeWarning } : {}) });
                }
                catch (err) {
                    return wrapToolResult({ error: err.message });
                }
            },
        },
        {
            name: 'permission_delete_user',
            label: 'Delete User',
            description: 'Delete a workspace user (workspace owner only).',
            parameters: T.Object({
                email: T.String({ description: 'Email of the user to delete' }),
            }),
            execute: async (_toolCallId, params) => {
                try {
                    const denied = ownerOnly();
                    if (denied)
                        return wrapToolResult({ error: denied });
                    const users = authService.listUsers();
                    const target = users.find(u => u.email === params.email.toLowerCase().trim());
                    if (!target)
                        return wrapToolResult({ error: `User "${params.email}" not found` });
                    if (target.role === 'owner' && users.filter(u => u.role === 'owner').length <= 1) {
                        return wrapToolResult({ error: 'Cannot delete the last workspace owner. Transfer ownership first.' });
                    }
                    const deleted = authService.deleteUser(params.email);
                    return wrapToolResult(deleted ? { ok: true, email: params.email } : { error: `User "${params.email}" not found` });
                }
                catch (err) {
                    return wrapToolResult({ error: err.message });
                }
            },
        },
        {
            name: 'permission_grant_access',
            label: 'Grant Folder Access',
            description: 'Add a user to a folder as admin or editor. Requires folder-admin or workspace-owner privilege.',
            parameters: T.Object({
                email: T.String({ description: 'Email of the user to grant access to' }),
                folder: T.String({ description: 'Folder name' }),
                role: T.Optional(T.String({ description: 'Folder role: admin or editor. Defaults to editor.' })),
            }),
            execute: async (_toolCallId, params) => {
                try {
                    const denied = canManageFolder(params.folder);
                    if (denied)
                        return wrapToolResult({ error: denied });
                    if (params.role && params.role !== 'admin' && params.role !== 'editor') {
                        return wrapToolResult({ error: `Invalid folder role "${params.role}" — must be "admin" or "editor"` });
                    }
                    const users = authService.listUsers();
                    const user = users.find(u => u.email === params.email.toLowerCase().trim());
                    if (!user)
                        return wrapToolResult({ error: `User "${params.email}" not found` });
                    const role = params.role === 'admin' ? 'admin' : 'editor';
                    authService.grantFolderAccess(user.id, params.folder, role);
                    return wrapToolResult({ ok: true, email: params.email, folder: params.folder, role });
                }
                catch (err) {
                    return wrapToolResult({ error: err.message });
                }
            },
        },
        {
            name: 'permission_revoke_access',
            label: 'Revoke Folder Access',
            description: "Remove a user from a folder. Requires folder-admin or workspace-owner privilege.",
            parameters: T.Object({
                email: T.String({ description: 'Email of the user to revoke access from' }),
                folder: T.String({ description: 'Folder name' }),
            }),
            execute: async (_toolCallId, params) => {
                try {
                    const denied = canManageFolder(params.folder);
                    if (denied)
                        return wrapToolResult({ error: denied });
                    const users = authService.listUsers();
                    const user = users.find(u => u.email === params.email.toLowerCase().trim());
                    if (!user)
                        return wrapToolResult({ error: `User "${params.email}" not found` });
                    // Preserve the "≥1 admin" invariant.
                    if (authService.getFolderRole(user.id, params.folder) === 'admin') {
                        const admins = authService.getFolderAdmins(params.folder);
                        if (admins.length <= 1) {
                            return wrapToolResult({ error: 'Cannot remove the last folder admin. Promote another member first.' });
                        }
                    }
                    authService.revokeFolderAccess(user.id, params.folder);
                    return wrapToolResult({ ok: true, email: params.email, folder: params.folder });
                }
                catch (err) {
                    return wrapToolResult({ error: err.message });
                }
            },
        },
        {
            name: 'permission_list_folder_access',
            label: 'List Folder Access',
            description: 'List users who are members of a specific folder, with their folder role.',
            parameters: T.Object({
                folder: T.String({ description: 'Folder name' }),
            }),
            execute: async (_toolCallId, params) => {
                try {
                    const members = authService.getFolderMembers(params.folder);
                    return wrapToolResult({
                        folder: params.folder,
                        members: members.map(m => ({
                            email: m.user.email,
                            displayName: m.user.displayName,
                            role: m.role,
                        })),
                    });
                }
                catch (err) {
                    return wrapToolResult({ error: err.message });
                }
            },
        },
        {
            name: 'link_person_to_user',
            label: 'Link Person to User',
            description: 'Bridge an existing person entity to an auth user account by stamping the user\'s handle onto the entity\'s `user_handle` field. Use ONLY when the user explicitly asks to bridge a record (e.g. "this person is me", "[[chris]] is the same person as me") or to fix an ambiguous migration. Do NOT call for team members the user merely mentions — they don\'t have accounts yet, and the email-match auto-link runs automatically when they register.',
            parameters: T.Object({
                person: T.Object({
                    repo: T.String({ description: 'Folder containing the person entity' }),
                    entityId: T.String({ description: 'Person entity ID (e.g. `fritz`)' }),
                }),
                userHandle: T.String({ description: 'Stable auth handle of the user account (e.g. `christian-marc-schmidt`). Call list_users first to confirm the handle.' }),
            }),
            execute: async (_toolCallId, params) => {
                if (!workspaceManager)
                    return wrapToolResult({ error: 'Bridge unavailable (workspace not loaded).' });
                try {
                    // Owner-only: linking another user's auth account to a person
                    // entity is identity-claim territory and must not be available
                    // to ordinary members (who could otherwise impersonate via
                    // the orchestrator's "Current User" system-prompt section).
                    // Open-mode / system context (no AuthUser) is allowed — same
                    // pattern as the other owner-gated tools above.
                    const denied = ownerOnly();
                    if (denied)
                        return wrapToolResult({ error: denied });
                    // Verify the user exists.
                    const user = authService.findUserByHandle(params.userHandle);
                    if (!user) {
                        return wrapToolResult({ error: `No auth user with handle "${params.userHandle}". Call permission_list_users to see valid handles.`, code: 'USER_HANDLE_UNKNOWN' });
                    }
                    // Verify the entity exists + isn't already linked to a DIFFERENT user.
                    const graph = workspaceManager.getGraph(params.person.repo);
                    let entity;
                    try {
                        entity = graph.get('person', params.person.entityId);
                    }
                    catch {
                        return wrapToolResult({ error: `Person entity ${params.person.repo}/${params.person.entityId} not found.`, code: 'PERSON_NOT_FOUND' });
                    }
                    const currentHandle = entity.data.user_handle;
                    if (typeof currentHandle === 'string' && currentHandle.length > 0 && currentHandle !== params.userHandle) {
                        return wrapToolResult({
                            error: `Person ${params.person.repo}/${params.person.entityId} is already linked to user "${currentHandle}". Unlink first or pick a different person.`,
                            code: 'PERSON_ALREADY_LINKED',
                            currentHandle,
                        });
                    }
                    // Verify workspace-wide uniqueness, skipping self.
                    try {
                        assertUserHandleUnique(workspaceManager, params.userHandle, params.person);
                    }
                    catch (err) {
                        if (err instanceof BridgeError) {
                            return wrapToolResult({ error: err.message, code: err.code, ...err.details });
                        }
                        throw err;
                    }
                    // Stamp. Attribute to the calling actor; fall back to a generic
                    // system identity rather than the linked-target user (which would
                    // make the audit log claim the new user committed their own link).
                    const actor = getUser();
                    const gitUser = actor
                        ? authUserToGitUser(actor)
                        : { id: 'studiograph', name: 'Studiograph', email: 'system@studiograph.local' };
                    await graph.update({
                        entityType: 'person',
                        entityId: params.person.entityId,
                        data: {
                            user_handle: params.userHandle,
                            updated_at: new Date().toISOString(),
                            updated_by: gitUser.id,
                        },
                        commitMessage: `Link person/${params.person.entityId} to user ${params.userHandle}`,
                        user: gitUser,
                    });
                    return wrapToolResult({
                        linked: true,
                        person: params.person,
                        userHandle: params.userHandle,
                        message: `Linked ${params.person.repo}/${params.person.entityId} → user ${params.userHandle}.`,
                    });
                }
                catch (err) {
                    return wrapToolResult({ error: err.message });
                }
            },
        },
        {
            name: 'unlink_person_from_user',
            label: 'Unlink Person from User',
            description: 'Clear the `user_handle` field on a person entity, severing the auth-user bridge. The entity itself is left intact (historical record of the person remains).',
            parameters: T.Object({
                person: T.Object({
                    repo: T.String({ description: 'Folder containing the person entity' }),
                    entityId: T.String({ description: 'Person entity ID' }),
                }),
            }),
            execute: async (_toolCallId, params) => {
                if (!workspaceManager)
                    return wrapToolResult({ error: 'Bridge unavailable (workspace not loaded).' });
                try {
                    // Owner-only: severing an auth bridge could be used to harass
                    // another user (their "My Tasks" goes empty, mentions silently
                    // stop firing). Same restraint as link_person_to_user.
                    const denied = ownerOnly();
                    if (denied)
                        return wrapToolResult({ error: denied });
                    const graph = workspaceManager.getGraph(params.person.repo);
                    let entity;
                    try {
                        entity = graph.get('person', params.person.entityId);
                    }
                    catch {
                        return wrapToolResult({ error: `Person entity ${params.person.repo}/${params.person.entityId} not found.`, code: 'PERSON_NOT_FOUND' });
                    }
                    const currentHandle = entity.data.user_handle;
                    if (typeof currentHandle !== 'string' || currentHandle.length === 0) {
                        // Idempotent: already unlinked.
                        return wrapToolResult({ unlinked: true, person: params.person, note: 'Already unlinked.' });
                    }
                    const actor = getUser();
                    const gitUser = actor ? authUserToGitUser(actor) : { id: 'studiograph', name: 'Studiograph', email: 'system@studiograph.local' };
                    await graph.update({
                        entityType: 'person',
                        entityId: params.person.entityId,
                        // Write empty string — graph.update merges over current frontmatter,
                        // and the schema's optional-string field treats "" as cleared.
                        data: {
                            user_handle: '',
                            updated_at: new Date().toISOString(),
                            updated_by: gitUser.id,
                        },
                        commitMessage: `Unlink person/${params.person.entityId} from user ${currentHandle}`,
                        user: gitUser,
                    });
                    return wrapToolResult({
                        unlinked: true,
                        person: params.person,
                        previousHandle: currentHandle,
                        message: `Unlinked ${params.person.repo}/${params.person.entityId} from user ${currentHandle}.`,
                    });
                }
                catch (err) {
                    return wrapToolResult({ error: err.message });
                }
            },
        },
    ];
}
//# sourceMappingURL=permission-tools.js.map