import { randomUUID } from "node:crypto";
import { existsSync, readFileSync } from "node:fs";
import { mkdir, rename, writeFile } from "node:fs/promises";
import { isAbsolute, join, resolve } from "node:path";

const collectionKeys = [
  "clients",
  "users",
  "memberships",
  "groups",
  "services",
  "grants",
  "exceptions",
  "serviceModuleEntitlements",
  "invites",
  "accessRequests",
  "revokedAccounts",
  "taskerInviteRequests",
  "engineWorkflowAccessRequests",
  "syncStatuses",
  "auditEvents",
  "taskManagerMemberships",
  "taskManagerProjectMemberships",
];

const clientTypes = new Set(["company", "person"]);
const clientStatuses = new Set(["active", "suspended", "demo", "expired"]);
const userStatuses = new Set(["invited", "active", "blocked"]);
const membershipRoles = new Set(["client_owner", "client_admin", "member"]);
const coreAssistantRoles = new Set(["blocked", "member", "admin"]);
const grantTargetTypes = new Set(["client", "group", "user"]);
const appRoles = new Set(["viewer", "member", "admin", "owner"]);
const grantStatuses = new Set(["active", "disabled"]);
const exceptionTypes = new Set(["deny", "allow"]);
const serviceStatuses = new Set(["active", "maintenance", "hidden", "disabled"]);
const taskManagerWorkspaceManagedByValues = new Set(["launcher", "tasker"]);
const serviceModuleIds = new Set(["codex_agents"]);
const accessRequestStatuses = new Set(["new", "approved", "rejected"]);
const taskerInviteRequestStatuses = new Set(["new", "approved", "rejected", "cancelled"]);
const taskManagerInviteRoles = new Set(["guest", "member", "admin"]);
const engineWorkflowAccessRequestStatuses = new Set(["new", "approved", "rejected", "cancelled"]);
const engineWorkflowAccessRequestTypes = new Set(["workflow", "service_role"]);
const engineWorkflowRoles = new Set(["viewer", "editor", "admin"]);
const engineServiceRoles = new Set(["viewer", "member"]);
const syncStates = new Set(["synced", "pending", "error", "disabled"]);
const publicPoolClientId = "client_public_pool";
const protectedLauncherUserIds = new Set(["user_root"]);
const engineAuthentikGroups = ["nodedc_admin", "nodedc_editor", "nodedc_viewer"];
const publicPoolClient = {
  id: publicPoolClientId,
  type: "person",
  name: "Открытый контур",
  legalName: "Public access pool",
  status: "active",
  contractStartsAt: null,
  contractEndsAt: null,
  paidUntil: null,
  demoEndsAt: null,
  contactName: "NODE.DC",
  contactEmail: null,
  avatarUrl: null,
  notes: "Системный контур для публичных заявок, публичных инвайтов и self-service пользователей.",
  createdAt: "2026-05-09T00:00:00.000Z",
  updatedAt: "2026-05-09T00:00:00.000Z",
};
const bimViewerService = {
  id: "service_bim_viewer",
  slug: "bim-viewer",
  title: "BIM VIEWER",
  subtitle: "BIM и CAD просмотр",
  description: "Персональный просмотр BIM/CAD моделей, версий и публичных ссылок.",
  fullDescription:
    "Standalone BIM/CAD viewer NODE.DC для рабочих моделей, вложений Operational Core, персонального модельного хранилища и безопасного внешнего просмотра по share-ссылкам.",
  url: "https://bim.nodedc.tech",
  launchUrl: "https://bim.nodedc.tech",
  logoutUrl: "https://bim.nodedc.tech/logout",
  accentColor: "#8FE3C8",
  fallbackGradient: "linear-gradient(132deg, rgba(143, 227, 200, 0.78), rgba(24, 70, 64, 0.9) 46%, #080B0F 84%)",
  status: "active",
  order: 25,
  authentikApplicationSlug: "bim-viewer",
  authentikGroupName: "nodedc:bim:access",
  isAvailableForAllNewClients: true,
  createdAt: "2026-06-18T00:00:00.000Z",
  updatedAt: "2026-06-18T00:00:00.000Z",
};
const bimViewerDefaultGrants = [
  {
    id: "grant_bim_viewer_client_romashka",
    serviceId: "service_bim_viewer",
    targetType: "client",
    targetId: "client_romashka",
    appRole: "member",
    status: "active",
    createdAt: "2026-06-18T00:00:00.000Z",
    updatedAt: "2026-06-18T00:00:00.000Z",
  },
  {
    id: "grant_bim_viewer_client_public_pool",
    serviceId: "service_bim_viewer",
    targetType: "client",
    targetId: publicPoolClientId,
    appRole: "member",
    status: "active",
    createdAt: "2026-06-18T00:00:00.000Z",
    updatedAt: "2026-06-18T00:00:00.000Z",
  },
];
const defaultSettings = {
  brand: {
    logoLinkUrl: "/",
  },
  taskManager: {
    workspaceCreationPolicy: "any_authorized_user",
  },
};

export function createControlPlaneStore({ projectRoot }) {
  const serverStorageRoot = resolveStorageRoot(projectRoot);
  const legacyPublicDataPath = join(projectRoot, "public", "storage", "launcher-data.json");
  const dataPath = join(serverStorageRoot, "launcher-data.json");
  let mutationQueue = Promise.resolve();

  function enqueueMutation(operation) {
    const nextOperation = mutationQueue.catch(() => {}).then(operation);
    mutationQueue = nextOperation.then(
      () => undefined,
      () => undefined
    );
    return nextOperation;
  }

  function readData() {
    const readablePath = existsSync(dataPath) ? dataPath : legacyPublicDataPath;

    if (!existsSync(readablePath)) {
      return normalizeData({});
    }

    return normalizeData(JSON.parse(readFileSync(readablePath, "utf8")));
  }

  async function writeData(data) {
    const normalizedData = normalizeData(data);
    const payload = `${JSON.stringify(normalizedData, null, 2)}\n`;

    await Promise.all(
      getWritableStorageRoots().map(async (storageRoot) => {
        await mkdir(storageRoot, { recursive: true });
        await writeJsonAtomically(join(storageRoot, "launcher-data.json"), payload);
      })
    );

    return normalizedData;
  }

  function getSnapshot(identity) {
    const data = readData();

    return {
      actor: resolveActor(data, identity),
      counts: Object.fromEntries(collectionKeys.map((key) => [key, data[key].length])),
      data,
    };
  }

  async function replaceData(payload, identity) {
    const data = normalizeData(payload);
    const actor = resolveActor(data, identity);
    addAuditEvent(data, actor, {
      action: "Обновлено control-plane состояние",
      objectType: "control_plane",
      objectName: "Launcher data",
      result: "success",
      details: "Полная запись launcher-data.json через backend store.",
    });

    return writeData(data);
  }

  async function createClient(payload, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const now = isoNow();
    const name = requireString(payload?.name, "name");
    const client = {
      id: uniqueId(data.clients, "client", name),
      type: pickEnum(payload?.type, clientTypes, "company"),
      name,
      legalName: nullableString(payload?.legalName),
      inn: nullableString(payload?.inn),
      status: pickEnum(payload?.status, clientStatuses, "active"),
      contractStartsAt: nullableString(payload?.contractStartsAt),
      contractEndsAt: nullableString(payload?.contractEndsAt),
      paidUntil: nullableString(payload?.paidUntil),
      demoEndsAt: nullableString(payload?.demoEndsAt),
      contactName: nullableString(payload?.contactName),
      contactEmail: nullableString(payload?.contactEmail),
      avatarUrl: nullableString(payload?.avatarUrl),
      integrations: normalizeClientIntegrations(payload?.integrations),
      notes: nullableString(payload?.notes),
      createdAt: now,
      updatedAt: now,
    };

    data.clients.push(client);
    addAuditEvent(data, actor, {
      action: "Создан клиент",
      objectType: "client",
      objectName: client.name,
      clientId: client.id,
      result: "success",
    });
    markPendingSync(data, client, "client");

    await writeData(data);
    return { client, data };
  }

  async function updateClient(clientId, payload, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const client = findClientById(data, clientId);

    client.type = pickEnum(payload?.type, clientTypes, client.type);
    client.name = optionalString(payload?.name, client.name);
    client.legalName = nullableStringWithFallback(payload?.legalName, client.legalName ?? null);
    client.inn = nullableStringWithFallback(payload?.inn, client.inn ?? null);
    client.status = pickEnum(payload?.status, clientStatuses, client.status);
    client.contractStartsAt = nullableStringWithFallback(payload?.contractStartsAt, client.contractStartsAt ?? null);
    client.contractEndsAt = nullableStringWithFallback(payload?.contractEndsAt, client.contractEndsAt ?? null);
    client.paidUntil = nullableStringWithFallback(payload?.paidUntil, client.paidUntil ?? null);
    client.demoEndsAt = nullableStringWithFallback(payload?.demoEndsAt, client.demoEndsAt ?? null);
    client.contactName = nullableStringWithFallback(payload?.contactName, client.contactName ?? null);
    client.contactEmail = nullableStringWithFallback(payload?.contactEmail, client.contactEmail ?? null);
    client.avatarUrl = nullableStringWithFallback(payload?.avatarUrl, client.avatarUrl ?? null);
    if ("integrations" in (payload ?? {})) {
      client.integrations = normalizeClientIntegrations(payload.integrations, client.integrations);
    }
    client.notes = nullableStringWithFallback(payload?.notes, client.notes ?? null);
    client.updatedAt = isoNow();

    addAuditEvent(data, actor, {
      action: "Обновлён клиент",
      objectType: "client",
      objectName: client.name,
      clientId: client.id,
      result: "success",
    });
    markPendingSync(data, client, "client");

    await writeData(data);
    return { client, data };
  }

  async function recordTaskManagerWorkspaceMembership(payload, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const client = findById(data.clients, payload?.clientId, "client");
    const user = findById(data.users, payload?.userId, "user");
    const taskManager = typeof payload?.taskManager === "object" && payload.taskManager !== null ? payload.taskManager : {};
    const membership = typeof taskManager.membership === "object" && taskManager.membership !== null ? taskManager.membership : {};
    const workspace = typeof membership.workspace === "object" && membership.workspace !== null ? membership.workspace : {};

    upsertTaskManagerMembership(data, {
      clientId: client.id,
      userId: user.id,
      workspaceSlug: workspace.slug ?? payload?.workspaceSlug,
      workspaceName: workspace.name ?? payload?.workspaceName ?? null,
      role: normalizeTaskManagerMembershipRole(payload?.role),
      managedBy: payload?.managedBy,
      planeUserId: membership.member?.id ?? null,
      planeRole: typeof membership.role === "number" ? membership.role : null,
    });

    addAuditEvent(data, actor, {
      action: "Назначен Tasker workspace",
      objectType: "task-manager-membership",
      objectName: user.name,
      clientId: client.id,
      result: "success",
      details: `Workspace: ${workspace.name ?? workspace.slug ?? payload?.workspaceSlug}; Role: ${payload?.role ?? "member"}`,
    });

    await writeData(data);
    return { data };
  }

  async function removeTaskManagerWorkspaceMembership(payload, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const client = findById(data.clients, payload?.clientId, "client");
    const user = findById(data.users, payload?.userId, "user");
    const workspaceSlug = requireString(payload?.workspaceSlug ?? client.integrations?.taskManager?.workspaceSlug, "workspaceSlug");

    data.taskManagerMemberships = data.taskManagerMemberships.filter(
      (membership) => !(membership.clientId === client.id && membership.userId === user.id && membership.workspaceSlug === workspaceSlug)
    );
    data.taskManagerProjectMemberships = data.taskManagerProjectMemberships.filter(
      (membership) => !(membership.clientId === client.id && membership.userId === user.id && membership.workspaceSlug === workspaceSlug)
    );

    addAuditEvent(data, actor, {
      action: "Снят Tasker workspace",
      objectType: "task-manager-membership",
      objectName: user.name,
      clientId: client.id,
      result: "success",
      details: `Workspace: ${workspaceSlug}`,
    });

    await writeData(data);
    return { data };
  }

  async function recordTaskManagerProjectMembership(payload, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const client = findById(data.clients, payload?.clientId, "client");
    const user = findById(data.users, payload?.userId, "user");
    const taskManager = typeof payload?.taskManager === "object" && payload.taskManager !== null ? payload.taskManager : {};
    const membership = typeof taskManager.membership === "object" && taskManager.membership !== null ? taskManager.membership : {};
    const workspace = typeof membership.workspace === "object" && membership.workspace !== null ? membership.workspace : {};
    const project = typeof membership.project === "object" && membership.project !== null ? membership.project : {};

    upsertTaskManagerProjectMembership(data, {
      clientId: client.id,
      userId: user.id,
	      workspaceSlug: workspace.slug ?? payload?.workspaceSlug,
	      workspaceName: workspace.name ?? payload?.workspaceName ?? null,
	      projectId: project.id ?? payload?.projectId,
	      projectIdentifier: project.identifier ?? payload?.projectIdentifier ?? null,
	      projectName: project.name ?? payload?.projectName ?? null,
	      role: normalizeTaskManagerMembershipRole(payload?.role),
	      managedBy: payload?.managedBy,
	      planeUserId: membership.member?.id ?? null,
	      planeRole: typeof membership.role === "number" ? membership.role : null,
	    });

    addAuditEvent(data, actor, {
      action: "Назначен Tasker project",
      objectType: "task-manager-project-membership",
      objectName: user.name,
      clientId: client.id,
      result: "success",
      details: `Workspace: ${workspace.name ?? workspace.slug ?? payload?.workspaceSlug}; Project: ${project.name ?? project.identifier ?? payload?.projectId}; Role: ${payload?.role ?? "member"}`,
    });

    await writeData(data);
    return { data };
  }

  async function removeTaskManagerProjectMembership(payload, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const client = findById(data.clients, payload?.clientId, "client");
    const user = findById(data.users, payload?.userId, "user");
    const workspaceSlug = requireString(payload?.workspaceSlug ?? client.integrations?.taskManager?.workspaceSlug, "workspaceSlug");
    const projectId = requireString(payload?.projectId, "projectId");

    data.taskManagerProjectMemberships = data.taskManagerProjectMemberships.filter(
      (membership) =>
        !(
          membership.clientId === client.id &&
          membership.userId === user.id &&
          membership.workspaceSlug === workspaceSlug &&
          membership.projectId === projectId
        )
    );

    addAuditEvent(data, actor, {
      action: "Снят Tasker project",
      objectType: "task-manager-project-membership",
      objectName: user.name,
      clientId: client.id,
      result: "success",
      details: `Workspace: ${workspaceSlug}; Project: ${projectId}`,
    });

    await writeData(data);
    return { data };
  }

  async function deleteClient(clientId, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const client = findClientById(data, clientId);

    if (data.clients.length <= 1) {
      throw new Error("Cannot delete the last client");
    }

    const deletedGroupIds = new Set(data.groups.filter((group) => group.clientId === clientId).map((group) => group.id));
    data.clients = data.clients.filter((item) => item.id !== clientId);
    data.memberships = data.memberships.filter((membership) => membership.clientId !== clientId);
    data.groups = data.groups.filter((group) => group.clientId !== clientId);
    data.grants = data.grants.filter(
      (grant) =>
        !(grant.targetType === "client" && grant.targetId === clientId) &&
        !(grant.targetType === "group" && deletedGroupIds.has(grant.targetId))
    );
    data.serviceModuleEntitlements = data.serviceModuleEntitlements.filter((entitlement) => entitlement.clientId !== clientId);
    data.invites = data.invites.filter((invite) => invite.clientId !== clientId);
    data.syncStatuses = data.syncStatuses.filter((syncStatus) => syncStatus.objectId !== clientId);

    addAuditEvent(data, actor, {
      action: "Удалён клиент",
      objectType: "client",
      objectName: client.name,
      clientId,
      result: "warning",
    });

    await writeData(data);
    return { client, data };
  }

  async function updateSettings(payload, identity) {
    return enqueueMutation(async () => {
      const data = readData();
      const actor = resolveActor(data, identity);
      const patch = typeof payload === "object" && payload !== null ? payload : {};
      const settings = normalizeSettings({
        ...data.settings,
        ...patch,
        brand: {
          ...(data.settings?.brand ?? {}),
          ...(patch.brand ?? {}),
        },
        taskManager: {
          ...(data.settings?.taskManager ?? {}),
          ...(patch.taskManager ?? {}),
        },
      });

      data.settings = settings;
      addAuditEvent(data, actor, {
        action: "Обновлены системные настройки",
        objectType: "settings",
        objectName: "Brand settings",
        result: "success",
        details: `Logo link: ${settings.brand.logoLinkUrl}; Tasker workspace policy: ${settings.taskManager.workspaceCreationPolicy}`,
      });

      await writeData(data);
      return { settings, data };
    });
  }

  async function updateUserProfile(userId, payload, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const user = findById(data.users, userId, "user");
    const now = isoNow();

    user.name = optionalString(payload?.name, user.name);
    user.email = optionalString(payload?.email, user.email);
    user.phone = nullableStringWithFallback(payload?.phone, user.phone);
    user.position = nullableStringWithFallback(payload?.position, user.position);
    user.notes = nullableStringWithFallback(payload?.notes, user.notes);
    user.avatarUrl = nullableStringWithFallback(payload?.avatarUrl, user.avatarUrl ?? null);
    user.globalStatus = pickEnum(payload?.globalStatus, userStatuses, user.globalStatus);
    user.updatedAt = now;

    addAuditEvent(data, actor, {
      action: "Обновлён профиль пользователя",
      objectType: "user",
      objectName: user.email,
      result: "success",
    });
    markPendingSync(data, user, "user");

    await writeData(data);
    return { user, data };
  }

  async function createUser(payload, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const now = isoNow();
    const clientId = requireString(payload?.clientId, "clientId");
    const client = findClientById(data, clientId);
    const email = requireString(payload?.email, "email").toLowerCase();
    const existingUser = data.users.find((item) => item.email.toLowerCase() === email);
    const user =
      existingUser ??
      {
        id: uniqueId(data.users, "user", email),
        authentikUserId: nullableString(payload?.authentikUserId),
        name: optionalString(payload?.name, email.split("@")[0]),
        email,
        phone: nullableString(payload?.phone),
        position: nullableString(payload?.position),
        notes: nullableString(payload?.notes),
        avatarUrl: nullableString(payload?.avatarUrl),
        globalStatus: pickEnum(payload?.globalStatus, userStatuses, "active"),
        createdAt: now,
        updatedAt: now,
      };

    if (existingUser) {
      user.name = optionalString(payload?.name, user.name);
      user.phone = nullableStringWithFallback(payload?.phone, user.phone ?? null);
      user.position = nullableStringWithFallback(payload?.position, user.position ?? null);
      user.notes = nullableStringWithFallback(payload?.notes, user.notes ?? null);
      user.avatarUrl = nullableStringWithFallback(payload?.avatarUrl, user.avatarUrl ?? null);
      user.globalStatus = pickEnum(payload?.globalStatus, userStatuses, user.globalStatus);
      user.updatedAt = now;
    } else {
      data.users.push(user);
    }

    const existingMembership = data.memberships.find((membership) => membership.clientId === clientId && membership.userId === user.id);

    if (existingMembership) {
      throw new Error(`User ${email} already belongs to client ${client.name}`);
    }

    const membership = {
      id: uniqueId(data.memberships, "mem", `${clientId}-${email}`),
      clientId,
      userId: user.id,
      role: pickEnum(payload?.role, membershipRoles, "member"),
      status: pickEnum(payload?.membershipStatus, new Set(["active", "disabled"]), "active"),
      coreAssistantRole: pickEnum(payload?.coreAssistantRole, coreAssistantRoles, "member"),
      invitedByUserId: actor.id,
      inviteId: null,
      source: "launcher",
      sourceTaskerInviteRequestId: null,
      createdAt: now,
      updatedAt: now,
    };

    data.memberships.push(membership);

    if (Array.isArray(payload?.groupIds)) {
      for (const group of data.groups) {
        if (group.clientId !== clientId || !payload.groupIds.includes(group.id) || group.memberIds.includes(user.id)) {
          continue;
        }

        group.memberIds.push(user.id);
        group.updatedAt = now;
      }
    }

    clearRevokedAccount(data, email);
    addAuditEvent(data, actor, {
      action: existingUser ? "Добавлен пользователь в клиента" : "Создан пользователь",
      objectType: "user",
      objectName: email,
      clientId: client.id,
      result: "success",
      details: `Role: ${membership.role}; status: ${membership.status}`,
    });
    markPendingSync(data, user, "user", email);

    await writeData(data);
    return { user, membership, data };
  }

  async function updateMembership(membershipId, payload, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const membership = findById(data.memberships, membershipId, "membership");
    const user = findById(data.users, membership.userId, "user");

    if (isProtectedLauncherUserId(user.id)) {
      const nextRole = pickEnum(payload?.role, membershipRoles, "client_owner");
      const nextStatus = pickEnum(payload?.status, new Set(["active", "disabled"]), "active");

      if (nextRole !== "client_owner" || nextStatus !== "active") {
        throw new Error("Системного root-пользователя нельзя понижать или отключать");
      }

      membership.role = "client_owner";
      membership.status = "active";
      membership.coreAssistantRole = "admin";
    } else {
      membership.role = pickEnum(payload?.role, membershipRoles, membership.role);
      membership.status = pickEnum(payload?.status, new Set(["active", "disabled"]), membership.status);
      membership.coreAssistantRole = pickEnum(payload?.coreAssistantRole, coreAssistantRoles, membership.coreAssistantRole ?? "member");
    }
    membership.updatedAt = isoNow();

    addAuditEvent(data, actor, {
      action: "Обновлено членство",
      objectType: "user",
      objectName: user.email,
      clientId: membership.clientId,
      result: "success",
      details: `Role: ${membership.role}; status: ${membership.status}; coreAssistantRole: ${membership.coreAssistantRole}`,
    });
    markPendingSync(data, user, "user");

    await writeData(data);
    return { membership, data };
  }

  async function deleteMembership(membershipId, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const membership = findById(data.memberships, membershipId, "membership");
    const user = findById(data.users, membership.userId, "user");

    data.memberships = data.memberships.filter((item) => item.id !== membershipId);
    data.groups = data.groups.map((group) =>
      group.clientId === membership.clientId
        ? {
            ...group,
            memberIds: group.memberIds.filter((userId) => userId !== membership.userId),
            updatedAt: isoNow(),
          }
        : group
    );

    addAuditEvent(data, actor, {
      action: "Удалено членство",
      objectType: "user",
      objectName: user.email,
      clientId: membership.clientId,
      result: "warning",
    });
    markPendingSync(data, user, "user");

    await writeData(data);
    return { membership, data };
  }

  async function deleteUser(userId, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const user = findById(data.users, userId, "user");

    if (user.id === "user_root") {
      throw new Error("Системного root-пользователя нельзя удалить");
    }

    const email = user.email.toLowerCase();
    const removedMembershipIds = new Set(data.memberships.filter((membership) => membership.userId === user.id).map((membership) => membership.id));
    const removedInviteIds = new Set(
      data.invites
        .filter((invite) => invite.email.toLowerCase() === email)
        .map((invite) => invite.id)
    );
    const removedAccessRequestIds = new Set(
      data.accessRequests
        .filter((request) => request.email.toLowerCase() === email)
        .map((request) => request.id)
    );
    const removedTaskerInviteRequestIds = new Set(
      data.taskerInviteRequests
        .filter(
          (request) =>
            request.inviteeEmail.toLowerCase() === email ||
            request.inviterEmail.toLowerCase() === email ||
            request.inviterUserId === user.id
        )
        .map((request) => request.id)
    );

    upsertRevokedAccount(data, user, actor);
    data.users = data.users.filter((candidate) => candidate.id !== user.id);
    data.memberships = data.memberships.filter((membership) => membership.userId !== user.id);
    data.groups = data.groups.map((group) => ({
      ...group,
      memberIds: group.memberIds.filter((memberId) => memberId !== user.id),
      updatedAt: group.memberIds.includes(user.id) ? isoNow() : group.updatedAt,
    }));
    data.grants = data.grants.filter((grant) => !(grant.targetType === "user" && grant.targetId === user.id));
    data.exceptions = data.exceptions.filter((exception) => exception.userId !== user.id);
    data.serviceModuleEntitlements = data.serviceModuleEntitlements.filter((entitlement) => entitlement.userId !== user.id);
    data.invites = data.invites.filter(
      (invite) => invite.email.toLowerCase() !== email && invite.invitedByUserId !== user.id
    );
    data.accessRequests = data.accessRequests.filter((request) => request.email.toLowerCase() !== email);
    data.taskerInviteRequests = data.taskerInviteRequests.filter(
      (request) =>
        request.inviteeEmail.toLowerCase() !== email &&
        request.inviterEmail.toLowerCase() !== email &&
        request.inviterUserId !== user.id
    );
    data.taskManagerMemberships = data.taskManagerMemberships.filter((membership) => membership.userId !== user.id);
    data.taskManagerProjectMemberships = data.taskManagerProjectMemberships.filter((membership) => membership.userId !== user.id);
    data.syncStatuses = data.syncStatuses.filter((syncStatus) => {
      if (syncStatus.objectId === user.id || syncStatus.objectName?.toLowerCase?.() === email) return false;
      if (removedInviteIds.has(syncStatus.objectId)) return false;
      if (removedAccessRequestIds.has(syncStatus.objectId)) return false;
      if (removedTaskerInviteRequestIds.has(syncStatus.objectId)) return false;
      if (removedMembershipIds.has(syncStatus.objectId)) return false;
      return true;
    });

    addAuditEvent(data, actor, {
      action: "Пользователь удалён полностью",
      objectType: "user",
      objectName: email,
      clientId: null,
      result: "warning",
      details: `Удалены профиль, членства: ${removedMembershipIds.size}, инвайты: ${removedInviteIds.size}, заявки: ${
        removedAccessRequestIds.size + removedTaskerInviteRequestIds.size
      }`,
    });

    await writeData(data);
    return {
      user,
      affected: {
        memberships: removedMembershipIds.size,
        invites: removedInviteIds.size,
        accessRequests: removedAccessRequestIds.size,
        taskerInviteRequests: removedTaskerInviteRequestIds.size,
      },
      data,
    };
  }

  async function createInvite(payload, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const now = isoNow();
    const clientId = requireString(payload?.clientId, "clientId");
    const client = findClientById(data, clientId);
    const email = requireString(payload?.email, "email").toLowerCase();
    const role = pickEnum(payload?.role, membershipRoles, "member");
    const expiresAt = optionalString(payload?.expiresAt, new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString());
    const invite = {
      id: uniqueId(data.invites, "invite", email),
      clientId,
      email,
      role,
      invitedByUserId: actor.id,
      source: "launcher",
      sourceTaskerInviteRequestId: null,
      sourceTaskerInviteId: null,
      sourceWorkspaceSlug: null,
      sourceWorkspaceName: null,
      token: randomUUID(),
      expiresAt,
      status: "created",
      createdAt: now,
      updatedAt: now,
    };

    data.invites.push(invite);
    addAuditEvent(data, actor, {
      action: "Создан инвайт",
      objectType: "invite",
      objectName: email,
      clientId: client.id,
      result: "success",
      details: `Роль: ${role}`,
    });
    markPendingSync(data, invite, "invite", email);

    await writeData(data);
    return { invite, data };
  }

  async function createAccessRequest(payload) {
    const data = readData();
    const now = isoNow();
    const requestPayload = sanitizeAccessRequestPayload(payload);
    const email = requestPayload.email.toLowerCase();
    const existingUser = data.users.find((candidate) => candidate.email.toLowerCase() === email);

    if (existingUser?.globalStatus === "blocked") {
      throw new Error("Аккаунт с этой почтой заблокирован. Обратитесь к администратору NODE.DC.");
    }

    if (existingUser?.globalStatus === "active") {
      const hasOnlyPendingAccessRequest =
        data.accessRequests.some((candidate) => candidate.email.toLowerCase() === email && candidate.status === "new") &&
        data.memberships.some(
          (membership) =>
            membership.userId === existingUser.id &&
            membership.clientId === publicPoolClientId &&
            membership.source === "access_request" &&
            membership.status === "disabled"
        );

      if (!hasOnlyPendingAccessRequest) {
        throw new Error("Аккаунт с этой почтой уже существует. Войдите в NODE.DC или обратитесь к администратору.");
      }
    }

    const user = upsertAccessRequestUser(data, requestPayload, now);
    upsertAccessRequestMembership(data, user, requestPayload, { status: "disabled", now });
    clearRevokedAccount(data, email);
    markPendingSync(data, user, "user", user.email);
    const existingRequest = data.accessRequests.find(
      (candidate) => candidate.email.toLowerCase() === email && candidate.status === "new"
    );

    if (existingRequest) {
      Object.assign(existingRequest, {
        ...requestPayload,
        email,
        updatedAt: now,
      });
      addAuditEvent(data, { id: "public", name: requestPayload.company, email, source: "public" }, {
        action: "Обновлена публичная заявка",
        objectType: "access_request",
        objectName: email,
        clientId: null,
        result: "success",
        details: requestPayload.company,
      });

      await writeData(data);
      return { accessRequest: existingRequest, user, data };
    }

    const accessRequest = {
      id: uniqueId(data.accessRequests, "access_request", email),
      ...requestPayload,
      email,
      status: "new",
      targetClientId: publicPoolClientId,
      role: "member",
      approvedInviteId: null,
      reviewedByUserId: null,
      reviewedAt: null,
      comment: null,
      createdAt: now,
      updatedAt: now,
    };

    data.accessRequests.push(accessRequest);
    addAuditEvent(data, { id: "public", name: requestPayload.company, email, source: "public" }, {
      action: "Создана публичная заявка",
      objectType: "access_request",
      objectName: email,
      clientId: null,
      result: "success",
      details: requestPayload.company,
    });

    await writeData(data);
    return { accessRequest, user, data };
  }

  async function updateAccessRequest(accessRequestId, payload, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const accessRequest = findAccessRequestById(data, accessRequestId);

    if (payload?.targetClientId !== undefined) {
      accessRequest.targetClientId = resolveAccessRequestTargetClientId(data, payload.targetClientId, accessRequest.targetClientId);
    }

    accessRequest.role = pickEnum(payload?.role, membershipRoles, accessRequest.role);
    accessRequest.comment = nullableStringWithFallback(payload?.comment, accessRequest.comment ?? null);
    accessRequest.updatedAt = isoNow();

    addAuditEvent(data, actor, {
      action: "Обновлена публичная заявка",
      objectType: "access_request",
      objectName: accessRequest.email,
      clientId: accessRequest.targetClientId === publicPoolClientId ? null : accessRequest.targetClientId,
      result: "success",
      details: `Target: ${accessRequest.targetClientId}; role: ${accessRequest.role}`,
    });

    await writeData(data);
    return { accessRequest, data };
  }

  async function approveAccessRequest(accessRequestId, payload, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const now = isoNow();
    const accessRequest = findAccessRequestById(data, accessRequestId);

    if (accessRequest.status === "rejected") {
      throw new Error("Отклонённую заявку нельзя подтвердить без повторного запроса");
    }

    if (payload?.targetClientId !== undefined) {
      accessRequest.targetClientId = resolveAccessRequestTargetClientId(data, payload.targetClientId, accessRequest.targetClientId);
    } else {
      accessRequest.targetClientId = resolveAccessRequestTargetClientId(data, accessRequest.targetClientId, publicPoolClientId);
    }

    accessRequest.role = pickEnum(payload?.role, membershipRoles, accessRequest.role);
    accessRequest.comment = nullableStringWithFallback(payload?.comment, accessRequest.comment ?? null);

    const existingUser = data.users.find((candidate) => candidate.email.toLowerCase() === accessRequest.email.toLowerCase());
    if (existingUser?.globalStatus === "blocked") {
      throw new Error("Заблокированный аккаунт нельзя подтвердить без ручного разблокирования.");
    }

    const user = upsertAccessRequestUser(data, accessRequest, now);
    const client = findClientById(data, accessRequest.targetClientId);
    const membership = upsertAccessRequestMembership(data, user, accessRequest, {
      status: "active",
      clientId: client.id,
      invitedByUserId: actor.id,
      now,
    });

    if (client.id !== publicPoolClientId) {
      data.memberships = data.memberships.filter(
        (candidate) =>
          !(
            candidate.userId === user.id &&
            candidate.clientId === publicPoolClientId &&
            candidate.source === "access_request" &&
            candidate.status === "disabled"
          )
      );
    }

    if (accessRequest.status === "approved") {
      markPendingSync(data, user, "user", user.email);
      await writeData(data);
      return { accessRequest, user, membership, invite: null, data };
    }

    accessRequest.status = "approved";
    accessRequest.approvedInviteId = null;
    accessRequest.reviewedByUserId = actor.id;
    accessRequest.reviewedAt = now;
    accessRequest.updatedAt = now;

    addAuditEvent(data, actor, {
      action: "Подтверждена публичная заявка",
      objectType: "access_request",
      objectName: accessRequest.email,
      clientId: client.id === publicPoolClientId ? null : client.id,
      result: "success",
      details: `Account activated; target: ${client.name}; role: ${membership.role}`,
    });
    markPendingSync(data, user, "user", user.email);

    await writeData(data);
    return { accessRequest, user, membership, invite: null, data };
  }

  async function rejectAccessRequest(accessRequestId, payload, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const accessRequest = findAccessRequestById(data, accessRequestId);
    const now = isoNow();

    if (accessRequest.status === "approved") {
      throw new Error("Подтверждённую заявку нельзя отклонить");
    }

    accessRequest.status = "rejected";
    accessRequest.comment = nullableStringWithFallback(payload?.comment, accessRequest.comment ?? null);
    accessRequest.reviewedByUserId = actor.id;
    accessRequest.reviewedAt = now;
    accessRequest.updatedAt = now;

    addAuditEvent(data, actor, {
      action: "Отклонена публичная заявка",
      objectType: "access_request",
      objectName: accessRequest.email,
      clientId: accessRequest.targetClientId === publicPoolClientId ? null : accessRequest.targetClientId,
      result: "warning",
      details: accessRequest.comment,
    });

    await writeData(data);
    return { accessRequest, data };
  }

  async function createTaskerInviteRequest(payload, identity = { name: "Operational Core", source: "tasker" }) {
    const data = readData();
    const now = isoNow();
    const actor = resolveActor(data, identity);
    const taskerInviteId = requireString(payload?.taskerInviteId, "taskerInviteId");
    const workspaceSlug = requireString(payload?.workspaceSlug, "workspaceSlug");
    const workspaceName = optionalString(payload?.workspaceName, workspaceSlug);
    const inviteeEmail = requireString(payload?.inviteeEmail, "inviteeEmail").toLowerCase();
    const role = normalizeTaskManagerInviteRole(payload?.role);
    const inviteeUser = data.users.find((user) => user.email.toLowerCase() === inviteeEmail && user.globalStatus === "active");
    const autoApproveExistingUser = Boolean(inviteeUser && !hasTaskManagerDenyException(data, inviteeUser.id));
    const existingRequest = data.taskerInviteRequests.find(
      (request) =>
        request.taskerInviteId === taskerInviteId ||
        (request.status === "new" && request.workspaceSlug === workspaceSlug && request.inviteeEmail.toLowerCase() === inviteeEmail)
    );
    const request =
      existingRequest ??
      {
        id: uniqueId(data.taskerInviteRequests, "tasker_invite_request", `${workspaceSlug}-${inviteeEmail}`),
        taskerInviteId,
        createdAt: now,
      };
    let nextStatus = "new";
    if (existingRequest?.status && existingRequest.status !== "rejected") {
      nextStatus = existingRequest.status;
    }
    if (autoApproveExistingUser) {
      nextStatus = "approved";
    }

    let auditAction = existingRequest ? "Обновлена заявка workspace-инвайта" : "Создана заявка workspace-инвайта";
    if (autoApproveExistingUser) {
      auditAction = "Автоподтверждена заявка workspace-инвайта";
    }

    Object.assign(request, {
      taskerInviteId,
      workspaceId: nullableStringWithFallback(payload?.workspaceId, request.workspaceId ?? null),
      workspaceSlug,
      workspaceName,
      inviteeEmail,
      role,
      inviterUserId: nullableStringWithFallback(payload?.inviterUserId, request.inviterUserId ?? null),
      inviterPlaneUserId: nullableStringWithFallback(payload?.inviterPlaneUserId, request.inviterPlaneUserId ?? null),
      inviterEmail: requireString(payload?.inviterEmail, "inviterEmail").toLowerCase(),
      inviterName: optionalString(payload?.inviterName, payload?.inviterEmail ?? "Operational Core user"),
      status: nextStatus,
      taskerInviteLink: existingRequest?.taskerInviteLink ?? null,
      reviewedByUserId: autoApproveExistingUser ? actor.id : existingRequest?.reviewedByUserId ?? null,
      reviewedAt: autoApproveExistingUser ? now : existingRequest?.reviewedAt ?? null,
      comment: autoApproveExistingUser
        ? "Автоподтверждено: пользователь уже активен в NODE.DC."
        : nullableStringWithFallback(payload?.comment, existingRequest?.comment ?? null),
      updatedAt: now,
    });

    if (!existingRequest) {
      data.taskerInviteRequests.push(request);
    }

    if (autoApproveExistingUser && inviteeUser) {
      ensureTaskerInviteServiceAccess(
        data,
        {
          source: "tasker_workspace_invite",
          sourceTaskerInviteRequestId: request.id,
        },
        inviteeUser,
        now
      );
    }

    addAuditEvent(data, actor, {
      action: auditAction,
      objectType: "tasker_invite_request",
      objectName: `${workspaceSlug}:${inviteeEmail}`,
      result: "success",
      details: `Role: ${role}`,
    });

    await writeData(data);
    return {
      taskerInviteRequest: request,
      autoApproved: autoApproveExistingUser,
      affectedUserIds: [payload?.inviterUserId, inviteeUser?.id].filter((userId) => typeof userId === "string" && userId),
      data,
    };
  }

  async function approveTaskerInviteRequest(taskerInviteRequestId, payload, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const request = findTaskerInviteRequestById(data, taskerInviteRequestId);
    const now = isoNow();

    if (request.status === "rejected") {
      throw new Error("Отклонённую заявку workspace-инвайта нельзя подтвердить");
    }

    request.status = "approved";
    request.taskerInviteLink = nullableStringWithFallback(payload?.taskerInviteLink, request.taskerInviteLink ?? null);
    request.platformInviteId = nullableStringWithFallback(payload?.platformInviteId, request.platformInviteId ?? null);
    request.platformInviteToken = nullableStringWithFallback(payload?.platformInviteToken, request.platformInviteToken ?? null);
    request.comment = nullableStringWithFallback(payload?.comment, request.comment ?? null);
    request.reviewedByUserId = actor.id;
    request.reviewedAt = now;
    request.updatedAt = now;

    if (request.platformInviteId) {
      const invite = data.invites.find((candidate) => candidate.id === request.platformInviteId);
      if (invite) {
        invite.sourceTaskerInviteLink = request.taskerInviteLink ?? invite.sourceTaskerInviteLink ?? null;
        invite.updatedAt = now;
      }
    }

    addAuditEvent(data, actor, {
      action: "Подтверждена заявка workspace-инвайта",
      objectType: "tasker_invite_request",
      objectName: `${request.workspaceSlug}:${request.inviteeEmail}`,
      result: "success",
      details: request.taskerInviteLink ?? null,
    });

    await writeData(data);
    return { taskerInviteRequest: request, data };
  }

  async function ensureTaskerInvitePlatformInvite(taskerInviteRequestId, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const request = findTaskerInviteRequestById(data, taskerInviteRequestId);
    const now = isoNow();
    const client = findClientById(data, publicPoolClientId);
    let invite = request.platformInviteId ? data.invites.find((candidate) => candidate.id === request.platformInviteId) : null;

    if (!invite) {
      invite = data.invites.find(
        (candidate) =>
          candidate.source === "tasker_workspace_invite" &&
          candidate.sourceTaskerInviteRequestId === request.id &&
          candidate.email.toLowerCase() === request.inviteeEmail.toLowerCase()
      );
    }

    if (!invite) {
      invite = {
        id: uniqueId(data.invites, "invite", `${request.workspaceSlug}-${request.inviteeEmail}`),
        clientId: client.id,
        email: request.inviteeEmail,
        role: "member",
        invitedByUserId: request.inviterUserId || actor.id,
        source: "tasker_workspace_invite",
        sourceTaskerInviteRequestId: request.id,
        sourceTaskerInviteId: request.taskerInviteId,
        sourceWorkspaceSlug: request.workspaceSlug,
        sourceWorkspaceName: request.workspaceName,
        sourceTaskerInviteLink: request.taskerInviteLink ?? null,
        token: randomUUID(),
        expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
        status: "created",
        createdAt: now,
        updatedAt: now,
      };
      data.invites.push(invite);
      addAuditEvent(data, actor, {
        action: "Создан platform-инвайт для workspace-инвайта",
        objectType: "invite",
        objectName: invite.email,
        clientId: client.id,
        result: "success",
        details: `${request.workspaceSlug}; inviter: ${request.inviterEmail}`,
      });
    } else {
      invite.clientId = client.id;
      invite.role = "member";
      invite.invitedByUserId = request.inviterUserId || invite.invitedByUserId || actor.id;
      invite.source = "tasker_workspace_invite";
      invite.sourceTaskerInviteRequestId = request.id;
      invite.sourceTaskerInviteId = request.taskerInviteId;
      invite.sourceWorkspaceSlug = request.workspaceSlug;
      invite.sourceWorkspaceName = request.workspaceName;
      invite.status = invite.status === "revoked" || invite.status === "expired" ? "created" : invite.status;
      invite.updatedAt = now;
    }

    request.platformInviteId = invite.id;
    request.platformInviteToken = invite.token;
    request.updatedAt = now;

    await writeData(data);
    return { taskerInviteRequest: request, invite, data };
  }

  async function rejectTaskerInviteRequest(taskerInviteRequestId, payload, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const request = findTaskerInviteRequestById(data, taskerInviteRequestId);
    const now = isoNow();

    if (request.status === "approved") {
      throw new Error("Подтверждённую заявку workspace-инвайта нельзя отклонить");
    }

    request.status = "rejected";
    request.comment = nullableStringWithFallback(payload?.comment, request.comment ?? null);
    request.reviewedByUserId = actor.id;
    request.reviewedAt = now;
    request.updatedAt = now;

    addAuditEvent(data, actor, {
      action: "Отклонена заявка workspace-инвайта",
      objectType: "tasker_invite_request",
      objectName: `${request.workspaceSlug}:${request.inviteeEmail}`,
      result: "warning",
      details: request.comment ?? null,
    });

    await writeData(data);
    return { taskerInviteRequest: request, data };
  }

  async function createEngineWorkflowAccessRequest(payload, identity = { name: "NODE.DC Engine", source: "engine" }) {
    const data = readData();
    const now = isoNow();
    const actor = resolveActor(data, identity);
    const workflowId = requireString(payload?.workflowId, "workflowId");
    const workflowName = optionalString(payload?.workflowName, workflowId);
    const targetEmail = normalizeEmail(requireString(payload?.targetEmail ?? payload?.email, "targetEmail"));
    const role = normalizeEngineWorkflowRole(payload?.role);

    if (!isValidEmail(targetEmail)) {
      throw new Error("Введите корректную электронную почту");
    }

    const targetUser = data.users.find((user) => normalizeEmail(user.email) === targetEmail && user.globalStatus === "active");
    if (!targetUser) {
      throw new Error("engine_target_user_not_found");
    }

    const requesterEmail = normalizeEmail(payload?.requesterEmail ?? actor.email ?? "");
    const requesterUser =
      (payload?.requesterUserId ? data.users.find((user) => user.id === payload.requesterUserId) : null) ??
      (requesterEmail ? data.users.find((user) => normalizeEmail(user.email) === requesterEmail) : null) ??
      null;
    const requesterName = optionalString(payload?.requesterName, requesterUser?.name ?? actor.name ?? "NODE.DC Engine");
    const existingRequest = data.engineWorkflowAccessRequests.find(
      (request) =>
        request.status === "new" &&
        request.workflowId === workflowId &&
        request.targetUserId === targetUser.id
    );
    const request =
      existingRequest ??
      {
        id: uniqueId(data.engineWorkflowAccessRequests, "engine_workflow_access_request", `${workflowId}-${targetEmail}`),
        createdAt: now,
      };

    Object.assign(request, {
      requestType: "workflow",
      workflowId,
      workflowName,
      targetUserId: targetUser.id,
      targetEmail,
      targetName: targetUser.name ?? null,
      role,
      requestedServiceRole: null,
      currentServiceRole: null,
      requesterUserId: requesterUser?.id ?? nullableStringWithFallback(payload?.requesterUserId, null),
      requesterEmail: requesterEmail || normalizeEmail(payload?.requesterEmail) || actor.email || "engine@nodedc.ru",
      requesterName,
      status: "new",
      reviewedByUserId: null,
      reviewedAt: null,
      engineAppliedAt: null,
      comment: nullableStringWithFallback(payload?.comment, existingRequest?.comment ?? null),
      updatedAt: now,
    });

    if (!existingRequest) {
      data.engineWorkflowAccessRequests.push(request);
    }

    addAuditEvent(data, actor, {
      action: existingRequest ? "Обновлена заявка доступа Engine workflow" : "Создана заявка доступа Engine workflow",
      objectType: "engine_workflow_access_request",
      objectName: `${workflowName}:${targetEmail}`,
      result: "success",
      details: `Role: ${role}; requester: ${request.requesterEmail}`,
    });

    await writeData(data);
    return {
      engineWorkflowAccessRequest: request,
      affectedUserIds: [request.requesterUserId, targetUser.id, "user_root"].filter((userId) => typeof userId === "string" && userId),
      data,
    };
  }

  async function createEngineRoleAccessRequest(payload, identity = { name: "NODE.DC Engine", source: "engine" }) {
    const data = readData();
    const now = isoNow();
    const actor = resolveActor(data, identity);
    const requesterEmail = normalizeEmail(payload?.requesterEmail ?? actor.email ?? payload?.email ?? "");
    const targetUser =
      (payload?.requesterUserId ? data.users.find((user) => user.id === payload.requesterUserId) : null) ??
      (requesterEmail ? data.users.find((user) => normalizeEmail(user.email) === requesterEmail) : null) ??
      null;

    if (!targetUser || targetUser.globalStatus !== "active") {
      throw new Error("engine_target_user_not_found");
    }

    const requestedServiceRole = normalizeEngineServiceRole(payload?.requestedServiceRole ?? payload?.requestedRole ?? "member");
    const currentServiceRole = normalizeNullableEngineServiceRole(payload?.currentServiceRole ?? payload?.currentRole);
    const role = requestedServiceRole === "viewer" ? "viewer" : "editor";
    const existingRequest = data.engineWorkflowAccessRequests.find(
      (request) =>
        request.status === "new" &&
        request.requestType === "service_role" &&
        request.targetUserId === targetUser.id
    );
    const request =
      existingRequest ??
      {
        id: uniqueId(data.engineWorkflowAccessRequests, "engine_role_access_request", targetUser.email),
        createdAt: now,
      };

    Object.assign(request, {
      requestType: "service_role",
      workflowId: "engine:service-role",
      workflowName: "NODE.DC Engine",
      targetUserId: targetUser.id,
      targetEmail: normalizeEmail(targetUser.email),
      targetName: targetUser.name ?? null,
      role,
      requestedServiceRole,
      currentServiceRole,
      requesterUserId: targetUser.id,
      requesterEmail: normalizeEmail(targetUser.email),
      requesterName: optionalString(payload?.requesterName, targetUser.name ?? targetUser.email),
      status: "new",
      reviewedByUserId: null,
      reviewedAt: null,
      engineAppliedAt: null,
      comment: nullableStringWithFallback(payload?.comment, existingRequest?.comment ?? null),
      updatedAt: now,
    });

    if (!existingRequest) {
      data.engineWorkflowAccessRequests.push(request);
    }

    addAuditEvent(data, actor, {
      action: existingRequest ? "Обновлена заявка повышения роли Engine" : "Создана заявка повышения роли Engine",
      objectType: "engine_role_access_request",
      objectName: `${request.targetName ?? request.targetEmail}:${requestedServiceRole}`,
      result: "success",
      details: `Current role: ${currentServiceRole ?? "unknown"}; requested role: ${requestedServiceRole}`,
    });

    await writeData(data);
    return {
      engineWorkflowAccessRequest: request,
      affectedUserIds: [targetUser.id, "user_root"].filter(Boolean),
      targetUser,
      data,
    };
  }

  async function approveEngineWorkflowAccessRequest(engineWorkflowAccessRequestId, payload, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const request = findEngineWorkflowAccessRequestById(data, engineWorkflowAccessRequestId);
    const now = isoNow();

    if (request.status === "rejected") {
      throw new Error("Отклонённую заявку Engine нельзя подтвердить");
    }

    const targetUser = findById(data.users, request.targetUserId, "user");
    if (request.requestType === "service_role") {
      const serviceRole = normalizeEngineServiceRole(payload?.serviceRole ?? payload?.requestedServiceRole ?? request.requestedServiceRole);
      request.requestedServiceRole = serviceRole;
      request.role = serviceRole === "viewer" ? "viewer" : "editor";
      const membership = ensureEngineWorkflowPublicPoolMembership(data, targetUser, request, actor, now);
      const grant = ensureEngineWorkflowServiceAccess(data, targetUser, request.role, now);

      request.status = "approved";
      request.reviewedByUserId = actor.id;
      request.reviewedAt = now;
      request.engineAppliedAt = nullableStringWithFallback(payload?.engineAppliedAt, now);
      request.comment = nullableStringWithFallback(payload?.comment, request.comment ?? null);
      request.updatedAt = now;

      addAuditEvent(data, actor, {
        action: "Подтверждена заявка повышения роли Engine",
        objectType: "engine_role_access_request",
        objectName: `${request.targetName ?? request.targetEmail}:${serviceRole}`,
        result: "success",
        details: `Engine role: ${serviceRole}`,
      });

      await writeData(data);
      return { engineWorkflowAccessRequest: request, grant, membership, targetUser, data };
    }

    const membership = ensureEngineWorkflowPublicPoolMembership(data, targetUser, request, actor, now);
    const grant = ensureEngineWorkflowServiceAccess(data, targetUser, request.role, now);

    request.status = "approved";
    request.reviewedByUserId = actor.id;
    request.reviewedAt = now;
    request.engineAppliedAt = nullableStringWithFallback(payload?.engineAppliedAt, now);
    request.comment = nullableStringWithFallback(payload?.comment, request.comment ?? null);
    request.updatedAt = now;

    addAuditEvent(data, actor, {
      action: "Подтверждена заявка доступа Engine workflow",
      objectType: "engine_workflow_access_request",
      objectName: `${request.workflowName}:${request.targetEmail}`,
      result: "success",
      details: `Workflow: ${request.workflowId}; role: ${request.role}`,
    });

    await writeData(data);
    return { engineWorkflowAccessRequest: request, grant, membership, targetUser, data };
  }

  async function rejectEngineWorkflowAccessRequest(engineWorkflowAccessRequestId, payload, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const request = findEngineWorkflowAccessRequestById(data, engineWorkflowAccessRequestId);
    const now = isoNow();

    if (request.status === "approved") {
      throw new Error("Подтверждённую заявку Engine нельзя отклонить");
    }

    request.status = "rejected";
    request.reviewedByUserId = actor.id;
    request.reviewedAt = now;
    request.comment = nullableStringWithFallback(payload?.comment, request.comment ?? null);
    request.updatedAt = now;

    addAuditEvent(data, actor, {
      action: request.requestType === "service_role" ? "Отклонена заявка повышения роли Engine" : "Отклонена заявка доступа Engine workflow",
      objectType: request.requestType === "service_role" ? "engine_role_access_request" : "engine_workflow_access_request",
      objectName: request.requestType === "service_role"
        ? `${request.targetName ?? request.targetEmail}:${request.requestedServiceRole ?? request.role}`
        : `${request.workflowName}:${request.targetEmail}`,
      result: "warning",
      details: request.comment ?? null,
    });

    await writeData(data);
    return { engineWorkflowAccessRequest: request, data };
  }

  async function cancelTaskerInviteRequest(payload, identity = { name: "Operational Core", source: "tasker" }) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const request = findTaskerInviteRequestForCancellation(data, payload);

    if (!request) {
      return { taskerInviteRequest: null, affectedUserIds: [], data };
    }

    request.status = "cancelled";
    request.taskerInviteLink = null;
    request.comment = nullableStringWithFallback(payload?.comment, "Отозвано в Operational Core");
    request.updatedAt = isoNow();
    const affectedUserIds = revokeTaskerInviteServiceAccessIfOrphaned(data, request, request.updatedAt);

    if (request.platformInviteId) {
      const invite = data.invites.find((candidate) => candidate.id === request.platformInviteId);
      if (invite && invite.status !== "accepted") {
        invite.status = "revoked";
        invite.updatedAt = request.updatedAt;
      }
    }

    addAuditEvent(data, actor, {
      action: "Отозвана заявка workspace-инвайта",
      objectType: "tasker_invite_request",
      objectName: `${request.workspaceSlug}:${request.inviteeEmail}`,
      result: "warning",
      details: request.comment,
    });

    await writeData(data);
    return { taskerInviteRequest: request, affectedUserIds, data };
  }

  async function updateInvite(inviteId, payload, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const invite = findById(data.invites, inviteId, "invite");

    invite.email = optionalString(payload?.email, invite.email).toLowerCase();
    invite.role = pickEnum(payload?.role, membershipRoles, invite.role);
    invite.expiresAt = optionalString(payload?.expiresAt, invite.expiresAt);
    invite.status = pickEnum(payload?.status, new Set(["created", "sent", "accepted", "expired", "revoked"]), invite.status);
    invite.updatedAt = isoNow();

    addAuditEvent(data, actor, {
      action: "Обновлён инвайт",
      objectType: "invite",
      objectName: invite.email,
      clientId: invite.clientId,
      result: "success",
    });
    markPendingSync(data, invite, "invite", invite.email);

    await writeData(data);
    return { invite, data };
  }

  async function deleteInvite(inviteId, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const invite = findById(data.invites, inviteId, "invite");

    data.invites = data.invites.filter((item) => item.id !== inviteId);
    data.syncStatuses = data.syncStatuses.filter((syncStatus) => syncStatus.objectId !== inviteId);
    addAuditEvent(data, actor, {
      action: "Удалён инвайт",
      objectType: "invite",
      objectName: invite.email,
      clientId: invite.clientId,
      result: "warning",
    });

    await writeData(data);
    return { invite, data };
  }

  function getInviteByToken(token) {
    const data = readData();
    const invite = findInviteByToken(data, token);
    const client = findClientById(data, invite.clientId);
    const existingUser = data.users.find((user) => user.email.toLowerCase() === invite.email.toLowerCase()) ?? null;

    return {
      invite: toPublicInvite(invite),
      client: toPublicClient(client),
      redirectUrl: resolvePublicInviteRedirectUrl(invite),
      account: {
        exists: Boolean(existingUser?.authentikUserId),
        email: invite.email,
      },
    };
  }

  function prepareInviteRegistration(token, payload = {}) {
    const data = readData();
    const result = applyInviteRegistration(data, token, payload, { commit: false });

    return result;
  }

  async function commitInviteRegistration(token, payload = {}, provisioning) {
    const data = readData();
    const result = applyInviteRegistration(data, token, payload, { commit: true, provisioning });

    await writeData(data);
    return result;
  }

  async function acceptInvite(token, identity) {
    const data = readData();
    const invite = findInviteByToken(data, token);
    const client = findClientById(data, invite.clientId);
    const email = requireInviteIdentityEmail(identity);
    const now = isoNow();

    if (invite.email.toLowerCase() !== email) {
      throw new Error("Этот инвайт выписан на другую почту");
    }

    if (invite.status === "accepted") {
      throw new Error("Инвайт уже принят");
    }

    if (invite.status === "revoked") {
      throw new Error("Инвайт отозван");
    }

    if (invite.status === "expired" || isInviteExpired(invite)) {
      invite.status = "expired";
      invite.updatedAt = now;
      await writeData(data);
      throw new Error("Срок действия инвайта истёк");
    }
    validateTaskerInviteSourceCanBeAccepted(data, invite);

    const actor = resolveActor(data, identity);
    let user = data.users.find((item) => item.email.toLowerCase() === email);

    if (user?.globalStatus === "blocked") {
      throw new Error("Аккаунт заблокирован. Обратитесь к администратору NODE.DC.");
    }

    if (user) {
      user.authentikUserId = optionalString(identity?.sub, user.authentikUserId ?? null);
      user.name = optionalString(identity?.name, user.name);
      user.avatarUrl = nullableStringWithFallback(identity?.avatarUrl, user.avatarUrl ?? null);
      user.globalStatus = "active";
      user.updatedAt = now;
    } else {
      user = {
        id: uniqueId(data.users, "user", email),
        authentikUserId: nullableString(identity?.sub),
        name: optionalString(identity?.name, email.split("@")[0]),
        email,
        phone: null,
        position: null,
        notes: `Создан через инвайт клиента ${client.name}.`,
        avatarUrl: nullableString(identity?.avatarUrl),
        globalStatus: "active",
        createdAt: now,
        updatedAt: now,
      };
      data.users.push(user);
    }

    let membership = data.memberships.find((item) => item.clientId === invite.clientId && item.userId === user.id);

    if (membership) {
      membership.role = invite.role;
      membership.status = "active";
      membership.coreAssistantRole = normalizeCoreAssistantRole(membership.coreAssistantRole);
      membership.invitedByUserId = invite.invitedByUserId ?? membership.invitedByUserId ?? null;
      membership.inviteId = invite.id;
      membership.source = invite.source ?? membership.source ?? "launcher";
      membership.sourceTaskerInviteRequestId = invite.sourceTaskerInviteRequestId ?? membership.sourceTaskerInviteRequestId ?? null;
      membership.updatedAt = now;
    } else {
      membership = {
        id: uniqueId(data.memberships, "mem", `${invite.clientId}-${email}`),
        clientId: invite.clientId,
        userId: user.id,
        role: invite.role,
        status: "active",
        coreAssistantRole: "member",
        invitedByUserId: invite.invitedByUserId ?? null,
        inviteId: invite.id,
        source: invite.source ?? "launcher",
        sourceTaskerInviteRequestId: invite.sourceTaskerInviteRequestId ?? null,
        createdAt: now,
        updatedAt: now,
      };
      data.memberships.push(membership);
    }

    ensureTaskerInviteServiceAccess(data, invite, user, now);
    clearRevokedAccount(data, email);
    invite.status = "accepted";
    invite.updatedAt = now;

    addAuditEvent(data, actor, {
      action: "Инвайт принят",
      objectType: "invite",
      objectName: invite.email,
      clientId: client.id,
      result: "success",
      details: `Role: ${invite.role}`,
    });
    markPendingSync(data, user, "user", email);

    await writeData(data);
    return { invite, client, user, membership, data };
  }

  async function createGroup(payload, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const now = isoNow();
    const clientId = requireString(payload?.clientId, "clientId");
    const client = findById(data.clients, clientId, "client");
    const groupName = optionalString(payload?.name, "Новая группа");
    const group = {
      id: uniqueId(data.groups, "group", `${clientId}-${groupName}`),
      clientId,
      name: groupName,
      description: nullableString(payload?.description),
      memberIds: Array.isArray(payload?.memberIds) ? payload.memberIds.filter((userId) => data.users.some((user) => user.id === userId)) : [],
      createdAt: now,
      updatedAt: now,
    };

    data.groups.push(group);
    addAuditEvent(data, actor, {
      action: "Создана группа",
      objectType: "group",
      objectName: group.name,
      clientId: client.id,
      result: "success",
    });
    markPendingSync(data, group, "group");

    await writeData(data);
    return { group, data };
  }

  async function updateGroup(groupId, payload, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const group = findById(data.groups, groupId, "group");

    group.name = optionalString(payload?.name, group.name);
    group.description = nullableStringWithFallback(payload?.description, group.description ?? null);
    group.memberIds = Array.isArray(payload?.memberIds)
      ? payload.memberIds.filter((userId) => data.users.some((user) => user.id === userId))
      : group.memberIds;
    group.updatedAt = isoNow();

    addAuditEvent(data, actor, {
      action: "Обновлена группа",
      objectType: "group",
      objectName: group.name,
      clientId: group.clientId,
      result: "success",
    });
    markPendingSync(data, group, "group");

    await writeData(data);
    return { group, data };
  }

  async function deleteGroup(groupId, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const group = findById(data.groups, groupId, "group");

    data.groups = data.groups.filter((item) => item.id !== groupId);
    data.grants = data.grants.filter((grant) => !(grant.targetType === "group" && grant.targetId === groupId));
    data.syncStatuses = data.syncStatuses.filter((syncStatus) => syncStatus.objectId !== groupId);
    addAuditEvent(data, actor, {
      action: "Удалена группа",
      objectType: "group",
      objectName: group.name,
      clientId: group.clientId,
      result: "warning",
    });

    await writeData(data);
    return { group, data };
  }

  async function upsertGrant(payload, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const now = isoNow();
    const serviceId = requireString(payload?.serviceId, "serviceId");
    const targetType = pickEnum(payload?.targetType, grantTargetTypes, "client");
    const targetId = requireString(payload?.targetId, "targetId");
    const service = findById(data.services, serviceId, "service");

    assertGrantTargetExists(data, targetType, targetId);

    const existingGrant = data.grants.find(
      (grant) => grant.serviceId === serviceId && grant.targetType === targetType && grant.targetId === targetId
    );
    const nextAppRole = pickEnum(payload?.appRole, appRoles, existingGrant?.appRole ?? "member");

    if (targetType === "user" && isProtectedLauncherUserId(targetId)) {
      throw new Error("Доступ системного root-пользователя зафиксирован платформой");
    }

    if (isOperationalCoreService(service) && (nextAppRole === "admin" || nextAppRole === "owner")) {
      throw new Error("Operational Core admin-роль назначается только системно");
    }

    const grant =
      existingGrant ??
      {
        id: uniqueId(data.grants, "grant", `${service.slug}-${targetType}-${targetId}`),
        serviceId,
        targetType,
        targetId,
        createdAt: now,
      };

    grant.appRole = nextAppRole;
    grant.status = pickEnum(payload?.status, grantStatuses, existingGrant?.status ?? "active");
    grant.updatedAt = now;

    if (!existingGrant) {
      data.grants.push(grant);
    }

    addAuditEvent(data, actor, {
      action: existingGrant ? "Обновлён доступ" : "Создан доступ",
      objectType: "grant",
      objectName: `${service.slug}:${targetType}:${targetId}`,
      result: "success",
      details: `App role: ${grant.appRole}; status: ${grant.status}`,
    });
    markPendingSync(data, grant, "grant", `${service.slug}:${targetType}:${targetId}`);

    await writeData(data);
    return { grant, data };
  }

  async function upsertException(payload, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const now = isoNow();
    const serviceId = requireString(payload?.serviceId, "serviceId");
    const userId = requireString(payload?.userId, "userId");
    const type = pickEnum(payload?.type, exceptionTypes, "deny");
    const service = findById(data.services, serviceId, "service");
    const user = findById(data.users, userId, "user");

    if (isProtectedLauncherUserId(user.id)) {
      throw new Error("Доступ системного root-пользователя зафиксирован платформой");
    }

    const existingException = data.exceptions.find(
      (exception) => exception.serviceId === serviceId && exception.userId === userId
    );
    const exception =
      existingException ??
      {
        id: uniqueId(data.exceptions, "exception", `${service.slug}-${user.email}`),
        serviceId,
        userId,
        createdAt: now,
      };

    exception.type = type;
    exception.reason = nullableString(payload?.reason);
    exception.updatedAt = now;

    if (!existingException) {
      data.exceptions.push(exception);
    }

    addAuditEvent(data, actor, {
      action: existingException ? "Обновлено исключение доступа" : "Создано исключение доступа",
      objectType: "exception",
      objectName: `${service.slug}:${user.email}`,
      result: "success",
      details: `Type: ${type}`,
    });
    markPendingSync(data, exception, "grant", `${service.slug}:${user.email}`);

    await writeData(data);
    return { exception, data };
  }

  async function setUserServiceAccess(payload, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const now = isoNow();
    const userId = requireString(payload?.userId, "userId");
    const serviceId = requireString(payload?.serviceId, "serviceId");
    const value = requireString(payload?.value, "value");
    const user = findById(data.users, userId, "user");
    const service = findById(data.services, serviceId, "service");
    const engineService = isEngineService(service);
    const previousEngineServiceRole = engineService ? resolveDirectEngineServiceRole(data, serviceId, userId) : null;

    if (isProtectedLauncherUserId(user.id)) {
      throw new Error("Доступ системного root-пользователя зафиксирован платформой");
    }

    if (engineService && (value === "admin" || value === "owner")) {
      throw new Error("Для Engine доступны только роли guest, member или блокировка");
    }

    if (isOperationalCoreService(service) && (value === "admin" || value === "owner")) {
      throw new Error("Operational Core admin-роль назначается только системно");
    }

    const directGrant = data.grants.find(
      (grant) => grant.serviceId === serviceId && grant.targetType === "user" && grant.targetId === userId
    );
    data.grants = data.grants.filter(
      (grant) => !(grant.serviceId === serviceId && grant.targetType === "user" && grant.targetId === userId)
    );
    data.exceptions = data.exceptions.filter((exception) => !(exception.serviceId === serviceId && exception.userId === userId));

    if (value === "deny") {
      data.exceptions.push({
        id: uniqueId(data.exceptions, "exception", `${service.slug}-${user.email}`),
        serviceId,
        userId,
        type: "deny",
        reason: "Создано из матрицы доступа.",
        createdAt: now,
        updatedAt: now,
      });
    } else if (appRoles.has(value) && value !== "owner") {
      data.grants.push({
        id: directGrant?.id ?? uniqueId(data.grants, "grant", `${service.slug}-user-${user.email}`),
        serviceId,
        targetType: "user",
        targetId: userId,
        appRole: value,
        status: "active",
        createdAt: directGrant?.createdAt ?? now,
        updatedAt: now,
      });
      if (engineService) {
        ensurePublicPoolMembershipActiveForServiceAccess(data, user, actor, now);
      }
    } else if (value !== "unset") {
      throw new Error(`Unsupported access value: ${value}`);
    }

    let engineWorkflowAccessRequest = null;
    if (engineService && (value === "viewer" || value === "member") && previousEngineServiceRole !== value) {
      engineWorkflowAccessRequest = appendEngineRoleChangeNotification(data, {
        actor,
        currentServiceRole: previousEngineServiceRole,
        serviceRole: value,
        targetUser: user,
        now,
      });
    }

    addAuditEvent(data, actor, {
      action: "Обновлён доступ пользователя к сервису",
      objectType: "grant",
      objectName: `${user.email} / ${service.slug}`,
      result: "success",
      details: `Value: ${value}`,
    });
    markPendingSync(data, { id: `${serviceId}:${userId}` }, "grant", `${service.slug}:${user.email}`);

    await writeData(data);
    return { data, engineWorkflowAccessRequest, affectedUserIds: [user.id] };
  }

  async function setServiceModuleEntitlement(payload, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const now = isoNow();
    const clientId = requireString(payload?.clientId, "clientId");
    const userId = requireString(payload?.userId, "userId");
    const serviceId = requireString(payload?.serviceId, "serviceId");
    const moduleId = pickEnum(payload?.moduleId, serviceModuleIds, "codex_agents");
    const enabled = payload?.enabled === true;
    const client = findClientById(data, clientId);
    const user = findById(data.users, userId, "user");
    const service = findById(data.services, serviceId, "service");

    const existingEntitlement = data.serviceModuleEntitlements.find(
      (entitlement) => entitlement.userId === user.id && entitlement.serviceId === service.id && entitlement.moduleId === moduleId
    );

    let entitlement = null;

    if (enabled) {
      entitlement =
        existingEntitlement ??
        {
          id: uniqueId(data.serviceModuleEntitlements, "svc_module", `${client.id}-${service.slug}-${user.email}-${moduleId}`),
          userId: user.id,
          serviceId: service.id,
          moduleId,
          createdByUserId: actor.id,
          createdAt: now,
        };
      entitlement.clientId = client.id;
      entitlement.enabled = true;
      entitlement.updatedAt = now;

      if (!existingEntitlement) {
        data.serviceModuleEntitlements.push(entitlement);
      }
    } else {
      data.serviceModuleEntitlements = data.serviceModuleEntitlements.filter(
        (candidate) => !(candidate.userId === user.id && candidate.serviceId === service.id && candidate.moduleId === moduleId)
      );
    }

    addAuditEvent(data, actor, {
      action: enabled ? "Включён модуль сервиса" : "Отключён модуль сервиса",
      objectType: "service-module-entitlement",
      objectName: `${service.slug}:${moduleId}:${user.email}`,
      clientId: client.id,
      result: "success",
      details: `Module: ${moduleId}; enabled: ${enabled}`,
    });

    await writeData(data);
    return { entitlement, data };
  }

  async function updateService(serviceId, payload, identity) {
    return enqueueMutation(async () => {
      const data = readData();
      const actor = resolveActor(data, identity);
      const service = findById(data.services, serviceId, "service");

      Object.assign(service, sanitizeServicePatch(payload, service));
      Object.assign(service, syncServiceLaunchLink(service));
      service.updatedAt = isoNow();

      addAuditEvent(data, actor, {
        action: "Обновлён сервис",
        objectType: "service",
        objectName: service.title,
        result: "success",
      });
      markPendingSync(data, service, "service");

      await writeData(data);
      return { service, data };
    });
  }

  async function reorderServices(payload, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const orderedServiceIds = Array.isArray(payload?.orderedServiceIds) ? payload.orderedServiceIds : [];
    const orderById = new Map(orderedServiceIds.map((serviceId, index) => [serviceId, (index + 1) * 10]));
    const now = isoNow();

    data.services = data.services.map((service) => ({
      ...service,
      order: orderById.get(service.id) ?? service.order,
      updatedAt: orderById.has(service.id) ? now : service.updatedAt,
    }));

    addAuditEvent(data, actor, {
      action: "Изменён порядок сервисов",
      objectType: "service",
      objectName: "Каталог сервисов",
      result: "success",
    });

    await writeData(data);
    return { data };
  }

  async function createService(payload, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const now = isoNow();
    const nextIndex = data.services.length + 1;
    const nextOrder = Math.max(0, ...data.services.map((service) => Number(service.order) || 0)) + 10;
    const title = optionalString(payload?.title, "New Service");
    const service = syncServiceLaunchLink({
      id: uniqueId(data.services, "service", title),
      slug: optionalString(payload?.slug, `new-service-${nextIndex}`),
      title,
      subtitle: nullableString(payload?.subtitle) ?? "Новый сервис",
      description: optionalString(payload?.description, "Описание сервиса для витрины."),
      fullDescription: nullableString(payload?.fullDescription) ?? "Заполните описание, медиа и ссылку запуска в редакторе контента.",
      url: optionalString(payload?.url, "https://service.handhdc.ru/sso/launch"),
      launchUrl: nullableString(payload?.launchUrl) ?? optionalString(payload?.url, "https://service.handhdc.ru/sso/launch"),
      accentColor: nullableString(payload?.accentColor) ?? "#F7F8F4",
      fallbackGradient:
        nullableString(payload?.fallbackGradient) ??
        "linear-gradient(135deg, rgba(247, 248, 244, 0.72), rgba(36, 37, 42, 0.9) 52%, #090B0F 88%)",
      coverMediaSource: nullableString(payload?.coverMediaSource) ?? "url",
      coverMediaKind: nullableString(payload?.coverMediaKind) ?? "image",
      ambientMediaSource: nullableString(payload?.ambientMediaSource) ?? "url",
      ambientMediaKind: nullableString(payload?.ambientMediaKind) ?? "gif",
      status: pickEnum(payload?.status, serviceStatuses, "hidden"),
      order: nextOrder,
      authentikApplicationSlug: nullableString(payload?.authentikApplicationSlug) ?? `new-service-${nextIndex}`,
      authentikGroupName: nullableString(payload?.authentikGroupName) ?? `service-new-${nextIndex}`,
      createdAt: now,
      updatedAt: now,
    });

    data.services.push(service);
    addAuditEvent(data, actor, {
      action: "Создан сервис",
      objectType: "service",
      objectName: service.title,
      result: "success",
    });
    markPendingSync(data, service, "service");

    await writeData(data);
    return { service, data };
  }

  async function deleteService(serviceId, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const service = findById(data.services, serviceId, "service");

    data.services = data.services.filter((item) => item.id !== serviceId);
    data.grants = data.grants.filter((grant) => grant.serviceId !== serviceId);
    data.exceptions = data.exceptions.filter((exception) => exception.serviceId !== serviceId);
    data.syncStatuses = data.syncStatuses.filter((syncStatus) => syncStatus.objectId !== serviceId);
    addAuditEvent(data, actor, {
      action: "Удалён сервис",
      objectType: "service",
      objectName: service.title,
      result: "warning",
    });

    await writeData(data);
    return { service, data };
  }

  async function retrySync(syncId, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const syncStatus = findById(data.syncStatuses, syncId, "sync status");

    syncStatus.state = "pending";
    syncStatus.error = null;
    syncStatus.updatedAt = isoNow();
    addAuditEvent(data, actor, {
      action: "Повтор sync",
      objectType: syncStatus.objectType,
      objectName: syncStatus.objectName,
      result: "success",
      details: `Target: ${syncStatus.target}`,
    });

    await writeData(data);
    return { syncStatus, data };
  }

  async function updateSyncStatus(syncId, patch, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const syncStatus = findById(data.syncStatuses, syncId, "sync status");
    const now = isoNow();

    if ("state" in (patch ?? {})) {
      syncStatus.state = pickEnum(patch.state, syncStates, syncStatus.state);
    }

    if ("error" in (patch ?? {})) {
      syncStatus.error = nullableString(patch.error);
    }

    if ("objectName" in (patch ?? {})) {
      syncStatus.objectName = optionalString(patch.objectName, syncStatus.objectName);
    }

    if (syncStatus.state === "synced") {
      syncStatus.lastSyncAt = nullableString(patch?.lastSyncAt) ?? now;
      syncStatus.error = null;
    }

    syncStatus.updatedAt = now;
    addAuditEvent(data, actor, {
      action: "Статус sync обновлён",
      objectType: syncStatus.objectType,
      objectName: syncStatus.objectName,
      result: syncStatus.state === "error" ? "error" : "success",
      details: `Target: ${syncStatus.target}; State: ${syncStatus.state}`,
    });

    await writeData(data);
    return { syncStatus, data };
  }

  async function markUserAuthentikProvisioned(userId, provisioning, identity) {
    const data = readData();
    const actor = resolveActor(data, identity);
    const user = findById(data.users, userId, "user");
    const now = isoNow();

    user.authentikUserId = provisioning.authentikUserId ?? user.authentikUserId ?? null;
    user.email = optionalString(provisioning.email, user.email).toLowerCase();
    user.name = optionalString(provisioning.name, user.name);
    user.updatedAt = now;
    clearRevokedAccount(data, user.email);

    const syncStatus = data.syncStatuses.find(
      (status) => status.target === "authentik" && status.objectType === "user" && status.objectId === user.id
    );
    const objectName = user.email;

    if (syncStatus) {
      syncStatus.objectName = objectName;
      syncStatus.state = "synced";
      syncStatus.lastSyncAt = now;
      syncStatus.error = null;
      syncStatus.updatedAt = now;
    } else {
      data.syncStatuses.push({
        id: uniqueId(data.syncStatuses, "sync", `user-${user.id}`),
        objectId: user.id,
        objectName,
        objectType: "user",
        target: "authentik",
        state: "synced",
        lastSyncAt: now,
        error: null,
        updatedAt: now,
      });
    }

    addAuditEvent(data, actor, {
      action: "Пользователь синхронизирован в Authentik",
      objectType: "user",
      objectName,
      result: "success",
      details: `Groups: ${(provisioning.groups ?? []).join(", ") || "none"}`,
    });

    await writeData(data);
    return { user, data };
  }

  function buildAuthentikSyncPlan() {
    const data = readData();

    return {
      mode: "dry-run",
      source: "launcher-control-plane",
      target: "authentik",
      users: data.users.map((user) => ({
        id: user.id,
        authentikUserId: user.authentikUserId ?? null,
        email: user.email,
        name: user.name,
        avatarUrl: user.avatarUrl ?? null,
        active: user.globalStatus === "active",
      })),
      groups: [
        "nodedc:superadmin",
        "nodedc:launcher:admin",
        "nodedc:launcher:user",
        ...engineAuthentikGroups,
        ...data.services.flatMap((service) => (service.authentikGroupName ? [service.authentikGroupName] : [])),
        ...data.groups.map((group) => `client:${group.clientId}:group:${slugify(group.name)}`),
      ],
      accessProjection: {
        services: data.services.length,
        grants: data.grants.filter((grant) => grant.status === "active").length,
        exceptions: data.exceptions.length,
        pendingSyncObjects: data.syncStatuses.filter((syncStatus) => syncStatus.target === "authentik" && syncStatus.state === "pending").length,
      },
    };
  }

  function getWritableStorageRoots() {
    return [serverStorageRoot];
  }

  function getLoginAccountStatus(email) {
    const data = readData();
    const normalizedEmail = normalizeEmail(email);

    if (!normalizedEmail) {
      return { status: "unknown" };
    }

    const existingUser = data.users.find((user) => normalizeEmail(user.email) === normalizedEmail);
    if (existingUser) {
      return { status: "unknown" };
    }

    const revokedAccount = data.revokedAccounts.find((account) => normalizeEmail(account.email) === normalizedEmail);
    if (revokedAccount) {
      return { status: "revoked", revokedAt: revokedAccount.revokedAt ?? null };
    }

    const hardDeleteAuditEvent = data.auditEvents.find(
      (event) =>
        normalizeEmail(event.objectName) === normalizedEmail &&
        event.objectType === "user" &&
        event.action === "Пользователь удалён полностью"
    );

    return hardDeleteAuditEvent ? { status: "revoked", revokedAt: hardDeleteAuditEvent.at ?? null } : { status: "unknown" };
  }

  return {
    approveAccessRequest,
    approveEngineWorkflowAccessRequest,
    approveTaskerInviteRequest,
    buildAuthentikSyncPlan,
    cancelTaskerInviteRequest,
    createAccessRequest,
    createEngineRoleAccessRequest,
    createEngineWorkflowAccessRequest,
    createTaskerInviteRequest,
    createClient,
    createGroup,
    createInvite,
    createService,
    createUser,
    deleteClient,
    deleteGroup,
    deleteInvite,
    deleteMembership,
    deleteService,
    deleteUser,
    rejectAccessRequest,
    rejectEngineWorkflowAccessRequest,
    rejectTaskerInviteRequest,
    acceptInvite,
    commitInviteRegistration,
    getInviteByToken,
    getLoginAccountStatus,
    getSnapshot,
    ensureTaskerInvitePlatformInvite,
    prepareInviteRegistration,
    readData,
    replaceData,
    reorderServices,
    retrySync,
    updateSyncStatus,
    markUserAuthentikProvisioned,
    recordTaskManagerProjectMembership,
    recordTaskManagerWorkspaceMembership,
    removeTaskManagerProjectMembership,
    removeTaskManagerWorkspaceMembership,
    setServiceModuleEntitlement,
    setUserServiceAccess,
    updateAccessRequest,
    updateClient,
    updateGroup,
    updateInvite,
    updateMembership,
    updateService,
    updateSettings,
    updateUserProfile,
    upsertException,
    upsertGrant,
    writeData,
  };
}

function resolveStorageRoot(projectRoot) {
  const configuredRoot = process.env.NODEDC_LAUNCHER_STORAGE_DIR;

  if (configuredRoot && configuredRoot.trim()) {
    return isAbsolute(configuredRoot) ? configuredRoot : resolve(projectRoot, configuredRoot);
  }

  return join(projectRoot, "server", "storage");
}

function normalizeData(payload) {
  const data = typeof payload === "object" && payload !== null ? { ...payload } : {};

  for (const key of collectionKeys) {
    if (!Array.isArray(data[key])) {
      data[key] = [];
    }
  }

  data.settings = normalizeSettings(data.settings);
  data.clients = data.clients.map((client) => ({
    ...client,
    integrations: normalizeClientIntegrations(client.integrations),
  }));
  data.memberships = data.memberships.map(normalizeMembership).filter(Boolean);
  data.services = data.services.map(normalizeService);
  data.accessRequests = data.accessRequests.map(normalizeAccessRequest).filter(Boolean);
  data.revokedAccounts = data.revokedAccounts.map(normalizeRevokedAccount).filter(Boolean);
  data.serviceModuleEntitlements = data.serviceModuleEntitlements.map(normalizeServiceModuleEntitlement).filter(Boolean);
  data.taskerInviteRequests = data.taskerInviteRequests.map(normalizeTaskerInviteRequest).filter(Boolean);
  data.engineWorkflowAccessRequests = data.engineWorkflowAccessRequests.map(normalizeEngineWorkflowAccessRequest).filter(Boolean);
  applyProtectedLauncherUserInvariants(data);
  applyBimViewerServiceInvariants(data);
  return data;
}

function applyBimViewerServiceInvariants(data) {
  let service = data.services.find(
    (candidate) => candidate.id === bimViewerService.id || candidate.slug === bimViewerService.slug
  );

  if (!service) {
    service = normalizeService({ ...bimViewerService });
    data.services.push(service);
  } else {
    service.id = service.id || bimViewerService.id;
    service.slug = service.slug || bimViewerService.slug;
    service.title = service.title || bimViewerService.title;
    service.subtitle = service.subtitle || bimViewerService.subtitle;
    service.description = service.description || bimViewerService.description;
    service.fullDescription = service.fullDescription || bimViewerService.fullDescription;
    service.url = service.url || bimViewerService.url;
    service.launchUrl = service.launchUrl || service.url || bimViewerService.launchUrl;
    service.logoutUrl = service.logoutUrl || bimViewerService.logoutUrl;
    service.accentColor = service.accentColor || bimViewerService.accentColor;
    service.fallbackGradient = service.fallbackGradient || bimViewerService.fallbackGradient;
    service.status = service.status || bimViewerService.status;
    service.order = service.order ?? bimViewerService.order;
    service.authentikApplicationSlug = service.authentikApplicationSlug || bimViewerService.authentikApplicationSlug;
    service.authentikGroupName = service.authentikGroupName || bimViewerService.authentikGroupName;
    service.isAvailableForAllNewClients = service.isAvailableForAllNewClients ?? true;
    service.createdAt = service.createdAt || bimViewerService.createdAt;
    service.updatedAt = service.updatedAt || bimViewerService.updatedAt;
  }

  const serviceId = service.id || bimViewerService.id;
  for (const defaultGrant of bimViewerDefaultGrants) {
    const grant = { ...defaultGrant, serviceId };
    const exists = data.grants.some(
      (candidate) =>
        candidate.serviceId === grant.serviceId &&
        candidate.targetType === grant.targetType &&
        candidate.targetId === grant.targetId
    );
    if (!exists) {
      data.grants.push({ ...grant });
    }
  }

  ensureStaticSyncStatus(data, {
    id: "sync_bim_viewer_authentik",
    objectId: serviceId,
    objectName: bimViewerService.title,
    objectType: "service",
  });
  for (const defaultGrant of bimViewerDefaultGrants) {
    const grant = { ...defaultGrant, serviceId };
    ensureStaticSyncStatus(data, {
      id: `sync_${grant.id}`,
      objectId: grant.id,
      objectName: `${bimViewerService.slug}:${grant.targetType}:${grant.targetId}`,
      objectType: "grant",
    });
  }
}

function ensureStaticSyncStatus(data, status) {
  const exists = data.syncStatuses.some(
    (candidate) => candidate.target === "authentik" && candidate.objectType === status.objectType && candidate.objectId === status.objectId
  );
  if (exists) {
    return;
  }
  data.syncStatuses.push({
    id: status.id,
    objectId: status.objectId,
    objectName: status.objectName,
    objectType: status.objectType,
    target: "authentik",
    state: "pending",
    lastSyncAt: null,
    error: null,
    updatedAt: "2026-06-18T00:00:00.000Z",
  });
}

function applyProtectedLauncherUserInvariants(data) {
  const rootUser = data.users.find((user) => isProtectedLauncherUserId(user.id));
  if (!rootUser) return;

  const now = isoNow();
  if (rootUser.globalStatus !== "active") {
    rootUser.globalStatus = "active";
    rootUser.updatedAt = now;
  }

  for (const membership of data.memberships) {
    if (membership.userId !== rootUser.id) continue;
    if (membership.role !== "client_owner" || membership.status !== "active" || membership.coreAssistantRole !== "admin") {
      membership.role = "client_owner";
      membership.status = "active";
      membership.coreAssistantRole = "admin";
      membership.updatedAt = now;
    }
  }

  const engineService = data.services.find(isEngineService);
  if (engineService) {
    upsertSystemProtectedUserGrant(data, rootUser, engineService, "member", now);
  }

  const operationalCoreService = data.services.find(isOperationalCoreService);
  if (operationalCoreService) {
    upsertSystemProtectedUserGrant(data, rootUser, operationalCoreService, "admin", now);
  }

  const protectedServiceIds = new Set([engineService?.id, operationalCoreService?.id].filter(Boolean));
  if (protectedServiceIds.size) {
    data.exceptions = data.exceptions.filter(
      (exception) => !(exception.userId === rootUser.id && protectedServiceIds.has(exception.serviceId))
    );
  }
}

function upsertSystemProtectedUserGrant(data, user, service, appRole, now) {
  let existingGrant = null;
  data.grants = data.grants.filter((grant) => {
    const matches = grant.serviceId === service.id && grant.targetType === "user" && grant.targetId === user.id;
    if (!matches) return true;
    if (!existingGrant) {
      existingGrant = grant;
      return true;
    }
    return false;
  });

  const grant =
    existingGrant ??
    {
      id: uniqueId(data.grants, "grant", `${service.slug}-user-${user.email}`),
      serviceId: service.id,
      targetType: "user",
      targetId: user.id,
      createdAt: now,
    };

  if (grant.appRole !== appRole || grant.status !== "active") {
    grant.appRole = appRole;
    grant.status = "active";
    grant.updatedAt = now;
  } else {
    grant.updatedAt = grant.updatedAt ?? now;
  }

  if (!existingGrant) {
    data.grants.push(grant);
  }
}

function normalizeService(service) {
  if (typeof service !== "object" || service === null) return service;
  if (service.id === "service_bim_viewer" || service.slug === "bim-viewer" || service.authentikApplicationSlug === "bim-viewer") {
    const legacyLocalUrl = (value) => value === "http://localhost:8080" || value === "http://127.0.0.1:8080";
    return {
      ...service,
      url: !service.url || legacyLocalUrl(service.url) ? "https://bim.nodedc.tech" : service.url,
      launchUrl:
        !service.launchUrl || legacyLocalUrl(service.launchUrl)
          ? "https://bim.nodedc.tech"
          : service.launchUrl,
      logoutUrl: service.logoutUrl || "https://bim.nodedc.tech/logout",
      authentikApplicationSlug: service.authentikApplicationSlug || "bim-viewer",
      authentikGroupName: service.authentikGroupName || "nodedc:bim:access",
    };
  }
  if (service.id !== "service_nodedc" && service.slug !== "nodedc" && service.authentikApplicationSlug !== "nodedc") return service;

  return {
    ...service,
    title: service.title === "AGENT CORE" || service.title === "NodeDC" ? "ENGINE" : service.title,
    url: service.url === "https://nodedc.ru/" || service.url === "https://dev.handhdc.ru/sso/launch" ? "https://engine.nodedc.ru/" : service.url,
    launchUrl:
      service.launchUrl === "https://nodedc.ru/" || service.launchUrl === "https://dev.handhdc.ru/sso/launch"
        ? "https://engine.nodedc.ru/"
        : service.launchUrl,
    logoutUrl:
      !service.logoutUrl || service.logoutUrl === "https://nodedc.ru/logout"
        ? "https://engine.nodedc.ru/logout"
        : service.logoutUrl,
    authentikApplicationSlug: service.authentikApplicationSlug === "nodedc" ? "nodedc-engine" : service.authentikApplicationSlug,
    authentikGroupName:
      service.authentikGroupName === "service-nodedc" || service.authentikGroupName === "nodedc:engine:viewer"
        ? "nodedc_viewer"
        : service.authentikGroupName,
  };
}

function normalizeMembership(payload) {
  if (typeof payload !== "object" || payload === null) return null;
  const now = isoNow();
  const userId = optionalString(payload.userId, "");
  const fallbackAssistantRole = isProtectedLauncherUserId(userId) ? "admin" : "member";

  return {
    ...payload,
    role: pickEnum(payload.role, membershipRoles, "member"),
    status: pickEnum(payload.status, new Set(["active", "disabled"]), "active"),
    coreAssistantRole: normalizeCoreAssistantRole(payload.coreAssistantRole, fallbackAssistantRole),
    createdAt: optionalString(payload.createdAt, now),
    updatedAt: optionalString(payload.updatedAt, now),
  };
}

function normalizeCoreAssistantRole(value, fallback = "member") {
  return pickEnum(value, coreAssistantRoles, fallback);
}

function normalizeServiceModuleEntitlement(payload) {
  if (typeof payload !== "object" || payload === null) return null;
  const clientId = nullableString(payload.clientId);
  const serviceId = nullableString(payload.serviceId);
  const userId = nullableString(payload.userId);
  const moduleId = pickEnum(payload.moduleId, serviceModuleIds, null);
  if (!clientId || !serviceId || !userId || !moduleId || payload.enabled === false) return null;
  const now = isoNow();

  return {
    id: optionalString(payload.id, `svc_module_${slugify(`${clientId}-${serviceId}-${userId}-${moduleId}`)}`),
    clientId,
    serviceId,
    userId,
    moduleId,
    enabled: true,
    createdByUserId: nullableStringWithFallback(payload.createdByUserId, null),
    createdAt: optionalString(payload.createdAt, now),
    updatedAt: optionalString(payload.updatedAt, now),
  };
}

function normalizeRevokedAccount(payload) {
  if (typeof payload !== "object" || payload === null) return null;
  const email = normalizeEmail(payload.email);
  if (!email) return null;
  const now = isoNow();

  return {
    id: optionalString(payload.id, `revoked_account_${slugify(email)}`),
    email,
    name: nullableStringWithFallback(payload.name, null),
    sourceUserId: nullableStringWithFallback(payload.sourceUserId, null),
    authentikUserId: nullableStringWithFallback(payload.authentikUserId, null),
    reason: optionalString(payload.reason, "hard_deleted"),
    revokedByUserId: nullableStringWithFallback(payload.revokedByUserId, null),
    revokedByUserEmail: nullableStringWithFallback(payload.revokedByUserEmail, null),
    revokedByUserName: nullableStringWithFallback(payload.revokedByUserName, null),
    revokedAt: optionalString(payload.revokedAt, now),
    createdAt: optionalString(payload.createdAt, now),
    updatedAt: optionalString(payload.updatedAt, now),
  };
}

function normalizeAccessRequest(payload) {
  if (typeof payload !== "object" || payload === null) return null;
  const now = isoNow();
  const email = typeof payload.email === "string" ? payload.email.trim().toLowerCase() : "";
  const firstName = optionalString(payload.firstName, "");
  const lastName = optionalString(payload.lastName, "");
  const middleName = optionalString(payload.middleName, "");
  const phone = optionalString(payload.phone, "");
  const company = optionalString(payload.company, "");

  if (!email || !firstName || !lastName || !middleName || !phone || !company) return null;

  return {
    id: optionalString(payload.id, `access_request_${slugify(email)}`),
    email,
    firstName,
    lastName,
    middleName,
    phone,
    company,
    status: pickEnum(payload.status, accessRequestStatuses, "new"),
    targetClientId: typeof payload.targetClientId === "string" && payload.targetClientId.trim() ? payload.targetClientId.trim() : publicPoolClientId,
    role: pickEnum(payload.role, membershipRoles, "member"),
    approvedInviteId: nullableStringWithFallback(payload.approvedInviteId, null),
    reviewedByUserId: nullableStringWithFallback(payload.reviewedByUserId, null),
    reviewedAt: nullableStringWithFallback(payload.reviewedAt, null),
    comment: nullableStringWithFallback(payload.comment, null),
    createdAt: optionalString(payload.createdAt, now),
    updatedAt: optionalString(payload.updatedAt, now),
  };
}

function normalizeTaskerInviteRequest(payload) {
  if (typeof payload !== "object" || payload === null) return null;
  const now = isoNow();
  const taskerInviteId = typeof payload.taskerInviteId === "string" ? payload.taskerInviteId.trim() : "";
  const workspaceSlug = typeof payload.workspaceSlug === "string" ? payload.workspaceSlug.trim() : "";
  const inviteeEmail = typeof payload.inviteeEmail === "string" ? payload.inviteeEmail.trim().toLowerCase() : "";
  const inviterEmail = typeof payload.inviterEmail === "string" ? payload.inviterEmail.trim().toLowerCase() : "";

  if (!taskerInviteId || !workspaceSlug || !inviteeEmail || !inviterEmail) return null;

  return {
    id: optionalString(payload.id, `tasker_invite_request_${slugify(`${workspaceSlug}-${inviteeEmail}`)}`),
    taskerInviteId,
    workspaceId: nullableStringWithFallback(payload.workspaceId, null),
    workspaceSlug,
    workspaceName: optionalString(payload.workspaceName, workspaceSlug),
    inviteeEmail,
    role: normalizeTaskManagerInviteRole(payload.role),
    inviterUserId: nullableStringWithFallback(payload.inviterUserId, null),
    inviterPlaneUserId: nullableStringWithFallback(payload.inviterPlaneUserId, null),
    inviterEmail,
    inviterName: optionalString(payload.inviterName, inviterEmail),
    status: pickEnum(payload.status, taskerInviteRequestStatuses, "new"),
    taskerInviteLink: nullableStringWithFallback(payload.taskerInviteLink, null),
    platformInviteId: nullableStringWithFallback(payload.platformInviteId, null),
    platformInviteToken: nullableStringWithFallback(payload.platformInviteToken, null),
    reviewedByUserId: nullableStringWithFallback(payload.reviewedByUserId, null),
    reviewedAt: nullableStringWithFallback(payload.reviewedAt, null),
    comment: nullableStringWithFallback(payload.comment, null),
    createdAt: optionalString(payload.createdAt, now),
    updatedAt: optionalString(payload.updatedAt, now),
  };
}

function normalizeEngineWorkflowAccessRequest(payload) {
  if (typeof payload !== "object" || payload === null) return null;
  const now = isoNow();
  const requestType = pickEnum(payload.requestType ?? payload.type, engineWorkflowAccessRequestTypes, "workflow");
  const workflowId = typeof payload.workflowId === "string" ? payload.workflowId.trim() : "";
  const targetUserId = typeof payload.targetUserId === "string" ? payload.targetUserId.trim() : "";
  const targetEmail = normalizeEmail(payload.targetEmail ?? payload.email);
  const requesterEmail = normalizeEmail(payload.requesterEmail);

  if (!workflowId || !targetUserId || !targetEmail || !requesterEmail) return null;

  return {
    id: optionalString(payload.id, `engine_workflow_access_request_${slugify(`${workflowId}-${targetEmail}`)}`),
    requestType,
    workflowId,
    workflowName: optionalString(payload.workflowName, workflowId),
    targetUserId,
    targetEmail,
    targetName: nullableStringWithFallback(payload.targetName, null),
    role: normalizeEngineWorkflowRole(payload.role),
    requestedServiceRole: requestType === "service_role"
      ? normalizeEngineServiceRole(payload.requestedServiceRole ?? payload.requestedRole ?? payload.serviceRole)
      : nullableStringWithFallback(payload.requestedServiceRole, null),
    currentServiceRole: normalizeNullableEngineServiceRole(payload.currentServiceRole ?? payload.currentRole),
    requesterUserId: nullableStringWithFallback(payload.requesterUserId, null),
    requesterEmail,
    requesterName: optionalString(payload.requesterName, requesterEmail),
    status: pickEnum(payload.status, engineWorkflowAccessRequestStatuses, "new"),
    reviewedByUserId: nullableStringWithFallback(payload.reviewedByUserId, null),
    reviewedAt: nullableStringWithFallback(payload.reviewedAt, null),
    engineAppliedAt: nullableStringWithFallback(payload.engineAppliedAt, null),
    comment: nullableStringWithFallback(payload.comment, null),
    createdAt: optionalString(payload.createdAt, now),
    updatedAt: optionalString(payload.updatedAt, now),
  };
}

function normalizeSettings(payload) {
  const settings = typeof payload === "object" && payload !== null ? payload : {};
  const brand = typeof settings.brand === "object" && settings.brand !== null ? settings.brand : {};
  const taskManager = typeof settings.taskManager === "object" && settings.taskManager !== null ? settings.taskManager : {};

  return {
    brand: {
      logoLinkUrl: optionalString(brand.logoLinkUrl, defaultSettings.brand.logoLinkUrl),
    },
    taskManager: {
      workspaceCreationPolicy: pickEnum(
        taskManager.workspaceCreationPolicy,
        new Set(["any_authorized_user", "task_admins_only", "disabled"]),
        defaultSettings.taskManager.workspaceCreationPolicy
      ),
    },
  };
}

function normalizeClientIntegrations(payload, fallback = {}) {
  const integrations = typeof payload === "object" && payload !== null ? payload : {};
  const fallbackIntegrations = typeof fallback === "object" && fallback !== null ? fallback : {};
  const taskManager = typeof integrations.taskManager === "object" && integrations.taskManager !== null ? integrations.taskManager : {};
  const fallbackTaskManager =
    typeof fallbackIntegrations.taskManager === "object" && fallbackIntegrations.taskManager !== null ? fallbackIntegrations.taskManager : {};
  const workspaces = normalizeTaskManagerWorkspaces(taskManager, fallbackTaskManager);
  const primaryWorkspace = workspaces.find((workspace) => workspace.isPrimary) ?? workspaces[0] ?? null;

  return {
    taskManager: {
      workspaceSlug: primaryWorkspace?.slug ?? null,
      workspaceName: primaryWorkspace?.name ?? null,
      workspaces,
    },
  };
}

function normalizeTaskManagerWorkspaces(taskManager, fallbackTaskManager = {}) {
  const sourceWorkspaces = Array.isArray(taskManager.workspaces)
    ? taskManager.workspaces
    : Array.isArray(fallbackTaskManager.workspaces)
      ? fallbackTaskManager.workspaces
      : [];
  const legacySlug = nullableStringWithFallback(taskManager.workspaceSlug, fallbackTaskManager.workspaceSlug ?? null);
  const legacyName = nullableStringWithFallback(taskManager.workspaceName, fallbackTaskManager.workspaceName ?? null);
  const bySlug = new Map();

  for (const item of sourceWorkspaces) {
    if (typeof item !== "object" || item === null) continue;
    const slug = nullableString(item.slug);
    if (!slug) continue;
    bySlug.set(slug, {
      slug,
      name: nullableStringWithFallback(item.name, null),
      isPrimary: item.isPrimary === true,
      managedBy: normalizeTaskManagerWorkspaceManagedBy(item.managedBy),
    });
  }

  if (legacySlug && !bySlug.has(legacySlug)) {
    bySlug.set(legacySlug, { slug: legacySlug, name: legacyName, isPrimary: true, managedBy: "launcher" });
  }

  const workspaces = [...bySlug.values()];
  if (!workspaces.length) return [];

  if (!workspaces.some((workspace) => workspace.isPrimary)) {
    workspaces[0].isPrimary = true;
  }

  let primarySeen = false;
  return workspaces.map((workspace) => {
    const isPrimary = workspace.isPrimary && !primarySeen;
    if (isPrimary) primarySeen = true;
    return {
      slug: workspace.slug,
      name: workspace.name ?? null,
      isPrimary,
      managedBy: normalizeTaskManagerWorkspaceManagedBy(workspace.managedBy),
    };
  });
}

function normalizeTaskManagerWorkspaceManagedBy(value) {
  return taskManagerWorkspaceManagedByValues.has(value) ? value : "launcher";
}

function normalizeTaskManagerMembershipRole(value) {
  return value === "guest" || value === "admin" || value === "member" ? value : "member";
}

function upsertTaskManagerMembership(data, payload) {
  const workspaceSlug = requireString(payload.workspaceSlug, "workspaceSlug");
  const existingMembership = data.taskManagerMemberships.find(
    (membership) => membership.clientId === payload.clientId && membership.userId === payload.userId && membership.workspaceSlug === workspaceSlug
  );
  const nextMembership = {
    id: existingMembership?.id ?? uniqueId(data.taskManagerMemberships, "tasker_mem", `${payload.clientId}-${payload.userId}-${workspaceSlug}`),
    clientId: payload.clientId,
    userId: payload.userId,
    workspaceSlug,
    workspaceName: nullableStringWithFallback(payload.workspaceName, existingMembership?.workspaceName ?? null),
    role: normalizeTaskManagerMembershipRole(payload.role),
    managedBy: normalizeTaskManagerWorkspaceManagedBy(payload.managedBy ?? existingMembership?.managedBy),
    planeUserId: nullableStringWithFallback(payload.planeUserId, existingMembership?.planeUserId ?? null),
    planeRole: typeof payload.planeRole === "number" ? payload.planeRole : (existingMembership?.planeRole ?? null),
    updatedAt: isoNow(),
  };

  if (existingMembership) {
    Object.assign(existingMembership, nextMembership);
    return existingMembership;
  }

  data.taskManagerMemberships.push(nextMembership);
  return nextMembership;
}

function upsertTaskManagerProjectMembership(data, payload) {
  const workspaceSlug = requireString(payload.workspaceSlug, "workspaceSlug");
  const projectId = requireString(payload.projectId, "projectId");
  const existingMembership = data.taskManagerProjectMemberships.find(
    (membership) =>
      membership.clientId === payload.clientId &&
      membership.userId === payload.userId &&
      membership.workspaceSlug === workspaceSlug &&
      membership.projectId === projectId
  );
  const nextMembership = {
    id:
      existingMembership?.id ??
      uniqueId(data.taskManagerProjectMemberships, "tasker_project_mem", `${payload.clientId}-${payload.userId}-${workspaceSlug}-${projectId}`),
    clientId: payload.clientId,
    userId: payload.userId,
    workspaceSlug,
    workspaceName: nullableStringWithFallback(payload.workspaceName, existingMembership?.workspaceName ?? null),
    projectId,
    projectIdentifier: nullableStringWithFallback(payload.projectIdentifier, existingMembership?.projectIdentifier ?? null),
    projectName: nullableStringWithFallback(payload.projectName, existingMembership?.projectName ?? null),
    role: normalizeTaskManagerMembershipRole(payload.role),
    managedBy: normalizeTaskManagerWorkspaceManagedBy(payload.managedBy ?? existingMembership?.managedBy),
    planeUserId: nullableStringWithFallback(payload.planeUserId, existingMembership?.planeUserId ?? null),
    planeRole: typeof payload.planeRole === "number" ? payload.planeRole : (existingMembership?.planeRole ?? null),
    updatedAt: isoNow(),
  };

  if (existingMembership) {
    Object.assign(existingMembership, nextMembership);
    return existingMembership;
  }

  data.taskManagerProjectMemberships.push(nextMembership);
  return nextMembership;
}

function resolveActor(data, identity) {
  const user = data.users.find(
    (item) =>
      (identity?.sub && item.authentikUserId === identity.sub) ||
      (identity?.email && item.email.toLowerCase() === identity.email.toLowerCase())
  );

  if (user) {
    return {
      id: user.id,
      name: user.name,
      email: user.email,
      source: "launcher",
    };
  }

  return {
    id: identity?.sub ? `oidc:${identity.sub}` : "system",
    name: identity?.name || identity?.email || "System",
    email: identity?.email || null,
    source: "oidc",
  };
}

function addAuditEvent(data, actor, event) {
  data.auditEvents.push({
    id: uniqueId(data.auditEvents, "audit", event.objectName ?? event.objectType ?? "event"),
    at: isoNow(),
    actorUserId: actor.id,
    actorName: actor.name,
    action: event.action,
    objectType: event.objectType,
    objectName: event.objectName,
    clientId: event.clientId ?? null,
    result: event.result ?? "success",
    details: event.details ?? null,
  });
}

function upsertRevokedAccount(data, user, actor) {
  const email = normalizeEmail(user.email);
  if (!email) return null;
  const now = isoNow();
  const existingAccount = data.revokedAccounts.find((account) => normalizeEmail(account.email) === email);
  const patch = {
    email,
    name: user.name ?? null,
    sourceUserId: user.id,
    authentikUserId: user.authentikUserId ?? null,
    reason: "hard_deleted",
    revokedByUserId: actor.id ?? null,
    revokedByUserEmail: actor.email ?? null,
    revokedByUserName: actor.name ?? null,
    revokedAt: now,
    updatedAt: now,
  };

  if (existingAccount) {
    Object.assign(existingAccount, patch);
    return existingAccount;
  }

  const revokedAccount = {
    id: uniqueId(data.revokedAccounts, "revoked_account", email),
    ...patch,
    createdAt: now,
  };
  data.revokedAccounts.push(revokedAccount);
  return revokedAccount;
}

function clearRevokedAccount(data, email) {
  const normalizedEmail = normalizeEmail(email);
  if (!normalizedEmail) return;
  data.revokedAccounts = data.revokedAccounts.filter((account) => normalizeEmail(account.email) !== normalizedEmail);
}

function markPendingSync(data, object, objectType, objectName = object.name ?? object.email ?? object.id) {
  const now = isoNow();
  const existingStatus = data.syncStatuses.find(
    (status) => status.target === "authentik" && status.objectType === objectType && status.objectId === object.id
  );

  if (existingStatus) {
    existingStatus.objectName = objectName;
    existingStatus.state = "pending";
    existingStatus.error = null;
    existingStatus.updatedAt = now;
    return;
  }

  data.syncStatuses.push({
    id: uniqueId(data.syncStatuses, "sync", `${objectType}-${object.id}`),
    objectId: object.id,
    objectName,
    objectType,
    target: "authentik",
    state: "pending",
    lastSyncAt: null,
    error: null,
    updatedAt: now,
  });
}

async function writeJsonAtomically(filePath, payload) {
  const tempPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;

  await writeFile(tempPath, payload, "utf8");
  await rename(tempPath, filePath);
}

function assertGrantTargetExists(data, targetType, targetId) {
  if (targetType === "client") {
    findClientById(data, targetId);
  } else if (targetType === "group") {
    findById(data.groups, targetId, "group");
  } else {
    findById(data.users, targetId, "user");
  }
}

function findInviteByToken(data, token) {
  const normalizedToken = requireString(token, "token");
  const invite = data.invites.find((candidate) => candidate.token === normalizedToken);

  if (!invite) {
    throw new Error("Инвайт не найден");
  }

  return invite;
}

function requireInviteIdentityEmail(identity) {
  const email = typeof identity?.email === "string" ? identity.email.trim().toLowerCase() : "";

  if (!email) {
    throw new Error("Для принятия инвайта нужна подтверждённая почта");
  }

  return email;
}

function isInviteExpired(invite) {
  if (!invite.expiresAt) return false;
  return Number.isFinite(Date.parse(invite.expiresAt)) && Date.parse(invite.expiresAt) <= Date.now();
}

function toPublicInvite(invite) {
  return {
    id: invite.id,
    email: invite.email,
    role: invite.role,
    expiresAt: invite.expiresAt,
    status: isInviteExpired(invite) && invite.status !== "accepted" && invite.status !== "revoked" ? "expired" : invite.status,
    source: invite.source ?? "launcher",
    sourceWorkspaceName: invite.sourceWorkspaceName ?? null,
    sourceWorkspaceSlug: invite.sourceWorkspaceSlug ?? null,
  };
}

function resolvePublicInviteRedirectUrl(invite) {
  if (invite?.source === "tasker_workspace_invite" && invite.sourceTaskerInviteRequestId) {
    return `/tasker-workspace-invite/${encodeURIComponent(invite.sourceTaskerInviteRequestId)}`;
  }

  return "/";
}

function toPublicClient(client) {
  return {
    id: client.id,
    name: client.name,
    status: client.status,
  };
}

function ensureTaskerInviteServiceAccess(data, invite, user, now) {
  if (invite.source !== "tasker_workspace_invite") {
    return null;
  }

  const service = data.services.find((candidate) => candidate.slug === "task-manager");
  if (!service) {
    return null;
  }

  const taskerInviteRequest = invite.sourceTaskerInviteRequestId
    ? data.taskerInviteRequests.find((request) => request.id === invite.sourceTaskerInviteRequestId)
    : null;
  const requestedAppRole = taskerInviteRequest?.role === "guest" ? "viewer" : "member";
  const existingGrant = data.grants.find(
    (grant) => grant.serviceId === service.id && grant.targetType === "user" && grant.targetId === user.id
  );
  const existingException = data.exceptions.find((exception) => exception.serviceId === service.id && exception.userId === user.id);

  if (existingException?.type === "deny") {
    return null;
  }

  if (existingGrant) {
    existingGrant.status = "active";
    existingGrant.appRole =
      existingGrant.appRole === "admin" || existingGrant.appRole === "owner" ? existingGrant.appRole : requestedAppRole;
    existingGrant.updatedAt = now;
    markPendingSync(data, { id: `${service.id}:${user.id}` }, "grant", `${service.slug}:${user.email}`);
    return existingGrant;
  }

  const grant = {
    id: uniqueId(data.grants, "grant", `${service.slug}-user-${user.email}`),
    serviceId: service.id,
    targetType: "user",
    targetId: user.id,
    appRole: requestedAppRole,
    status: "active",
    createdAt: now,
    updatedAt: now,
  };
  data.grants.push(grant);
  markPendingSync(data, { id: `${service.id}:${user.id}` }, "grant", `${service.slug}:${user.email}`);
  return grant;
}

function ensureEngineWorkflowServiceAccess(data, user, workflowRole, now) {
  const service = findEngineService(data);
  if (!service) {
    throw new Error("engine_service_not_found");
  }

  const requestedAppRole = workflowRole === "viewer" ? "viewer" : "member";
  data.exceptions = data.exceptions.filter((exception) => !(exception.serviceId === service.id && exception.userId === user.id));
  const existingGrant = data.grants.find(
    (grant) => grant.serviceId === service.id && grant.targetType === "user" && grant.targetId === user.id
  );

  if (existingGrant) {
    existingGrant.status = "active";
    existingGrant.appRole =
      existingGrant.appRole === "admin" || existingGrant.appRole === "owner" ? existingGrant.appRole : requestedAppRole;
    existingGrant.updatedAt = now;
    markPendingSync(data, { id: `${service.id}:${user.id}` }, "grant", `${service.slug}:${user.email}`);
    return existingGrant;
  }

  const grant = {
    id: uniqueId(data.grants, "grant", `${service.slug}-user-${user.email}`),
    serviceId: service.id,
    targetType: "user",
    targetId: user.id,
    appRole: requestedAppRole,
    status: "active",
    createdAt: now,
    updatedAt: now,
  };
  data.grants.push(grant);
  markPendingSync(data, { id: `${service.id}:${user.id}` }, "grant", `${service.slug}:${user.email}`);
  return grant;
}

function resolveDirectEngineServiceRole(data, serviceId, userId) {
  const denied = data.exceptions.some(
    (exception) => exception.serviceId === serviceId && exception.userId === userId && exception.type === "deny"
  );
  if (denied) return null;

  const grant = data.grants.find(
    (candidate) => candidate.serviceId === serviceId && candidate.targetType === "user" && candidate.targetId === userId
  );
  if (!grant || grant.status !== "active") return null;
  if (grant.appRole === "viewer") return "viewer";
  if (grant.appRole === "member") return "member";
  return null;
}

function appendEngineRoleChangeNotification(data, { actor, currentServiceRole, serviceRole, targetUser, now }) {
  const requestedServiceRole = normalizeEngineServiceRole(serviceRole);
  const request = {
    id: uniqueId(data.engineWorkflowAccessRequests, "engine_role_change", `${targetUser.email}-${now}`),
    requestType: "service_role",
    workflowId: "engine:service-role",
    workflowName: "NODE.DC Engine",
    targetUserId: targetUser.id,
    targetEmail: normalizeEmail(targetUser.email),
    targetName: targetUser.name ?? null,
    role: requestedServiceRole === "viewer" ? "viewer" : "editor",
    requestedServiceRole,
    currentServiceRole: normalizeNullableEngineServiceRole(currentServiceRole),
    requesterUserId: actor.id ?? null,
    requesterEmail: normalizeEmail(actor.email) || "admin@nodedc.ru",
    requesterName: actor.name || actor.email || "Администратор",
    status: "approved",
    reviewedByUserId: actor.id ?? null,
    reviewedAt: now,
    engineAppliedAt: now,
    comment: "admin_role_changed",
    createdAt: now,
    updatedAt: now,
  };
  data.engineWorkflowAccessRequests.push(request);
  return request;
}

function ensureEngineWorkflowPublicPoolMembership(data, user, request, actor, now) {
  const invitedByUserId = request.requesterUserId || actor?.id || null;
  let membership = data.memberships.find(
    (candidate) => candidate.clientId === publicPoolClientId && candidate.userId === user.id
  );

  if (membership) {
    const wasActive = membership.status === "active";
    membership.status = "active";
    if (!wasActive) {
      membership.role = "member";
      membership.coreAssistantRole = normalizeCoreAssistantRole(membership.coreAssistantRole);
      membership.source = "engine_workflow_access_request";
      membership.sourceEngineWorkflowAccessRequestId = request.id;
    } else {
      membership.sourceEngineWorkflowAccessRequestId =
        membership.sourceEngineWorkflowAccessRequestId ?? request.id;
    }
    membership.invitedByUserId = membership.invitedByUserId ?? invitedByUserId;
    membership.updatedAt = now;
    markPendingSync(data, user, "user", user.email);
    return membership;
  }

  membership = {
    id: uniqueId(data.memberships, "mem", `${publicPoolClientId}-${user.email}`),
    clientId: publicPoolClientId,
    userId: user.id,
    role: "member",
    status: "active",
    coreAssistantRole: "member",
    invitedByUserId,
    inviteId: null,
    source: "engine_workflow_access_request",
    sourceTaskerInviteRequestId: null,
    sourceEngineWorkflowAccessRequestId: request.id,
    createdAt: now,
    updatedAt: now,
  };
  data.memberships.push(membership);
  markPendingSync(data, user, "user", user.email);
  return membership;
}

function ensurePublicPoolMembershipActiveForServiceAccess(data, user, actor, now) {
  const membership = data.memberships.find(
    (candidate) => candidate.clientId === publicPoolClientId && candidate.userId === user.id
  );
  if (!membership || membership.status === "active") {
    return null;
  }

  membership.role = "member";
  membership.status = "active";
  membership.coreAssistantRole = normalizeCoreAssistantRole(membership.coreAssistantRole);
  membership.invitedByUserId = membership.invitedByUserId ?? actor?.id ?? null;
  membership.source = membership.source ?? "launcher";
  membership.updatedAt = now;
  markPendingSync(data, user, "user", user.email);
  return membership;
}

function findEngineService(data) {
  return data.services.find(isEngineService);
}

function isProtectedLauncherUserId(userId) {
  return protectedLauncherUserIds.has(userId);
}

function isEngineService(candidate) {
  return Boolean(
    candidate &&
      (
        candidate.id === "service_nodedc" ||
        candidate.slug === "nodedc" ||
        candidate.slug === "engine" ||
        candidate.authentikApplicationSlug === "nodedc-engine"
      )
  );
}

function isOperationalCoreService(candidate) {
  return Boolean(candidate && (candidate.slug === "task-manager" || candidate.authentikApplicationSlug === "task-manager"));
}

function hasTaskManagerDenyException(data, userId) {
  const service = data.services.find((candidate) => candidate.slug === "task-manager");
  if (!service) {
    return false;
  }

  return data.exceptions.some(
    (exception) => exception.serviceId === service.id && exception.userId === userId && exception.type === "deny"
  );
}

function findTaskerInviteRequestForCancellation(data, payload) {
  const requestId = nullableString(payload?.requestId);
  const taskerInviteId = nullableString(payload?.taskerInviteId);
  const workspaceSlug = nullableString(payload?.workspaceSlug);
  const inviteeEmail = nullableString(payload?.inviteeEmail)?.toLowerCase() ?? null;

  if (requestId || taskerInviteId) {
    const request = data.taskerInviteRequests.find(
      (candidate) => (requestId && candidate.id === requestId) || (taskerInviteId && candidate.taskerInviteId === taskerInviteId)
    );
    if (request) return request;
  }

  if (!workspaceSlug || !inviteeEmail) {
    return null;
  }

  return data.taskerInviteRequests
    .filter(
      (candidate) =>
        candidate.workspaceSlug === workspaceSlug &&
        candidate.inviteeEmail.toLowerCase() === inviteeEmail
    )
    .sort((left, right) => {
      if (left.status !== "cancelled" && right.status === "cancelled") return -1;
      if (left.status === "cancelled" && right.status !== "cancelled") return 1;
      return Date.parse(right.updatedAt ?? right.createdAt ?? 0) - Date.parse(left.updatedAt ?? left.createdAt ?? 0);
    })[0] ?? null;
}

function revokeTaskerInviteServiceAccessIfOrphaned(data, request, now) {
  const user = data.users.find((candidate) => candidate.email.toLowerCase() === request.inviteeEmail.toLowerCase());
  const service = data.services.find((candidate) => candidate.slug === "task-manager");

  if (!user || !service) {
    return [];
  }

  const hasAnotherAcceptedWorkspaceInvite = data.taskerInviteRequests.some((candidate) => {
    if (candidate.id === request.id) return false;
    if (candidate.status !== "approved") return false;
    if (candidate.inviteeEmail.toLowerCase() !== request.inviteeEmail.toLowerCase()) return false;

    const platformInvite = candidate.platformInviteId
      ? data.invites.find((invite) => invite.id === candidate.platformInviteId)
      : null;
    return platformInvite?.status === "accepted";
  });

  if (hasAnotherAcceptedWorkspaceInvite) {
    return [];
  }

  const directGrant = data.grants.find(
    (grant) => grant.serviceId === service.id && grant.targetType === "user" && grant.targetId === user.id && grant.status === "active"
  );

  if (!directGrant || directGrant.appRole === "admin" || directGrant.appRole === "owner") {
    return [];
  }

  data.grants = data.grants.filter((grant) => grant.id !== directGrant.id);
  markPendingSync(data, { id: `${service.id}:${user.id}` }, "grant", `${service.slug}:${user.email}`);
  markPendingSync(data, user, "user", user.email);

  addAuditEvent(data, { id: user.id, name: user.name, email: user.email, source: "tasker" }, {
    action: "Снят доступ Operational Core по workspace-инвайту",
    objectType: "grant",
    objectName: `${user.email} / ${service.slug}`,
    result: "warning",
    details: `Workspace invite: ${request.workspaceSlug}; cancelled at: ${now}`,
  });

  return [user.id];
}

function applyInviteRegistration(data, token, payload, { commit, provisioning = null } = {}) {
  const invite = findInviteByToken(data, token);
  const client = findClientById(data, invite.clientId);
  const now = isoNow();
  const requestedEmail = normalizeInviteRegistrationEmail(payload?.email);
  const email = invite.email.toLowerCase();
  const name = optionalString(payload?.name, requestedEmail.split("@")[0]);

  validateInviteCanBeRegistered(invite);
  validateTaskerInviteSourceCanBeAccepted(data, invite);

  if (!requestedEmail || requestedEmail !== email) {
    throw new Error("Для этой почты нет активного инвайта");
  }

  let user = data.users.find((item) => item.email.toLowerCase() === email);

  if (user?.globalStatus === "blocked") {
    throw new Error("Аккаунт заблокирован. Обратитесь к администратору NODE.DC.");
  }

  if (user?.authentikUserId && !provisioning) {
    throw new Error("Аккаунт уже существует. Войдите под почтой инвайта.");
  }

  if (user) {
    user.name = name;
    user.globalStatus = "active";
    user.authentikUserId = provisioning?.authentikUserId ?? user.authentikUserId ?? null;
    user.updatedAt = now;
  } else {
    user = {
      id: uniqueId(data.users, "user", email),
      authentikUserId: provisioning?.authentikUserId ?? null,
      name,
      email,
      phone: null,
      position: null,
      notes: `Создан через публичную регистрацию по инвайту клиента ${client.name}.`,
      avatarUrl: null,
      globalStatus: "active",
      createdAt: now,
      updatedAt: now,
    };
    data.users.push(user);
  }

  let membership = data.memberships.find((item) => item.clientId === invite.clientId && item.userId === user.id);

  if (membership) {
    membership.role = invite.role;
    membership.status = "active";
    membership.coreAssistantRole = normalizeCoreAssistantRole(membership.coreAssistantRole);
    membership.invitedByUserId = invite.invitedByUserId ?? membership.invitedByUserId ?? null;
    membership.inviteId = invite.id;
    membership.source = invite.source ?? membership.source ?? "launcher";
    membership.sourceTaskerInviteRequestId = invite.sourceTaskerInviteRequestId ?? membership.sourceTaskerInviteRequestId ?? null;
    membership.updatedAt = now;
  } else {
    membership = {
      id: uniqueId(data.memberships, "mem", `${invite.clientId}-${email}`),
      clientId: invite.clientId,
      userId: user.id,
      role: invite.role,
      status: "active",
      coreAssistantRole: "member",
      invitedByUserId: invite.invitedByUserId ?? null,
      inviteId: invite.id,
      source: invite.source ?? "launcher",
      sourceTaskerInviteRequestId: invite.sourceTaskerInviteRequestId ?? null,
      createdAt: now,
      updatedAt: now,
    };
    data.memberships.push(membership);
  }

  ensureTaskerInviteServiceAccess(data, invite, user, now);
  clearRevokedAccount(data, email);
  invite.status = "accepted";
  invite.updatedAt = now;
  markPendingSync(data, user, "user", email);

  if (commit) {
    addAuditEvent(data, { id: user.id, name: user.name, email: user.email, source: "invite" }, {
      action: "Регистрация по инвайту",
      objectType: "invite",
      objectName: invite.email,
      clientId: client.id,
      result: "success",
      details: `Role: ${invite.role}`,
    });
  }

  return { invite, client, user, membership, data };
}

function normalizeInviteRegistrationEmail(value) {
  return typeof value === "string" ? value.trim().toLowerCase() : "";
}

function validateInviteCanBeRegistered(invite) {
  if (invite.status === "accepted") {
    throw new Error("Инвайт уже принят");
  }

  if (invite.status === "revoked") {
    throw new Error("Инвайт отозван");
  }

  if (invite.status === "expired" || isInviteExpired(invite)) {
    throw new Error("Срок действия инвайта истёк");
  }
}

function validateTaskerInviteSourceCanBeAccepted(data, invite) {
  if (invite.source !== "tasker_workspace_invite") {
    return;
  }

  const request = invite.sourceTaskerInviteRequestId
    ? data.taskerInviteRequests.find((candidate) => candidate.id === invite.sourceTaskerInviteRequestId)
    : null;

  if (!request || request.status !== "approved") {
    throw new Error("Workspace-инвайт больше не активен");
  }
}

function findById(items, id, label) {
  const item = items.find((candidate) => candidate.id === id);

  if (!item) {
    throw new Error(`Unknown ${label}: ${id}`);
  }

  return item;
}

function findClientById(data, clientId) {
  if (clientId === publicPoolClientId) {
    return publicPoolClient;
  }

  return findById(data.clients, clientId, "client");
}

function findAccessRequestById(data, accessRequestId) {
  return findById(data.accessRequests, accessRequestId, "access_request");
}

function findTaskerInviteRequestById(data, taskerInviteRequestId) {
  return findById(data.taskerInviteRequests, taskerInviteRequestId, "tasker_invite_request");
}

function findEngineWorkflowAccessRequestById(data, engineWorkflowAccessRequestId) {
  return findById(data.engineWorkflowAccessRequests, engineWorkflowAccessRequestId, "engine_workflow_access_request");
}

function resolveAccessRequestTargetClientId(data, value, fallback = publicPoolClientId) {
  const clientId = optionalString(value, fallback || publicPoolClientId);
  findClientById(data, clientId);
  return clientId;
}

function sanitizeAccessRequestPayload(payload) {
  const email = requireString(payload?.email, "email").toLowerCase();

  if (!isValidEmail(email)) {
    throw new Error("Введите корректную электронную почту");
  }

  return {
    email,
    firstName: requireString(payload?.firstName, "firstName").slice(0, 80),
    lastName: requireString(payload?.lastName, "lastName").slice(0, 80),
    middleName: requireString(payload?.middleName, "middleName").slice(0, 80),
    phone: requireString(payload?.phone, "phone").slice(0, 80),
    company: requireString(payload?.company, "company").slice(0, 160),
  };
}

function upsertAccessRequestUser(data, requestPayload, now) {
  const email = requestPayload.email.toLowerCase();
  const existingUser = data.users.find((candidate) => candidate.email.toLowerCase() === email);
  const userName = buildAccessRequestUserName(requestPayload);

  if (existingUser) {
    existingUser.name = userName;
    existingUser.phone = requestPayload.phone;
    existingUser.notes = `Public access request: ${requestPayload.company}`;
    existingUser.globalStatus = existingUser.globalStatus === "blocked" ? "blocked" : "active";
    existingUser.updatedAt = now;
    return existingUser;
  }

  const user = {
    id: uniqueId(data.users, "user", email),
    authentikUserId: null,
    email,
    name: userName,
    phone: requestPayload.phone,
    position: null,
    notes: `Public access request: ${requestPayload.company}`,
    avatarUrl: null,
    globalStatus: "active",
    createdAt: now,
    updatedAt: now,
  };

  data.users.push(user);
  return user;
}

function upsertAccessRequestMembership(data, user, requestPayload, options = {}) {
  const now = options.now ?? isoNow();
  const clientId = options.clientId ?? publicPoolClientId;
  const role = pickEnum(requestPayload.role, membershipRoles, "member");
  const existingMembership = data.memberships.find(
    (membership) => membership.userId === user.id && membership.clientId === clientId
  );

  if (existingMembership) {
    existingMembership.role = role;
    existingMembership.status = options.status ?? existingMembership.status;
    existingMembership.coreAssistantRole = normalizeCoreAssistantRole(existingMembership.coreAssistantRole);
    existingMembership.invitedByUserId = options.invitedByUserId ?? existingMembership.invitedByUserId ?? null;
    existingMembership.source = existingMembership.source ?? "access_request";
    existingMembership.updatedAt = now;
    return existingMembership;
  }

  const membership = {
    id: uniqueId(data.memberships, "mem", `${clientId}-${user.id}`),
    clientId,
    userId: user.id,
    role,
    status: options.status ?? "disabled",
    coreAssistantRole: "member",
    invitedByUserId: options.invitedByUserId ?? null,
    inviteId: null,
    source: "access_request",
    sourceTaskerInviteRequestId: null,
    createdAt: now,
    updatedAt: now,
  };

  data.memberships.push(membership);
  return membership;
}

function buildAccessRequestUserName(requestPayload) {
  return [requestPayload.lastName, requestPayload.firstName, requestPayload.middleName].filter(Boolean).join(" ");
}

function normalizeTaskManagerInviteRole(value) {
  const normalized = typeof value === "string" ? value.trim().toLowerCase() : value;

  if (normalized === "viewer") return "guest";
  if (normalized === "owner") return "admin";
  if (normalized === 5) return "guest";
  if (normalized === 15) return "member";
  if (normalized === 20) return "admin";
  return taskManagerInviteRoles.has(normalized) ? normalized : "member";
}

function normalizeEngineWorkflowRole(value) {
  const normalized = typeof value === "string" ? value.trim().toLowerCase() : "";
  return engineWorkflowRoles.has(normalized) ? normalized : "viewer";
}

function normalizeEngineServiceRole(value) {
  const normalized = typeof value === "string" ? value.trim().toLowerCase() : "";
  if (normalized === "guest" || normalized === "viewer" || normalized === "read_only" || normalized === "readonly") return "viewer";
  if (normalized === "participant" || normalized === "editor" || normalized === "member") return "member";
  return engineServiceRoles.has(normalized) ? normalized : "member";
}

function normalizeNullableEngineServiceRole(value) {
  const normalized = typeof value === "string" ? value.trim().toLowerCase() : "";
  if (!normalized || normalized === "null" || normalized === "undefined") return null;
  return normalizeEngineServiceRole(normalized);
}

function isValidEmail(email) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

function requireString(value, fieldName) {
  if (typeof value !== "string" || !value.trim()) {
    throw new Error(`Field ${fieldName} is required`);
  }

  return value.trim();
}

function optionalString(value, fallback) {
  return typeof value === "string" && value.trim() ? value.trim() : fallback;
}

function nullableString(value) {
  return typeof value === "string" && value.trim() ? value.trim() : null;
}

function nullableStringWithFallback(value, fallback) {
  return value === undefined ? fallback : nullableString(value);
}

function normalizeEmail(value) {
  return typeof value === "string" ? value.trim().toLowerCase() : "";
}

function pickEnum(value, allowedValues, fallback) {
  return typeof value === "string" && allowedValues.has(value) ? value : fallback;
}

function uniqueId(items, prefix, seed) {
  const base = `${prefix}_${slugify(seed)}`;
  let candidate = base;
  let counter = 1;
  const ids = new Set(items.map((item) => item.id));

  while (ids.has(candidate)) {
    counter += 1;
    candidate = `${base}_${counter}`;
  }

  return candidate;
}

function sanitizeServicePatch(payload, service) {
  const patch = {};
  const stringFields = [
    "slug",
    "title",
    "subtitle",
    "description",
    "fullDescription",
    "url",
    "launchUrl",
    "logoutUrl",
    "iconUrl",
    "coverImageUrl",
    "coverMediaKind",
    "coverMediaSource",
    "coverMediaFileName",
    "previewVideoUrl",
    "ambientVideoUrl",
    "ambientMediaKind",
    "ambientMediaSource",
    "ambientMediaFileName",
    "accentColor",
    "fallbackGradient",
    "authentikApplicationSlug",
    "authentikGroupName",
  ];

  for (const field of stringFields) {
    if (field in (payload ?? {})) {
      patch[field] = nullableStringWithFallback(payload[field], service[field] ?? null);
    }
  }

  if (typeof payload?.order === "number") {
    patch.order = payload.order;
  }

  if ("isAvailableForAllNewClients" in (payload ?? {})) {
    patch.isAvailableForAllNewClients = Boolean(payload.isAvailableForAllNewClients);
  }

  patch.status = pickEnum(payload?.status, serviceStatuses, service.status);
  return patch;
}

function syncServiceLaunchLink(service) {
  const launchLink = String(service.launchUrl || service.url || "").trim();

  return {
    ...service,
    url: launchLink,
    launchUrl: launchLink || null,
  };
}

function slugify(value) {
  const slug =
    String(value)
      .normalize("NFKD")
      .replace(/[^\w]+/g, "_")
      .replace(/^_+|_+$/g, "")
      .toLowerCase()
      .slice(0, 80) || randomUUID().slice(0, 8);

  return slug;
}

function isoNow() {
  return new Date().toISOString();
}
