import express from "express";
import { createServer as createHttpServer } from "node:http";
import { randomBytes, randomUUID, createHash, timingSafeEqual } from "node:crypto";
import { existsSync, readFileSync } from "node:fs";
import { mkdir, writeFile } from "node:fs/promises";
import { dirname, extname, isAbsolute, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { createRemoteJWKSet, jwtVerify } from "jose";
import { createAuthentikSyncClient, resolveRequiredGroups } from "./authentik-sync.mjs";
import { createControlPlaneStore } from "./control-plane-store.mjs";
import { createNotificationCoreClient } from "./notification-core-client.mjs";

const serverRoot = dirname(fileURLToPath(import.meta.url));
const projectRoot = resolve(serverRoot, "..");
const maxStorageJsonBodyBytes = "260mb";
const pendingLoginTtlMs = 10 * 60 * 1000;
const serviceHandoffTtlMs = 60 * 1000;
const sessionTtlMs = 12 * 60 * 60 * 1000;
const oidcStateCookieName = "nodedc_oidc_state";
const maxOidcStateCookieEntries = 8;
const sessionCookieName = "nodedc_session";
const noStoreCacheControl = "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
const publicPoolClientId = "client_public_pool";

loadEnvFiles([
  process.env.NODEDC_PLATFORM_ENV,
  resolve(projectRoot, ".env"),
  resolve(projectRoot, "..", "..", "NODEDC", "platform", "infra", ".env"),
]);

const config = readConfig();
const app = express();
const httpServer = createHttpServer(app);
const controlPlaneStore = createControlPlaneStore({ projectRoot });
const runtimeStorageRoot = resolveRuntimeStorageRoot(projectRoot);
const runtimeUploadsRoot = resolveRuntimeUploadsRoot(projectRoot, runtimeStorageRoot);
const authentikSyncClient = createAuthentikSyncClient({ baseUrl: config.authentikBaseUrl, token: config.authentikApiToken });
const notificationCoreClient = createNotificationCoreClient({ baseUrl: config.notificationCoreBaseUrl, token: config.internalAccessToken });
const pendingLogins = new Map();
const serviceHandoffs = new Map();
const sessions = new Map();
const adminMutationIdempotencyRecords = new Map();
const runtimeEventClients = new Set();
let discoveryCache = null;
let jwksCache = null;
const adminMutationIdempotencyTtlMs = 10 * 60 * 1000;
const assistantGatewayName = "ontology-core";
const assistantGatewayAdminRouteAllowlist = [
  { method: "GET", pattern: /^\/api\/admin\/control-plane$/ },
  { method: "PATCH", pattern: /^\/api\/admin\/users\/[^/?#]+\/profile$/ },
  { method: "PATCH", pattern: /^\/api\/admin\/memberships\/[^/?#]+$/ },
];

app.disable("x-powered-by");
app.use((req, res, next) => {
  if (shouldDisableHttpCache(req)) {
    lockNoStoreHeaders(res);
  }

  next();
});
app.use(express.json({ limit: maxStorageJsonBodyBytes }));

app.get("/healthz", (_req, res) => {
  res.json({
    ok: true,
    service: "nodedc-launcher-bff",
    oidcConfigured: config.oidcConfigured,
    authentikApiConfigured: authentikSyncClient.isConfigured(),
    internalAccessApiConfigured: Boolean(config.internalAccessToken),
    notificationCoreConfigured: notificationCoreClient.isConfigured(),
  });
});

app.get("/api/public/brand", (_req, res) => {
  const snapshot = controlPlaneStore.getSnapshot({ name: "NODE.DC public brand" });

  res.setHeader("Access-Control-Allow-Origin", "*");
  res.setHeader("Cache-Control", noStoreCacheControl);
  res.json(buildPublicBrandResponse(snapshot.data.settings));
});

app.get("/api/public/login-account-status", (req, res) => {
  const email = typeof req.query.email === "string" ? req.query.email : "";

  res.setHeader("Access-Control-Allow-Origin", "*");
  setNoStore(res);
  res.json(controlPlaneStore.getLoginAccountStatus(email));
});

app.post("/api/access-requests", asyncRoute(async (req, res) => {
  try {
    const password = sanitizeNewPassword(req.body?.password);

    if (!authentikSyncClient.isConfigured()) {
      res.status(503).json({ error: "Authentik API не настроен. Заявку с паролем сейчас создать нельзя." });
      return;
    }

    const result = await controlPlaneStore.createAccessRequest(req.body);
    const provisioning = await authentikSyncClient.provisionUser({
      data: result.data,
      userId: result.user.id,
      password,
    });
    await controlPlaneStore.markUserAuthentikProvisioned(result.user.id, provisioning, {
      sub: "public-access-request",
      name: "NODE.DC public request",
      email: result.user.email,
    });

    publishControlPlaneEvent("access-request.created", [result.user.id]);
    publishNotificationEvent({
      type: "nodedc.access_request.created",
      sourceService: "launcher",
      actor: { userId: result.user.id, email: result.user.email },
      subject: { type: "access_request", id: result.accessRequest.id },
      payload: {
        accessRequestId: result.accessRequest.id,
        userId: result.user.id,
        email: result.user.email,
        status: result.accessRequest.status,
      },
      idempotencyKey: `launcher:access-request:${result.accessRequest.id}:created:${result.accessRequest.updatedAt}`,
      deliveries: [
        rootAdminNotificationDelivery(result.data, {
          ...buildAccessRequestHubNotification(result.accessRequest),
          actionable: true,
          actionType: "nodedc.access_request.review",
          entityType: "access_request",
          entityId: result.accessRequest.id,
        }),
      ].filter(Boolean),
    });
    res.status(201).json({ accessRequest: result.accessRequest });
  } catch (error) {
    sendAccessRequestApiError(res, error);
  }
}));

app.get("/auth/login", asyncRoute(async (req, res) => {
  ensureOidcConfigured();

  const discovery = await getOidcDiscovery();
  const returnTo = sanitizeReturnTo(req.query.returnTo);
  const prompt = sanitizePrompt(req.query.prompt);
  const cookieStates = getValidOidcCookieStates(req);
  const reusableState = cookieStates.find((cookieState) => {
    const pendingLogin = pendingLogins.get(cookieState);
    return pendingLogin?.returnTo === returnTo && pendingLogin?.prompt === prompt;
  });

  if (reusableState) {
    const pendingLogin = pendingLogins.get(reusableState);

    if (pendingLogin) {
      res.redirect(buildOidcAuthorizationUrl(discovery, { state: reusableState, prompt, pendingLogin }).toString());
      return;
    }
  }

  const state = randomBase64Url(32);
  const nonce = randomBase64Url(32);
  const codeVerifier = randomBase64Url(64);

  pruneExpiredState();
  pendingLogins.set(state, {
    codeVerifier,
    nonce,
    returnTo,
    prompt,
    expiresAt: Date.now() + pendingLoginTtlMs,
  });

  setOidcStateCookie(res, [state, ...cookieStates].slice(0, maxOidcStateCookieEntries));

  const pendingLogin = pendingLogins.get(state);
  const authorizationUrl = buildOidcAuthorizationUrl(discovery, { state, prompt, pendingLogin });
  res.redirect(authorizationUrl.toString());
}));

app.get("/auth/callback", asyncRoute(async (req, res) => {
  ensureOidcConfigured();

  const error = typeof req.query.error === "string" ? req.query.error : null;
  if (error) {
    throw new Error(`OIDC provider returned error: ${error}`);
  }

  const code = typeof req.query.code === "string" ? req.query.code : null;
  const state = typeof req.query.state === "string" ? req.query.state : null;
  const cookieStates = getValidOidcCookieStates(req);

  if (!code || !state || !cookieStates.includes(state)) {
    res.clearCookie(oidcStateCookieName, clearCookieOptions());
    res.redirect("/auth/login?returnTo=/");
    return;
  }

  const pendingLogin = pendingLogins.get(state);
  pendingLogins.delete(state);
  setOidcStateCookie(res, cookieStates.filter((cookieState) => cookieState !== state));

  if (!pendingLogin || pendingLogin.expiresAt < Date.now()) {
    throw new Error("OIDC login state expired");
  }

  const discovery = await getOidcDiscovery();
  const tokenSet = await exchangeCodeForTokens(discovery, code, pendingLogin.codeVerifier);
  const claims = await verifyIdToken(discovery, tokenSet.id_token, pendingLogin.nonce);
  createLauncherSession(res, normalizeUser(claims), {
    idToken: tokenSet.id_token,
    accessToken: tokenSet.access_token ?? null,
    expiresAt: tokenSet.expires_in ? Date.now() + Number(tokenSet.expires_in) * 1000 : null,
  });
  res.redirect(pendingLogin.returnTo);
}));

app.get("/auth/logged-out", (req, res) => {
  const returnTo = sanitizeReturnTo(req.query.returnTo);

  res.clearCookie(sessionCookieName, clearCookieOptions());
  res.clearCookie(oidcStateCookieName, clearCookieOptions());
  setNoStore(res);
  res.redirect(buildLoginRedirectUrl(returnTo, { includeReturnTo: returnTo !== "/" }));
});

app.get("/auth/session-sync", (req, res) => {
  const allowedOrigins = getSessionSyncAllowedOrigins();
  setNoStore(res);
  res.setHeader(
    "Content-Security-Policy",
    `default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; frame-ancestors ${allowedOrigins.join(" ")}`
  );
  res.type("html").send(renderSessionSyncBridgePage(allowedOrigins));
});

app.get("/logout", (req, res) => {
  const session = getCurrentSession(req);

  if (session) {
    sessions.delete(session.id);
  }

  res.clearCookie(sessionCookieName, clearCookieOptions());
  setNoStore(res);
  res.type("html").send(
    "<!doctype html><html><head><meta charset='utf-8'></head><body>NODE.DC Launcher session closed.</body></html>"
  );
});

app.get("/auth/logout", asyncRoute(async (req, res) => {
  const session = getCurrentSession(req);
  const returnTo = sanitizeReturnTo(req.query.returnTo);
  const globalLogout = req.query.global === "1" || req.query.global === "true";
  const taskSessionLogoutPromise = globalLogout ? notifyTaskSessionLogout(session) : Promise.resolve();

  if (session) {
    sessions.delete(session.id);
  }

  res.clearCookie(sessionCookieName, clearCookieOptions());

  if (!globalLogout || !config.oidcConfigured) {
    setNoStore(res);
    res.redirect(returnTo);
    return;
  }

  const discovery = await getOidcDiscovery();
  const logoutUrl = buildOidcLogoutUrl(discovery, returnTo, session?.tokenSet.idToken);
  await taskSessionLogoutPromise;

  setNoStore(res);
  res.type("html").send(renderGlobalLogoutPage(getFrontchannelLogoutUrls(), logoutUrl.toString()));
}));

app.get("/api/me", (req, res) => {
  const session = getCurrentSession(req);

  if (!session) {
    res.status(401).json({ authenticated: false, loginUrl: "/auth/login" });
    return;
  }

  const sessionAccess = getSessionAccessState(session);

  if (!sessionAccess.ok) {
    rejectInactiveSession(res, session, sessionAccess);
    return;
  }

  const runtimeContext = sessionAccess.runtimeContext;

  res.json({
    authenticated: true,
    user: runtimeContext.user,
    groups: runtimeContext.groups,
    isSuperAdmin: runtimeContext.groups.includes("nodedc:superadmin"),
    logoutUrl: "/auth/logout?global=1&returnTo=/",
  });
});

app.get("/api/notifications", requireSession, asyncRoute(async (req, res) => {
  if (!notificationCoreClient.isConfigured()) {
    res.status(503).json({ ok: false, error: "notification_core_not_configured" });
    return;
  }

  const { actor, data } = getLauncherProfileContext(req.nodedcSession);
  const user = findLauncherUser(data, actor.id);
  const payload = await notificationCoreClient.listNotifications({
    subject: notificationSubjectFromUser(user),
    surface: sanitizeNotificationSurface(req.query.surface, "hub"),
    limit: req.query.limit,
    unreadOnly: req.query.unreadOnly === "1" || req.query.unreadOnly === "true",
  });
  res.json(payload);
}));

app.post("/api/notifications/:deliveryId/read", requireSession, asyncRoute(async (req, res) => {
  if (!notificationCoreClient.isConfigured()) {
    res.status(503).json({ ok: false, error: "notification_core_not_configured" });
    return;
  }

  const { actor, data } = getLauncherProfileContext(req.nodedcSession);
  const user = findLauncherUser(data, actor.id);
  const payload = await notificationCoreClient.markNotificationRead({
    subject: notificationSubjectFromUser(user),
    deliveryId: req.params.deliveryId,
  });
  res.json(payload);
}));

app.post("/api/notifications/read-all", requireSession, asyncRoute(async (req, res) => {
  if (!notificationCoreClient.isConfigured()) {
    res.status(503).json({ ok: false, error: "notification_core_not_configured" });
    return;
  }

  const { actor, data } = getLauncherProfileContext(req.nodedcSession);
  const user = findLauncherUser(data, actor.id);
  const payload = await notificationCoreClient.markAllNotificationsRead({
    subject: notificationSubjectFromUser(user),
    surface: sanitizeNotificationSurface(req.query.surface ?? req.body?.surface, "hub"),
  });
  res.json(payload);
}));

app.get("/api/notifications/stream", requireSession, asyncRoute(async (req, res) => {
  if (!notificationCoreClient.isConfigured()) {
    res.status(503).json({ ok: false, error: "notification_core_not_configured" });
    return;
  }

  const { actor, data } = getLauncherProfileContext(req.nodedcSession);
  const user = findLauncherUser(data, actor.id);
  const abortController = new AbortController();

  req.on("close", () => {
    abortController.abort();
  });

  const upstream = await notificationCoreClient.openNotificationStream({
    subject: notificationSubjectFromUser(user),
    surface: sanitizeNotificationSurface(req.query.surface, "hub"),
    signal: abortController.signal,
  });

  res.setHeader("Content-Type", "text/event-stream");
  res.setHeader("Cache-Control", "no-cache, no-transform");
  res.setHeader("Connection", "keep-alive");
  res.setHeader("X-Accel-Buffering", "no");
  res.flushHeaders?.();

  const reader = upstream.body.getReader();
  try {
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      res.write(Buffer.from(value));
    }
  } finally {
    reader.releaseLock();
    res.end();
  }
}));

app.get("/api/apps", (req, res) => {
  const session = getCurrentSession(req);

  if (!session) {
    res.status(401).json({ authenticated: false, loginUrl: "/auth/login" });
    return;
  }

  const sessionAccess = getSessionAccessState(session);

  if (!sessionAccess.ok) {
    rejectInactiveSession(res, session, sessionAccess);
    return;
  }

  res.json({ apps: getAppsForUser(sessionAccess.runtimeContext.groups) });
});

app.get("/api/services/:serviceSlug/optional-launch", (req, res) => {
  const serviceSlug = sanitizeServiceSlug(req.params.serviceSlug);
  const fallbackPath = req.query.fallbackTo || req.query.returnTo || "/";
  const fallbackUrl = buildOptionalServiceFallbackUrl(serviceSlug, null, fallbackPath);
  const session = getCurrentSession(req);

  if (!session) {
    setNoStore(res);
    res.redirect(fallbackUrl);
    return;
  }

  const sessionAccess = getSessionAccessState(session);

  if (!sessionAccess.ok) {
    expireSession(res, session);
    setNoStore(res);
    res.redirect(fallbackUrl);
    return;
  }

  const runtimeContext = sessionAccess.runtimeContext;
  const app = getAppsForUser(runtimeContext.groups).find((candidate) => candidate.slug === serviceSlug);
  const appFallbackUrl = buildOptionalServiceFallbackUrl(serviceSlug, app, fallbackPath);

  if (!app || !app.hasAccess || app.status !== "active") {
    setNoStore(res);
    res.redirect(appFallbackUrl);
    return;
  }

  redirectToServiceLaunchTarget(res, serviceSlug, runtimeContext, app, req.query.next_path || req.query.returnTo || "/");
});

app.get("/api/services/:serviceSlug/launch", requireSession, (req, res) => {
  const serviceSlug = sanitizeServiceSlug(req.params.serviceSlug);
  const runtimeContext = getRuntimeSessionContext(req.nodedcSession);
  const app = getAppsForUser(runtimeContext.groups).find((candidate) => candidate.slug === serviceSlug);

  if (!app || !app.hasAccess || app.status !== "active") {
    res.status(403).type("text/plain").send("NODE.DC service access denied.");
    return;
  }

  redirectToServiceLaunchTarget(res, serviceSlug, runtimeContext, app, req.query.next_path || req.query.returnTo || "/");
});

app.post("/api/internal/handoff/consume", (req, res) => {
  if (!isInternalRequestAuthorized(req)) {
    res.status(config.internalAccessToken ? 401 : 503).json({
      ok: false,
      error: config.internalAccessToken ? "internal_handoff_unauthorized" : "internal_handoff_not_configured",
    });
    return;
  }

  const token = typeof req.body?.token === "string" ? req.body.token : "";
  const serviceSlug = sanitizeServiceSlug(req.body?.serviceSlug);
  const handoff = consumeServiceHandoff(token, serviceSlug);

  if (!handoff) {
    res.status(404).json({ ok: false, error: "handoff_not_found" });
    return;
  }

  const snapshot = controlPlaneStore.getSnapshot({ name: "NODE.DC handoff validation" });
  const user = findInternalAccessUser(snapshot.data, {
    subject: handoff.user.sub,
    email: handoff.user.email,
  });
  const groups = user ? resolveRequiredGroups(snapshot.data, user) : handoff.user.groups;
  const app = getAppsForUser(groups).find((candidate) => candidate.slug === serviceSlug);

  if (!user || !app?.hasAccess) {
    res.status(403).json({ ok: false, error: "handoff_access_denied" });
    return;
  }

  res.json({
    ok: true,
    serviceSlug,
    user: {
      id: user.id,
      email: user.email,
      name: user.name,
      avatarUrl: resolveUserAvatarPublicUrl(user),
      subject: user.authentikUserId || handoff.user.sub,
      authentikUserId: user.authentikUserId ?? null,
      groups,
    },
  });
});

app.get("/api/profile", requireSession, (req, res) => {
  const { actor, data } = getLauncherProfileContext(req.nodedcSession);
  const user = findLauncherUser(data, actor.id);

  res.json({
    user,
    memberships: data.memberships.filter((membership) => membership.userId === user.id),
  });
});

app.post("/api/assistant/actions", requireSession, asyncRoute(async (req, res) => {
  const { actor, data } = getLauncherProfileContext(req.nodedcSession);
  const runtimeContext = getRuntimeSessionContext(req.nodedcSession);
  const command = buildAssistantActionProxyCommand(req.body, {
    actor,
    data,
    groups: runtimeContext.groups,
  });
  const payload = await requestAiWorkspaceAssistantJson("/api/ai-workspace/assistant/v1/actions", {
    body: command,
    actor,
    groups: runtimeContext.groups,
    role: command.input.launcherGlobalRole,
  });
  res.json(payload);
}));

app.get("/api/events", requireSession, (req, res) => {
  const client = {
    id: randomUUID(),
    res,
  };

  res.setHeader("Content-Type", "text/event-stream");
  res.setHeader("Cache-Control", "no-cache, no-transform");
  res.setHeader("Connection", "keep-alive");
  res.setHeader("X-Accel-Buffering", "no");
  res.flushHeaders?.();
  res.write(`event: nodedc-ready\ndata: ${JSON.stringify({ ok: true })}\n\n`);

  const keepAlive = setInterval(() => {
    res.write(": keep-alive\n\n");
  }, 30000);

  runtimeEventClients.add(client);

  req.on("close", () => {
    clearInterval(keepAlive);
    runtimeEventClients.delete(client);
  });
});

app.post("/api/internal/access/check", (req, res) => {
  if (!isInternalRequestAuthorized(req)) {
    res.status(config.internalAccessToken ? 401 : 503).json({
      ok: false,
      error: config.internalAccessToken ? "internal_access_unauthorized" : "internal_access_not_configured",
    });
    return;
  }

  const snapshot = controlPlaneStore.getSnapshot({ name: "NODE.DC internal access check" });
  const user = findInternalAccessUser(snapshot.data, req.body);
  const serviceSlug = sanitizeServiceSlug(req.body?.serviceSlug);
  const workspaceSlug = normalizeOptionalText(req.body?.workspaceSlug ?? req.body?.workspace?.slug);

  if (!user) {
    res.json({
      ok: true,
      allowed: false,
      reason: "user_not_found",
      serviceSlug,
      groups: [],
      matchedGroups: [],
      user: null,
    });
    return;
  }

  const groups = resolveRequiredGroups(snapshot.data, user);
  const app = getAppsForUser(groups).find((candidate) => candidate.slug === serviceSlug);
  const allowed = Boolean(app?.hasAccess);
  const serviceModules =
    serviceSlug === "task-manager"
      ? resolveTaskManagerWorkspaceServiceModules(snapshot.data, user, serviceSlug, workspaceSlug)
      : resolveUserServiceModules(snapshot.data, user, serviceSlug, null);
  const workspacePolicy =
    serviceSlug === "task-manager"
      ? resolveTaskManagerWorkspacePolicy(snapshot.data, groups, allowed, user, workspaceSlug, serviceModules)
      : null;

  res.json({
    ok: true,
    allowed,
    reason: allowed ? "access_confirmed" : "access_denied",
    serviceSlug,
    groups,
    matchedGroups: app?.matchedGroups ?? [],
    serviceModules,
    workspacePolicy,
    user: {
      id: user.id,
      email: user.email,
      name: user.name,
      avatarUrl: user.avatarUrl ?? null,
      authentikUserId: user.authentikUserId ?? null,
      globalStatus: user.globalStatus,
    },
  });
});

app.post("/api/internal/tasker/invite-requests", asyncRoute(async (req, res) => {
  if (!isInternalRequestAuthorized(req)) {
    res.status(config.internalAccessToken ? 401 : 503).json({
      ok: false,
      error: config.internalAccessToken ? "internal_access_unauthorized" : "internal_access_not_configured",
    });
    return;
  }

  const snapshot = controlPlaneStore.getSnapshot({ name: "NODE.DC tasker invite request" });
  const inviterPayload = typeof req.body?.inviter === "object" && req.body.inviter !== null ? req.body.inviter : req.body;
  const inviter = findInternalAccessUser(snapshot.data, {
    subject: inviterPayload.subject,
    email: inviterPayload.email,
    userId: inviterPayload.userId,
  });

  if (!inviter) {
    res.status(404).json({ ok: false, error: "inviter_not_found" });
    return;
  }

  const groups = resolveRequiredGroups(snapshot.data, inviter);
  const app = getAppsForUser(groups).find((candidate) => candidate.slug === "task-manager");
  const workspaceSlug = req.body?.workspace?.slug ?? req.body?.workspaceSlug;
  const workspacePolicy = resolveTaskManagerWorkspacePolicy(
    snapshot.data,
    groups,
    Boolean(app?.hasAccess),
    inviter,
    workspaceSlug
  );

  if (
    !app?.hasAccess ||
    workspacePolicy.managedBy !== "tasker" ||
    !["nodedc", "launcher"].includes(workspacePolicy.inviteApproval)
  ) {
    res.status(403).json({ ok: false, error: "nodedc_tasker_invite_approval_not_allowed", workspacePolicy });
    return;
  }

  const result = await controlPlaneStore.createTaskerInviteRequest({
    taskerInviteId: req.body?.taskerInviteId,
    workspaceId: req.body?.workspace?.id ?? req.body?.workspaceId,
    workspaceSlug: req.body?.workspace?.slug ?? req.body?.workspaceSlug,
    workspaceName: req.body?.workspace?.name ?? req.body?.workspaceName,
    inviteeEmail: req.body?.invitee?.email ?? req.body?.inviteeEmail,
    role: req.body?.invitee?.role ?? req.body?.role,
    inviterUserId: inviter.id,
    inviterPlaneUserId: inviterPayload.planeUserId,
    inviterEmail: inviter.email,
    inviterName: inviter.name,
  }, inviter);

  publishControlPlaneEvent(
    "tasker.invite-request.created",
    result.affectedUserIds?.length ? result.affectedUserIds : [inviter.id]
  );
  publishNotificationEvent({
    type: result.taskerInviteRequest.status === "approved" ? "tasker.workspace_invite.approved" : "tasker.workspace_invite.requested",
    sourceService: "tasker",
    actor: { userId: inviter.id, email: inviter.email },
    subject: { type: "tasker_invite_request", id: result.taskerInviteRequest.id },
    payload: {
      requestId: result.taskerInviteRequest.id,
      workspaceSlug: result.taskerInviteRequest.workspaceSlug,
      workspaceName: result.taskerInviteRequest.workspaceName,
      inviteeEmail: result.taskerInviteRequest.inviteeEmail,
      status: result.taskerInviteRequest.status,
    },
    idempotencyKey: `launcher:tasker-invite:${result.taskerInviteRequest.id}:${result.taskerInviteRequest.status}:${result.taskerInviteRequest.updatedAt}`,
    deliveries: (
      result.taskerInviteRequest.status === "new"
        ? [
            rootAdminNotificationDelivery(result.data, {
              ...buildTaskerInviteHubNotification(result.taskerInviteRequest),
              actionable: true,
              actionType: "tasker.workspace_invite.review",
              entityType: "tasker_invite_request",
              entityId: result.taskerInviteRequest.id,
            }),
          ]
        : notificationUsersForTaskerInvite(result.data, result.taskerInviteRequest).map((user) =>
            notificationDeliveryForUser(user, "hub", {
              ...buildTaskerInviteHubNotification(result.taskerInviteRequest),
              entityType: "tasker_invite_request",
              entityId: result.taskerInviteRequest.id,
            })
          )
    ).filter(Boolean),
  });
  res.json({ ok: true, taskerInviteRequest: result.taskerInviteRequest, autoApproved: Boolean(result.autoApproved) });
}));

app.post("/api/internal/tasker/invite-requests/cancel", asyncRoute(async (req, res) => {
  if (!isInternalRequestAuthorized(req)) {
    res.status(config.internalAccessToken ? 401 : 503).json({
      ok: false,
      error: config.internalAccessToken ? "internal_access_unauthorized" : "internal_access_not_configured",
    });
    return;
  }

  const result = await controlPlaneStore.cancelTaskerInviteRequest(req.body, {
    name: req.body?.cancelledBy?.name ?? req.body?.cancelledBy?.email ?? "Operational Core",
    email: req.body?.cancelledBy?.email,
    source: "tasker",
  });
  const syncResult = await syncUsersToAuthentik(result.data, result.affectedUserIds ?? [], {
    name: req.body?.cancelledBy?.name ?? req.body?.cancelledBy?.email ?? "Operational Core",
    email: req.body?.cancelledBy?.email,
    source: "tasker",
  });

  if (result.taskerInviteRequest) {
    publishControlPlaneEvent("tasker.invite-request.cancelled", [
      result.taskerInviteRequest.inviterUserId,
      ...syncResult.userIds,
    ]);
  }

  res.json({ ok: true, taskerInviteRequest: result.taskerInviteRequest });
}));

app.post("/api/internal/engine/workflow-access-requests", asyncRoute(async (req, res) => {
  if (!isInternalRequestAuthorized(req)) {
    res.status(config.internalAccessToken ? 401 : 503).json({
      ok: false,
      error: config.internalAccessToken ? "internal_access_unauthorized" : "internal_access_not_configured",
    });
    return;
  }

  const snapshot = controlPlaneStore.getSnapshot({ name: "NODE.DC Engine workflow access request" });
  const requesterPayload = typeof req.body?.requester === "object" && req.body.requester !== null ? req.body.requester : {};
  const requester = findInternalAccessUser(snapshot.data, {
    subject: requesterPayload.subject,
    email: requesterPayload.email,
    userId: requesterPayload.userId,
  });
  const targetEmail = typeof req.body?.targetEmail === "string" ? req.body.targetEmail.trim().toLowerCase() : "";
  const targetUser = findInternalAccessUser(snapshot.data, { email: targetEmail });

  if (!requester) {
    res.status(404).json({ ok: false, error: "requester_not_found" });
    return;
  }

  if (!targetUser || targetUser.globalStatus !== "active") {
    res.status(404).json({ ok: false, error: "user_not_found" });
    return;
  }

  const groups = resolveRequiredGroups(snapshot.data, targetUser);
  const app = getAppsForUser(groups).find((candidate) => candidate.slug === "nodedc");

  if (app?.hasAccess) {
    res.json({
      ok: true,
      alreadyAllowed: true,
      targetUser: {
        id: targetUser.id,
        email: targetUser.email,
        name: targetUser.name,
        avatarUrl: targetUser.avatarUrl ?? null,
      },
    });
    return;
  }

  const result = await controlPlaneStore.createEngineWorkflowAccessRequest({
    workflowId: req.body?.workflow?.id ?? req.body?.workflowId,
    workflowName: req.body?.workflow?.name ?? req.body?.workflowName,
    targetEmail,
    role: req.body?.role,
    requesterUserId: requester.id,
    requesterEmail: requester.email,
    requesterName: requester.name,
  }, requester);

  publishControlPlaneEvent(
    "engine.workflow-access-request.created",
    result.affectedUserIds?.length ? result.affectedUserIds : [requester.id, targetUser.id]
  );
  publishNotificationEvent({
    type: "engine.workflow_share.access_requested",
    sourceService: "engine",
    actor: { userId: requester.id, email: requester.email },
    subject: { type: "engine_workflow_access_request", id: result.engineWorkflowAccessRequest.id },
    payload: {
      requestId: result.engineWorkflowAccessRequest.id,
      workflowId: result.engineWorkflowAccessRequest.workflowId,
      workflowName: result.engineWorkflowAccessRequest.workflowName,
      targetUserId: targetUser.id,
      targetEmail: targetUser.email,
      role: result.engineWorkflowAccessRequest.role,
    },
    idempotencyKey: `launcher:engine-workflow-request:${result.engineWorkflowAccessRequest.id}:created:${result.engineWorkflowAccessRequest.updatedAt}`,
    deliveries: [
      rootAdminNotificationDelivery(result.data, {
        ...buildEngineWorkflowHubNotification(result.engineWorkflowAccessRequest),
        actionable: true,
        actionType: "engine.workflow_access.review",
        entityType: "engine_workflow_access_request",
        entityId: result.engineWorkflowAccessRequest.id,
      }),
    ].filter(Boolean),
  });
  res.json({ ok: true, engineWorkflowAccessRequest: result.engineWorkflowAccessRequest });
}));

app.post("/api/internal/engine/role-access-requests", asyncRoute(async (req, res) => {
  if (!isInternalRequestAuthorized(req)) {
    res.status(config.internalAccessToken ? 401 : 503).json({
      ok: false,
      error: config.internalAccessToken ? "internal_access_unauthorized" : "internal_access_not_configured",
    });
    return;
  }

  const snapshot = controlPlaneStore.getSnapshot({ name: "NODE.DC Engine role access request" });
  const requesterPayload = typeof req.body?.requester === "object" && req.body.requester !== null ? req.body.requester : {};
  const requester = findInternalAccessUser(snapshot.data, {
    subject: requesterPayload.subject,
    email: requesterPayload.email,
    userId: requesterPayload.userId,
  });

  if (!requester || requester.globalStatus !== "active") {
    res.status(404).json({ ok: false, error: "requester_not_found" });
    return;
  }

  const result = await controlPlaneStore.createEngineRoleAccessRequest({
    requestedRole: req.body?.requestedRole,
    currentRole: req.body?.currentRole,
    requesterUserId: requester.id,
    requesterEmail: requester.email,
    requesterName: requester.name,
  }, requester);

  publishControlPlaneEvent(
    "engine.role-access-request.created",
    result.affectedUserIds?.length ? result.affectedUserIds : [requester.id, "user_root"]
  );
  publishNotificationEvent({
    type: "engine.role_upgrade.requested",
    sourceService: "engine",
    actor: { userId: requester.id, email: requester.email },
    subject: { type: "engine_role_access_request", id: result.engineWorkflowAccessRequest.id },
    payload: {
      requestId: result.engineWorkflowAccessRequest.id,
      targetUserId: requester.id,
      targetEmail: requester.email,
      requestedServiceRole: result.engineWorkflowAccessRequest.requestedServiceRole,
      currentServiceRole: result.engineWorkflowAccessRequest.currentServiceRole,
    },
    idempotencyKey: `launcher:engine-role-request:${result.engineWorkflowAccessRequest.id}:created:${result.engineWorkflowAccessRequest.updatedAt}`,
    deliveries: [
      rootAdminNotificationDelivery(result.data, {
        ...buildEngineRoleHubNotification(result.engineWorkflowAccessRequest),
        actionable: true,
        actionType: "engine.role_upgrade.review",
        entityType: "engine_role_access_request",
        entityId: result.engineWorkflowAccessRequest.id,
      }),
    ].filter(Boolean),
  });
  res.json({ ok: true, engineWorkflowAccessRequest: result.engineWorkflowAccessRequest });
}));

app.post("/api/internal/engine/role-access-requests/list", asyncRoute(async (req, res) => {
  if (!isInternalRequestAuthorized(req)) {
    res.status(config.internalAccessToken ? 401 : 503).json({
      ok: false,
      error: config.internalAccessToken ? "internal_access_unauthorized" : "internal_access_not_configured",
    });
    return;
  }

  const snapshot = controlPlaneStore.getSnapshot({ name: "NODE.DC Engine role access request list" });
  const requesterPayload = typeof req.body?.requester === "object" && req.body.requester !== null ? req.body.requester : {};
  const requester = findInternalAccessUser(snapshot.data, {
    subject: requesterPayload.subject,
    email: requesterPayload.email,
    userId: requesterPayload.userId,
  });

  if (!requester || requester.globalStatus !== "active") {
    res.status(404).json({ ok: false, error: "requester_not_found" });
    return;
  }

  const requesterEmail = String(requester.email || "").toLowerCase();
  const requesterGroups = Array.isArray(requesterPayload.groups)
    ? requesterPayload.groups.map((group) => String(group || "").trim()).filter(Boolean)
    : [];
  const canReviewEngineRoleRequests = requester.id === "user_root" || isLauncherAdmin(requesterGroups);
  const requests = snapshot.data.engineWorkflowAccessRequests
    .filter((request) => {
      if (request.requestType !== "service_role" || request.status === "cancelled") return false;
      const isRelated =
        request.requesterUserId === requester.id ||
        request.targetUserId === requester.id ||
        String(request.requesterEmail || "").toLowerCase() === requesterEmail ||
        String(request.targetEmail || "").toLowerCase() === requesterEmail;

      if (canReviewEngineRoleRequests && request.status === "new") return true;
      return isRelated && request.status !== "new";
    })
    .sort((left, right) => String(right.updatedAt || right.createdAt).localeCompare(String(left.updatedAt || left.createdAt)));

  res.json({ ok: true, requests });
}));

app.post("/api/internal/tasker/profile-sync", asyncRoute(async (req, res) => {
  if (!isInternalRequestAuthorized(req)) {
    res.status(config.internalAccessToken ? 401 : 503).json({
      ok: false,
      error: config.internalAccessToken ? "internal_access_unauthorized" : "internal_access_not_configured",
    });
    return;
  }

  const snapshot = controlPlaneStore.getSnapshot({ name: "NODE.DC tasker profile sync" });
  const user = findInternalAccessUser(snapshot.data, req.body);

  if (!user) {
    res.status(404).json({ ok: false, error: "user_not_found" });
    return;
  }

  const patch = sanitizeTaskerProfilePatch(req.body);

  if (Object.keys(patch).length === 0) {
    res.json({ ok: true, user, data: snapshot.data, skipped: true });
    return;
  }

  const actor = {
    sub: "tasker-profile-sync",
    name: req.body?.source === "tasker" ? "Operational Core" : "NODE.DC profile sync",
    email: typeof req.body?.email === "string" ? req.body.email : user.email,
    source: "tasker",
  };
  const result = await controlPlaneStore.updateUserProfile(user.id, patch, actor);
  const syncResult = await syncUsersToAuthentik(result.data, [user.id], actor);
  const updatedUser = syncResult.data.users.find((candidate) => candidate.id === user.id) ?? result.user;

  publishControlPlaneEvent("tasker.profile.updated", [user.id]);
  res.json({ ok: true, user: updatedUser, data: syncResult.data });
}));

app.patch("/api/profile", requireSession, asyncRoute(async (req, res) => {
  const { actor } = getLauncherProfileContext(req.nodedcSession);
  const result = await controlPlaneStore.updateUserProfile(actor.id, sanitizeSelfProfilePatch(req.body), req.nodedcSession.user);
  const provisionedUser = await authentikSyncClient.provisionUser({
    data: result.data,
    userId: actor.id,
  });
  const storeResult = await controlPlaneStore.markUserAuthentikProvisioned(actor.id, provisionedUser, req.nodedcSession.user);
  const taskManagerProfile = await syncTaskManagerUserProfile(storeResult.user);

  publishControlPlaneEvent("profile.updated", [actor.id]);
  res.json({
    ...storeResult,
    data: scopeSessionControlPlaneData(req, storeResult.data, actor.id),
    provisioning: toProvisioningResponse(provisionedUser),
    taskManagerProfile,
  });
}));

app.post("/api/profile/password", requireSession, asyncRoute(async (req, res) => {
  const newPassword = sanitizeNewPassword(req.body?.newPassword);
  const { actor, data } = getLauncherProfileContext(req.nodedcSession);
  const provisionedUser = await authentikSyncClient.provisionUser({
    data,
    userId: actor.id,
    password: newPassword,
  });
  const result = await controlPlaneStore.markUserAuthentikProvisioned(actor.id, provisionedUser, req.nodedcSession.user);

  publishControlPlaneEvent("profile.password.updated", [actor.id]);
  res.json({ data: scopeSessionControlPlaneData(req, result.data, actor.id), ok: true });
}));

app.get("/api/invites/:token", (req, res) => {
  try {
    res.json(controlPlaneStore.getInviteByToken(req.params.token));
  } catch (error) {
    sendInviteApiError(res, error);
  }
});

app.post("/api/invites/:token/register", asyncRoute(async (req, res) => {
  let payload;

  try {
    payload = sanitizeInviteRegistrationPayload(req.body);
  } catch (error) {
    sendInviteApiError(res, error);
    return;
  }

  if (!authentikSyncClient.isConfigured()) {
    res.status(503).json({ error: "Регистрация временно недоступна: Authentik API не настроен" });
    return;
  }

  let draft;

  try {
    draft = controlPlaneStore.prepareInviteRegistration(req.params.token, payload);
  } catch (error) {
    sendInviteApiError(res, error);
    return;
  }

  const provisionedUser = await authentikSyncClient.provisionUser({
    data: draft.data,
    userId: draft.user.id,
    password: payload.password,
  });
  const result = await controlPlaneStore.commitInviteRegistration(req.params.token, payload, provisionedUser);
  const storeResult = await controlPlaneStore.markUserAuthentikProvisioned(result.user.id, provisionedUser, {
    sub: provisionedUser.authentikUserId,
    email: result.user.email,
    name: result.user.name,
  });
  const groups = resolveRequiredGroups(storeResult.data, storeResult.user);

  createLauncherSession(
    res,
    normalizeControlPlaneSessionUser(storeResult.user, groups),
    {
      idToken: null,
      accessToken: null,
      expiresAt: null,
    }
  );

  publishControlPlaneEvent("invite.registered", [result.user.id]);
  const redirectUrl = resolveInviteRedirectUrl(result.invite);
  res.json({
    ...result,
    user: storeResult.user,
    data: storeResult.data,
    provisioning: toProvisioningResponse(provisionedUser),
    loginUrl: buildLoginRedirectUrl(redirectUrl, { forceLogin: true, includeReturnTo: true }),
    redirectUrl,
    authenticated: true,
  });
}));

app.post("/api/invites/:token/accept", requireSession, asyncRoute(async (req, res) => {
  let result;

  try {
    result = await controlPlaneStore.acceptInvite(req.params.token, req.nodedcSession.user);
  } catch (error) {
    sendInviteApiError(res, error);
    return;
  }

  const syncResult = await syncUsersToAuthentik(result.data, [result.user.id], req.nodedcSession.user);

  publishControlPlaneEvent("invite.accepted", syncResult.userIds);
  res.json({ ...result, data: syncResult.data, redirectUrl: resolveInviteRedirectUrl(result.invite) });
}));

app.get("/tasker-workspace-invite/:taskerInviteRequestId", (req, res) => {
  const session = getCurrentSession(req);

  if (!session) {
    res.redirect(buildLoginRedirectUrl(req.originalUrl, { forceLogin: true }));
    return;
  }

  const sessionAccess = getSessionAccessState(session);

  if (!sessionAccess.ok) {
    expireSession(res, session);
    res.redirect(buildLoginRedirectUrl(req.originalUrl, { forceLogin: true }));
    return;
  }

  const runtimeContext = sessionAccess.runtimeContext;
  const request = controlPlaneStore
    .getSnapshot({ name: "NODE.DC tasker invite redirect" })
    .data.taskerInviteRequests.find((candidate) => candidate.id === req.params.taskerInviteRequestId);

  if (!request || request.status !== "approved") {
    res.status(404).send("Workspace-инвайт не найден или ещё не подтверждён NODE.DC.");
    return;
  }

  if (session.user.email?.toLowerCase() !== request.inviteeEmail.toLowerCase()) {
    res.status(403).send("Этот workspace-инвайт выписан на другую почту.");
    return;
  }

  const handoffToken = createServiceHandoff("task-manager", runtimeContext.user);
  const taskBaseUrl = getTaskBaseUrl();
  const targetUrl = new URL("/auth/nodedc/handoff/", taskBaseUrl);
  targetUrl.searchParams.set("token", handoffToken);
  targetUrl.searchParams.set(
    "next_path",
    `/auth/nodedc/workspace-invite/accept/${encodeURIComponent(request.id)}/`
  );

  res.redirect(targetUrl.toString());
});

app.get("/api/admin/control-plane", requireLauncherAdmin, (req, res) => {
  res.json(scopeAdminSnapshot(req));
});

app.get("/api/storage/data", requireSession, (req, res) => {
  const snapshot = controlPlaneStore.getSnapshot(req.nodedcSession.user);

  if (snapshot.actor.source !== "launcher") {
    res.status(403).json({ error: "Профиль пользователя не найден в Launcher control-plane" });
    return;
  }

  res.json(scopeSessionControlPlaneData(req, snapshot.data, snapshot.actor.id));
});

app.patch("/api/admin/settings", requireLauncherAdmin, requireRootLauncherAdmin, asyncRoute(async (req, res) => {
  const result = await controlPlaneStore.updateSettings(req.body, req.nodedcSession.user);
  publishControlPlaneEvent("admin.settings.updated");
  res.json(result);
}));

app.get("/api/admin/clients", requireLauncherAdmin, (req, res) => {
  const snapshot = scopeAdminSnapshot(req);
  res.json({ clients: snapshot.data.clients });
});

app.get("/api/admin/task-manager/workspaces", requireLauncherAdmin, asyncRoute(async (req, res) => {
  const taskManager = await requestTaskManagerInternalJson("/api/internal/nodedc/workspaces/");

  if (req.nodedcAdminScope?.isRoot) {
    res.json(taskManager);
    return;
  }

  const allowedWorkspaceSlugs = new Set(
    req.nodedcAdminScope.snapshot.data.clients
      .filter((client) => req.nodedcAdminScope.clientIds.has(client.id))
      .flatMap((client) => {
        const workspaces = Array.isArray(client.integrations?.taskManager?.workspaces)
          ? client.integrations.taskManager.workspaces
          : [];
        const slugs = workspaces.map((workspace) => workspace?.slug).filter((slug) => typeof slug === "string" && slug.trim());
        const legacySlug = client.integrations?.taskManager?.workspaceSlug;
        return legacySlug ? [...slugs, legacySlug] : slugs;
      })
  );
  res.json({
    ...taskManager,
    workspaces: (taskManager.workspaces ?? []).filter((workspace) => allowedWorkspaceSlugs.has(workspace.slug)),
  });
}));

app.post("/api/admin/task-manager/workspace-memberships/ensure", requireLauncherAdmin, asyncRoute(async (req, res) => {
  const snapshot = controlPlaneStore.getSnapshot(req.nodedcSession.user);
  const clientId = typeof req.body?.clientId === "string" ? req.body.clientId : "";
  const userId = typeof req.body?.userId === "string" ? req.body.userId : "";
  const client = snapshot.data.clients.find((candidate) => candidate.id === clientId);
  const user = snapshot.data.users.find((candidate) => candidate.id === userId);

  if (!client) {
    res.status(404).json({ ok: false, error: "client_not_found" });
    return;
  }

  if (!user) {
    res.status(404).json({ ok: false, error: "user_not_found" });
    return;
  }

  if (!assertAdminCanManageClient(req, res, client.id) || !assertAdminCanManageAccessForUser(req, res, user.id)) {
    return;
  }

  const membership = snapshot.data.memberships.find((candidate) => candidate.clientId === client.id && candidate.userId === user.id);
  const workspaceSlug = normalizeOptionalText(req.body?.workspaceSlug) ?? client.integrations?.taskManager?.workspaceSlug ?? null;
  const workspaceManagedBy = resolveTaskManagerWorkspaceManagedByForClient(client, workspaceSlug);

  if (!workspaceSlug) {
    res.status(400).json({ ok: false, error: "task_manager_workspace_not_configured" });
    return;
  }

  const requestedRole = normalizeTaskManagerRole(req.body?.role);
  if (requestedRole === "admin") {
    res.status(403).json({
      ok: false,
      error: "task_manager_admin_role_locked",
      message: "Admin-роль Operational Core назначается только системно.",
    });
    return;
  }

  const role = requestedRole ?? resolveTaskManagerRoleForMembership(membership?.role);
  const taskManager = await requestTaskManagerInternalJson("/api/internal/nodedc/workspace-memberships/ensure/", {
    method: "POST",
    body: {
      workspaceSlug,
      email: user.email,
      subject: user.authentikUserId ?? undefined,
      avatarUrl: resolveUserAvatarPublicUrl(user),
      role,
      companyRole: membership?.role ?? null,
      managedBy: workspaceManagedBy,
      setLastWorkspace: req.body?.setLastWorkspace !== false,
    },
  });

  const result = await controlPlaneStore.recordTaskManagerWorkspaceMembership(
    {
      clientId: client.id,
      userId: user.id,
      workspaceSlug,
      role,
      managedBy: workspaceManagedBy,
      taskManager,
    },
    req.nodedcSession.user
  );

  publishControlPlaneEvent("admin.task-manager.workspace-membership.updated", [user.id]);
  res.json({ ...scopeAdminMutationResult(req, result), taskManager });
}));

app.post("/api/admin/task-manager/workspace-memberships/remove", requireLauncherAdmin, asyncRoute(async (req, res) => {
  const snapshot = controlPlaneStore.getSnapshot(req.nodedcSession.user);
  const clientId = typeof req.body?.clientId === "string" ? req.body.clientId : "";
  const userId = typeof req.body?.userId === "string" ? req.body.userId : "";
  const client = snapshot.data.clients.find((candidate) => candidate.id === clientId);
  const user = snapshot.data.users.find((candidate) => candidate.id === userId);

  if (!client) {
    res.status(404).json({ ok: false, error: "client_not_found" });
    return;
  }

  if (!user) {
    res.status(404).json({ ok: false, error: "user_not_found" });
    return;
  }

  if (!assertAdminCanManageClient(req, res, client.id) || !assertAdminCanManageAccessForUser(req, res, user.id)) {
    return;
  }

  const membership = snapshot.data.memberships.find((candidate) => candidate.clientId === client.id && candidate.userId === user.id);
  const workspaceSlug = normalizeOptionalText(req.body?.workspaceSlug) ?? client.integrations?.taskManager?.workspaceSlug ?? null;

  if (!workspaceSlug) {
    res.status(400).json({ ok: false, error: "task_manager_workspace_not_configured" });
    return;
  }

  if (membership?.role === "client_owner") {
    const taskManager = await requestTaskManagerInternalJson("/api/internal/nodedc/workspace-memberships/ensure/", {
      method: "POST",
      body: {
        workspaceSlug,
        email: user.email,
        subject: user.authentikUserId ?? undefined,
        avatarUrl: resolveUserAvatarPublicUrl(user),
      role: "admin",
      companyRole: membership.role,
      managedBy: resolveTaskManagerWorkspaceManagedByForClient(client, workspaceSlug),
      setLastWorkspace: false,
    },
  });
    const result = await controlPlaneStore.recordTaskManagerWorkspaceMembership(
      {
        clientId: client.id,
        userId: user.id,
        workspaceSlug,
        role: "admin",
        managedBy: resolveTaskManagerWorkspaceManagedByForClient(client, workspaceSlug),
        taskManager,
      },
      req.nodedcSession.user
    );

    publishControlPlaneEvent("admin.task-manager.workspace-membership.updated", [user.id]);
    res.json({ ...scopeAdminMutationResult(req, result), taskManager, protected: true });
    return;
  }

  const taskManager = await requestTaskManagerInternalJson("/api/internal/nodedc/workspace-memberships/remove/", {
    method: "POST",
    body: {
      workspaceSlug,
      email: user.email,
      subject: user.authentikUserId ?? undefined,
    },
  });
  const result = await controlPlaneStore.removeTaskManagerWorkspaceMembership(
    {
      clientId: client.id,
      userId: user.id,
      workspaceSlug,
    },
    req.nodedcSession.user
  );

  publishControlPlaneEvent("admin.task-manager.workspace-membership.updated", [user.id]);
  res.json({ ...scopeAdminMutationResult(req, result), taskManager });
}));

app.post("/api/admin/task-manager/project-memberships/ensure", requireLauncherAdmin, asyncRoute(async (req, res) => {
  const snapshot = controlPlaneStore.getSnapshot(req.nodedcSession.user);
  const clientId = typeof req.body?.clientId === "string" ? req.body.clientId : "";
  const userId = typeof req.body?.userId === "string" ? req.body.userId : "";
  const client = snapshot.data.clients.find((candidate) => candidate.id === clientId);
  const user = snapshot.data.users.find((candidate) => candidate.id === userId);

  if (!client) {
    res.status(404).json({ ok: false, error: "client_not_found" });
    return;
  }

  if (!user) {
    res.status(404).json({ ok: false, error: "user_not_found" });
    return;
  }

  if (!assertAdminCanManageClient(req, res, client.id) || !assertAdminCanManageAccessForUser(req, res, user.id)) {
    return;
  }

  const workspaceSlug = normalizeOptionalText(req.body?.workspaceSlug);
  const projectId = normalizeOptionalText(req.body?.projectId);
  const role = normalizeTaskManagerRole(req.body?.role);
  const workspaceManagedBy = resolveTaskManagerWorkspaceManagedByForClient(client, workspaceSlug);

  if (!workspaceSlug) {
    res.status(400).json({ ok: false, error: "task_manager_workspace_not_configured" });
    return;
  }

  if (!projectId) {
    res.status(400).json({ ok: false, error: "task_manager_project_not_configured" });
    return;
  }

  if (!role) {
    res.status(400).json({ ok: false, error: "task_manager_role_invalid" });
    return;
  }

  if (role === "admin") {
    res.status(403).json({
      ok: false,
      error: "task_manager_admin_role_locked",
      message: "Admin-роль Operational Core назначается только системно.",
    });
    return;
  }

  const taskManager = await requestTaskManagerInternalJson("/api/internal/nodedc/project-memberships/ensure/", {
    method: "POST",
    body: {
      workspaceSlug,
      projectId,
      email: user.email,
      subject: user.authentikUserId ?? undefined,
      avatarUrl: resolveUserAvatarPublicUrl(user),
      role,
      managedBy: workspaceManagedBy,
      setLastWorkspace: false,
    },
  });

  const result = await controlPlaneStore.recordTaskManagerProjectMembership(
    {
      clientId: client.id,
      userId: user.id,
      workspaceSlug,
      projectId,
      role,
      managedBy: workspaceManagedBy,
      taskManager,
    },
    req.nodedcSession.user
  );

  publishControlPlaneEvent("admin.task-manager.project-membership.updated", [user.id]);
  res.json({ ...scopeAdminMutationResult(req, result), taskManager });
}));

app.post("/api/admin/task-manager/project-memberships/remove", requireLauncherAdmin, asyncRoute(async (req, res) => {
  const snapshot = controlPlaneStore.getSnapshot(req.nodedcSession.user);
  const clientId = typeof req.body?.clientId === "string" ? req.body.clientId : "";
  const userId = typeof req.body?.userId === "string" ? req.body.userId : "";
  const client = snapshot.data.clients.find((candidate) => candidate.id === clientId);
  const user = snapshot.data.users.find((candidate) => candidate.id === userId);

  if (!client) {
    res.status(404).json({ ok: false, error: "client_not_found" });
    return;
  }

  if (!user) {
    res.status(404).json({ ok: false, error: "user_not_found" });
    return;
  }

  if (!assertAdminCanManageClient(req, res, client.id) || !assertAdminCanManageAccessForUser(req, res, user.id)) {
    return;
  }

  const workspaceSlug = normalizeOptionalText(req.body?.workspaceSlug);
  const projectId = normalizeOptionalText(req.body?.projectId);

  if (!workspaceSlug) {
    res.status(400).json({ ok: false, error: "task_manager_workspace_not_configured" });
    return;
  }

  if (!projectId) {
    res.status(400).json({ ok: false, error: "task_manager_project_not_configured" });
    return;
  }

  const taskManager = await requestTaskManagerInternalJson("/api/internal/nodedc/project-memberships/remove/", {
    method: "POST",
    body: {
      workspaceSlug,
      projectId,
      email: user.email,
      subject: user.authentikUserId ?? undefined,
    },
  });
  const result = await controlPlaneStore.removeTaskManagerProjectMembership(
    {
      clientId: client.id,
      userId: user.id,
      workspaceSlug,
      projectId,
    },
    req.nodedcSession.user
  );

  publishControlPlaneEvent("admin.task-manager.project-membership.updated", [user.id]);
  res.json({ ...scopeAdminMutationResult(req, result), taskManager });
}));

app.post("/api/admin/clients", requireLauncherAdmin, requireRootLauncherAdmin, asyncRoute(async (req, res) => {
  const result = await controlPlaneStore.createClient(req.body, req.nodedcSession.user);
  res.status(201).json(result);
}));

app.patch("/api/admin/clients/:clientId", requireLauncherAdmin, requireRootLauncherAdmin, asyncRoute(async (req, res) => {
  const result = await controlPlaneStore.updateClient(req.params.clientId, req.body, req.nodedcSession.user);
  res.json(result);
}));

app.delete("/api/admin/clients/:clientId", requireLauncherAdmin, requireRootLauncherAdmin, asyncRoute(async (req, res) => {
  const result = await controlPlaneStore.deleteClient(req.params.clientId, req.nodedcSession.user);
  res.json(result);
}));

app.get("/api/admin/users", requireLauncherAdmin, (req, res) => {
  const snapshot = scopeAdminSnapshot(req);
  res.json({ users: snapshot.data.users, memberships: snapshot.data.memberships });
});

app.post("/api/admin/users", requireLauncherAdmin, asyncRoute(async (req, res) => {
  if (!assertAdminCanManageClient(req, res, req.body?.clientId)) {
    return;
  }

  const result = await controlPlaneStore.createUser(req.body, req.nodedcSession.user);
  let provisioning = null;

  if (req.body?.provisionAuth !== false) {
    const provisionedUser = await authentikSyncClient.provisionUser({
      data: result.data,
      userId: result.user.id,
      password: sanitizePassword(req.body?.password),
      generatePassword: req.body?.generatePassword !== false,
    });
    const storeResult = await controlPlaneStore.markUserAuthentikProvisioned(result.user.id, provisionedUser, req.nodedcSession.user);
    result.data = storeResult.data;
    provisioning = toProvisioningResponse(provisionedUser);
  }

  publishControlPlaneEvent("admin.user.created", [result.user.id]);
  res.status(201).json({ ...scopeAdminMutationResult(req, result), provisioning });
}));

app.patch("/api/admin/users/:userId/profile", requireLauncherAdmin, asyncRoute(async (req, res) => {
  if (!assertAdminCanManageUser(req, res, req.params.userId)) {
    return;
  }

  await sendAdminIdempotentJson(req, res, async () => {
    const beforeSnapshot = controlPlaneStore.getSnapshot(req.nodedcSession.user);
    const beforeUser = beforeSnapshot.data.users.find((candidate) => candidate.id === req.params.userId) ?? null;
    const result = await controlPlaneStore.updateUserProfile(req.params.userId, req.body, req.nodedcSession.user);
    const syncResult = await syncUsersToAuthentik(result.data, [req.params.userId], req.nodedcSession.user);
    const updatedUser = syncResult.data.users.find((candidate) => candidate.id === req.params.userId);
    const taskManagerProfile = await syncTaskManagerUserProfile(updatedUser);
    const taskManagerCleanup =
      beforeUser?.globalStatus === "active" && updatedUser?.globalStatus === "blocked"
        ? await cleanupTaskManagerUserAccess(updatedUser, {
            source: "launcher-user-blocked",
            revokeIdentityLinks: false,
            revokeTaskerAccess: true,
          })
        : null;
    publishControlPlaneEvent("admin.user.updated", syncResult.userIds);
    return { ...scopeAdminMutationResult(req, { ...result, data: syncResult.data }), taskManagerProfile, taskManagerCleanup };
  });
}));

app.delete("/api/admin/users/:userId", requireLauncherAdmin, requireRootLauncherAdmin, asyncRoute(async (req, res) => {
  const snapshot = controlPlaneStore.getSnapshot(req.nodedcSession.user);
  const user = snapshot.data.users.find((candidate) => candidate.id === req.params.userId);

  if (!user) {
    res.status(404).json({ error: "user_not_found" });
    return;
  }

  let authentik = null;

  if (authentikSyncClient.isConfigured()) {
    authentik = await authentikSyncClient.deleteUser({ data: snapshot.data, userId: req.params.userId });
  }

  const taskManagerCleanup = await cleanupTaskManagerUserAccess(user, {
    source: "launcher-user-hard-delete",
    revokeIdentityLinks: true,
    revokeTaskerAccess: true,
  });
  const result = await controlPlaneStore.deleteUser(req.params.userId, req.nodedcSession.user);
  publishControlPlaneEvent("admin.user.deleted", [req.params.userId]);
  res.json({ ...scopeAdminMutationResult(req, result), authentik, taskManagerCleanup });
}));

app.post("/api/admin/users/:userId/provision-authentik", requireLauncherAdmin, asyncRoute(async (req, res) => {
  if (!assertAdminCanManageUser(req, res, req.params.userId)) {
    return;
  }

  const snapshot = controlPlaneStore.getSnapshot(req.nodedcSession.user);
  const provisionedUser = await authentikSyncClient.provisionUser({
    data: snapshot.data,
    userId: req.params.userId,
    password: sanitizePassword(req.body?.password),
    generatePassword: req.body?.generatePassword === true,
  });
  const result = await controlPlaneStore.markUserAuthentikProvisioned(req.params.userId, provisionedUser, req.nodedcSession.user);

  publishControlPlaneEvent("admin.user.provisioned", [req.params.userId]);
  res.json({ ...scopeAdminMutationResult(req, result), provisioning: toProvisioningResponse(provisionedUser) });
}));

app.patch("/api/admin/memberships/:membershipId", requireLauncherAdmin, asyncRoute(async (req, res) => {
  const snapshot = controlPlaneStore.getSnapshot(req.nodedcSession.user);
  const membership = snapshot.data.memberships.find((candidate) => candidate.id === req.params.membershipId);

  if (!membership) {
    res.status(404).json({ error: "membership_not_found" });
    return;
  }

  if (!assertAdminCanManageMembership(req, res, membership)) {
    return;
  }

  await sendAdminIdempotentJson(req, res, async () => {
    const result = await controlPlaneStore.updateMembership(req.params.membershipId, req.body, req.nodedcSession.user);
    const syncResult = await syncUsersToAuthentik(result.data, [result.membership.userId], req.nodedcSession.user);
    publishControlPlaneEvent("admin.membership.updated", syncResult.userIds);
    return scopeAdminMutationResult(req, { ...result, data: syncResult.data });
  });
}));

app.delete("/api/admin/memberships/:membershipId", requireLauncherAdmin, asyncRoute(async (req, res) => {
  const snapshot = controlPlaneStore.getSnapshot(req.nodedcSession.user);
  const membership = snapshot.data.memberships.find((candidate) => candidate.id === req.params.membershipId);

  if (!membership) {
    res.status(404).json({ error: "membership_not_found" });
    return;
  }

  if (!assertAdminCanManageMembership(req, res, membership)) {
    return;
  }

  const result = await controlPlaneStore.deleteMembership(req.params.membershipId, req.nodedcSession.user);
  const syncResult = await syncUsersToAuthentik(result.data, [result.membership.userId], req.nodedcSession.user);
  publishControlPlaneEvent("admin.membership.deleted", syncResult.userIds);
  res.json(scopeAdminMutationResult(req, { ...result, data: syncResult.data }));
}));

app.post("/api/admin/invites", requireLauncherAdmin, asyncRoute(async (req, res) => {
  if (!assertAdminCanManageClient(req, res, req.body?.clientId)) {
    return;
  }

  const result = await controlPlaneStore.createInvite(req.body, req.nodedcSession.user);
  publishControlPlaneEvent("admin.invite.created");
  res.status(201).json(scopeAdminMutationResult(req, result));
}));

app.patch("/api/admin/invites/:inviteId", requireLauncherAdmin, asyncRoute(async (req, res) => {
  const snapshot = controlPlaneStore.getSnapshot(req.nodedcSession.user);
  const invite = snapshot.data.invites.find((candidate) => candidate.id === req.params.inviteId);

  if (!invite) {
    res.status(404).json({ error: "invite_not_found" });
    return;
  }

  if (!assertAdminCanManageClient(req, res, invite.clientId)) {
    return;
  }

  const result = await controlPlaneStore.updateInvite(req.params.inviteId, req.body, req.nodedcSession.user);
  publishControlPlaneEvent("admin.invite.updated");
  res.json(scopeAdminMutationResult(req, result));
}));

app.delete("/api/admin/invites/:inviteId", requireLauncherAdmin, asyncRoute(async (req, res) => {
  const snapshot = controlPlaneStore.getSnapshot(req.nodedcSession.user);
  const invite = snapshot.data.invites.find((candidate) => candidate.id === req.params.inviteId);

  if (!invite) {
    res.status(404).json({ error: "invite_not_found" });
    return;
  }

  if (!assertAdminCanManageClient(req, res, invite.clientId)) {
    return;
  }

  const result = await controlPlaneStore.deleteInvite(req.params.inviteId, req.nodedcSession.user);
  publishControlPlaneEvent("admin.invite.deleted");
  res.json(scopeAdminMutationResult(req, result));
}));

app.patch("/api/admin/access-requests/:accessRequestId", requireLauncherAdmin, requireRootLauncherAdmin, asyncRoute(async (req, res) => {
  try {
    const result = await controlPlaneStore.updateAccessRequest(req.params.accessRequestId, req.body, req.nodedcSession.user);
    publishControlPlaneEvent("admin.access-request.updated");
    res.json(scopeAdminMutationResult(req, result));
  } catch (error) {
    sendAccessRequestApiError(res, error);
  }
}));

app.post("/api/admin/access-requests/:accessRequestId/approve", requireLauncherAdmin, requireRootLauncherAdmin, asyncRoute(async (req, res) => {
  try {
    let result = await controlPlaneStore.approveAccessRequest(req.params.accessRequestId, req.body, req.nodedcSession.user);
    let provisioning = null;

    if (result.user && authentikSyncClient.isConfigured()) {
      provisioning = await authentikSyncClient.provisionUser({
        data: result.data,
        userId: result.user.id,
      });
      const syncResult = await controlPlaneStore.markUserAuthentikProvisioned(result.user.id, provisioning, req.nodedcSession.user);
      result = { ...result, data: syncResult.data, user: syncResult.user, provisioning };
    }

    publishControlPlaneEvent("admin.access-request.approved", result.user ? [result.user.id] : []);
    if (result.user) {
      publishNotificationEvent({
        type: "nodedc.access_request.approved",
        sourceService: "launcher",
        actor: { userId: req.nodedcSession.user?.id, email: req.nodedcSession.user?.email },
        subject: { type: "access_request", id: result.accessRequest.id },
        payload: {
          accessRequestId: result.accessRequest.id,
          userId: result.user.id,
          email: result.user.email,
          status: result.accessRequest.status,
        },
        idempotencyKey: `launcher:access-request:${result.accessRequest.id}:approved:${result.accessRequest.updatedAt}`,
        deliveries: [
          notificationDeliveryForUser(result.user, "hub", {
            ...buildAccessRequestHubNotification(result.accessRequest),
            entityType: "access_request",
            entityId: result.accessRequest.id,
          }),
        ].filter(Boolean),
      });
    }
    res.json(scopeAdminMutationResult(req, result));
  } catch (error) {
    sendAccessRequestApiError(res, error);
  }
}));

app.post("/api/admin/access-requests/:accessRequestId/reject", requireLauncherAdmin, requireRootLauncherAdmin, asyncRoute(async (req, res) => {
  try {
    const result = await controlPlaneStore.rejectAccessRequest(req.params.accessRequestId, req.body, req.nodedcSession.user);
    publishControlPlaneEvent("admin.access-request.rejected");
    const targetUser = result.data.users.find((user) => user.email.toLowerCase() === result.accessRequest.email.toLowerCase()) ?? null;
    if (targetUser) {
      publishNotificationEvent({
        type: "nodedc.access_request.rejected",
        sourceService: "launcher",
        actor: { userId: req.nodedcSession.user?.id, email: req.nodedcSession.user?.email },
        subject: { type: "access_request", id: result.accessRequest.id },
        payload: {
          accessRequestId: result.accessRequest.id,
          userId: targetUser.id,
          email: targetUser.email,
          status: result.accessRequest.status,
        },
        idempotencyKey: `launcher:access-request:${result.accessRequest.id}:rejected:${result.accessRequest.updatedAt}`,
        deliveries: [
          notificationDeliveryForUser(targetUser, "hub", {
            ...buildAccessRequestHubNotification(result.accessRequest),
            entityType: "access_request",
            entityId: result.accessRequest.id,
          }),
        ].filter(Boolean),
      });
    }
    res.json(scopeAdminMutationResult(req, result));
  } catch (error) {
    sendAccessRequestApiError(res, error);
  }
}));

app.post("/api/admin/tasker-invite-requests/:taskerInviteRequestId/approve", requireLauncherAdmin, requireRootLauncherAdmin, asyncRoute(async (req, res) => {
  const snapshot = controlPlaneStore.getSnapshot(req.nodedcSession.user);
  const taskerInviteRequest = snapshot.data.taskerInviteRequests.find((request) => request.id === req.params.taskerInviteRequestId);

  if (!taskerInviteRequest) {
    res.status(404).json({ error: "tasker_invite_request_not_found" });
    return;
  }

  const platformInviteResult = await controlPlaneStore.ensureTaskerInvitePlatformInvite(
    req.params.taskerInviteRequestId,
    req.nodedcSession.user
  );
  const platformInviteLink = buildPlatformInviteUrl(platformInviteResult.invite);
  const taskerResult = await requestTaskManagerInternalJson("/api/internal/nodedc/workspace-invite-requests/approve/", {
    body: {
      taskerInviteId: taskerInviteRequest.taskerInviteId,
      requestId: taskerInviteRequest.id,
      platformInviteLink,
    },
  });
  const result = await controlPlaneStore.approveTaskerInviteRequest(
    req.params.taskerInviteRequestId,
    {
      taskerInviteLink: taskerResult.invite?.taskerInviteLink ?? taskerResult.invite?.tasker_invite_link ?? taskerResult.invite?.inviteLink ?? null,
      platformInviteId: platformInviteResult.invite.id,
      platformInviteToken: platformInviteResult.invite.token,
      comment: req.body?.comment,
    },
    req.nodedcSession.user
  );

  publishControlPlaneEvent("admin.tasker-invite-request.approved", [result.taskerInviteRequest.inviterUserId]);
  publishNotificationEvent({
    type: "tasker.workspace_invite.approved",
    sourceService: "tasker",
    actor: { userId: req.nodedcSession.user?.id, email: req.nodedcSession.user?.email },
    subject: { type: "tasker_invite_request", id: result.taskerInviteRequest.id },
    payload: {
      requestId: result.taskerInviteRequest.id,
      workspaceSlug: result.taskerInviteRequest.workspaceSlug,
      workspaceName: result.taskerInviteRequest.workspaceName,
      inviteeEmail: result.taskerInviteRequest.inviteeEmail,
      status: result.taskerInviteRequest.status,
    },
    idempotencyKey: `launcher:tasker-invite:${result.taskerInviteRequest.id}:approved:${result.taskerInviteRequest.updatedAt}`,
    deliveries: notificationUsersForTaskerInvite(result.data, result.taskerInviteRequest).map((user) =>
      notificationDeliveryForUser(user, "hub", {
        ...buildTaskerInviteHubNotification(result.taskerInviteRequest),
        entityType: "tasker_invite_request",
        entityId: result.taskerInviteRequest.id,
      })
    ).filter(Boolean),
  });
  res.json(scopeAdminMutationResult(req, { ...result, tasker: taskerResult }));
}));

app.post("/api/admin/tasker-invite-requests/:taskerInviteRequestId/reject", requireLauncherAdmin, requireRootLauncherAdmin, asyncRoute(async (req, res) => {
  const snapshot = controlPlaneStore.getSnapshot(req.nodedcSession.user);
  const taskerInviteRequest = snapshot.data.taskerInviteRequests.find((request) => request.id === req.params.taskerInviteRequestId);

  if (!taskerInviteRequest) {
    res.status(404).json({ error: "tasker_invite_request_not_found" });
    return;
  }

  const taskerResult = await requestTaskManagerInternalJson("/api/internal/nodedc/workspace-invite-requests/reject/", {
    body: {
      taskerInviteId: taskerInviteRequest.taskerInviteId,
      requestId: taskerInviteRequest.id,
      comment: req.body?.comment,
    },
  });
  const result = await controlPlaneStore.rejectTaskerInviteRequest(req.params.taskerInviteRequestId, req.body, req.nodedcSession.user);

  publishControlPlaneEvent("admin.tasker-invite-request.rejected", [result.taskerInviteRequest.inviterUserId]);
  publishNotificationEvent({
    type: "tasker.workspace_invite.rejected",
    sourceService: "tasker",
    actor: { userId: req.nodedcSession.user?.id, email: req.nodedcSession.user?.email },
    subject: { type: "tasker_invite_request", id: result.taskerInviteRequest.id },
    payload: {
      requestId: result.taskerInviteRequest.id,
      workspaceSlug: result.taskerInviteRequest.workspaceSlug,
      workspaceName: result.taskerInviteRequest.workspaceName,
      inviteeEmail: result.taskerInviteRequest.inviteeEmail,
      status: result.taskerInviteRequest.status,
    },
    idempotencyKey: `launcher:tasker-invite:${result.taskerInviteRequest.id}:rejected:${result.taskerInviteRequest.updatedAt}`,
    deliveries: notificationUsersForTaskerInvite(result.data, result.taskerInviteRequest).map((user) =>
      notificationDeliveryForUser(user, "hub", {
        ...buildTaskerInviteHubNotification(result.taskerInviteRequest),
        entityType: "tasker_invite_request",
        entityId: result.taskerInviteRequest.id,
      })
    ).filter(Boolean),
  });
  res.json(scopeAdminMutationResult(req, { ...result, tasker: taskerResult }));
}));

app.post("/api/admin/engine-workflow-access-requests/:engineWorkflowAccessRequestId/approve", requireLauncherAdmin, requireRootLauncherAdmin, asyncRoute(async (req, res) => {
  const snapshot = controlPlaneStore.getSnapshot(req.nodedcSession.user);
  const request = snapshot.data.engineWorkflowAccessRequests.find(
    (candidate) => candidate.id === req.params.engineWorkflowAccessRequestId
  );

  if (!request) {
    res.status(404).json({ error: "engine_workflow_access_request_not_found" });
    return;
  }

  if (request.requestType === "service_role") {
    const result = await controlPlaneStore.approveEngineWorkflowAccessRequest(
      req.params.engineWorkflowAccessRequestId,
      {
        serviceRole: req.body?.serviceRole ?? req.body?.requestedServiceRole ?? req.body?.role,
        comment: req.body?.comment,
      },
      req.nodedcSession.user
    );
    const syncResult = await syncUsersToAuthentik(result.data, [result.targetUser.id], req.nodedcSession.user);

    publishControlPlaneEvent("admin.engine-role-access-request.approved", [
      result.engineWorkflowAccessRequest.requesterUserId,
      result.targetUser.id,
    ]);
    publishNotificationEvent({
      type: "engine.role_upgrade.approved",
      sourceService: "launcher",
      actor: { userId: req.nodedcSession.user?.id, email: req.nodedcSession.user?.email },
      subject: { type: "engine_role_access_request", id: result.engineWorkflowAccessRequest.id },
      payload: {
        requestId: result.engineWorkflowAccessRequest.id,
        targetUserId: result.targetUser.id,
        targetEmail: result.targetUser.email,
        serviceRole: result.engineWorkflowAccessRequest.requestedServiceRole,
      },
      idempotencyKey: `launcher:engine-role-request:${result.engineWorkflowAccessRequest.id}:approved:${result.engineWorkflowAccessRequest.updatedAt}`,
      deliveries: [
        notificationDeliveryForUser(result.targetUser, "hub", {
          ...buildEngineRoleHubNotification(result.engineWorkflowAccessRequest),
          entityType: "engine_role_access_request",
          entityId: result.engineWorkflowAccessRequest.id,
        }),
        notificationDeliveryForUser(result.targetUser, "engine", {
          ...buildEngineSurfaceNotification(result.engineWorkflowAccessRequest),
          entityType: "engine_role_access_request",
          entityId: result.engineWorkflowAccessRequest.id,
        }),
      ].filter(Boolean),
    });
    res.json(scopeAdminMutationResult(req, { ...result, data: syncResult.data }));
    return;
  }

  const engineResult = await requestEngineInternalJson(`/api/internal/workflows/${encodeURIComponent(request.workflowId)}/share/users`, {
    body: {
      email: request.targetEmail,
      role: request.role,
      requestId: request.id,
      approvedBy: {
        userId: req.nodedcSession.user?.id,
        email: req.nodedcSession.user?.email,
        name: req.nodedcSession.user?.name,
      },
    },
  });
  const result = await controlPlaneStore.approveEngineWorkflowAccessRequest(
    req.params.engineWorkflowAccessRequestId,
    { engineAppliedAt: engineResult.updatedAt ?? new Date().toISOString(), comment: req.body?.comment },
    req.nodedcSession.user
  );
  const syncResult = await syncUsersToAuthentik(result.data, [result.targetUser.id], req.nodedcSession.user);

  publishControlPlaneEvent("admin.engine-workflow-access-request.approved", [
    result.engineWorkflowAccessRequest.requesterUserId,
    result.targetUser.id,
  ]);
  publishNotificationEvent({
    type: "engine.workflow_share.approved",
    sourceService: "launcher",
    actor: { userId: req.nodedcSession.user?.id, email: req.nodedcSession.user?.email },
    subject: { type: "engine_workflow_access_request", id: result.engineWorkflowAccessRequest.id },
    payload: {
      requestId: result.engineWorkflowAccessRequest.id,
      workflowId: result.engineWorkflowAccessRequest.workflowId,
      workflowName: result.engineWorkflowAccessRequest.workflowName,
      targetUserId: result.targetUser.id,
      targetEmail: result.targetUser.email,
      role: result.engineWorkflowAccessRequest.role,
    },
    idempotencyKey: `launcher:engine-workflow-request:${result.engineWorkflowAccessRequest.id}:approved:${result.engineWorkflowAccessRequest.updatedAt}`,
    deliveries: [
      notificationDeliveryForUser(result.targetUser, "hub", {
        ...buildEngineWorkflowHubNotification(result.engineWorkflowAccessRequest),
        entityType: "engine_workflow_access_request",
        entityId: result.engineWorkflowAccessRequest.id,
      }),
      notificationDeliveryForUser(result.targetUser, "engine", {
        ...buildEngineSurfaceNotification(result.engineWorkflowAccessRequest),
        entityType: "engine_workflow_access_request",
        entityId: result.engineWorkflowAccessRequest.id,
      }),
    ].filter(Boolean),
  });
  res.json(scopeAdminMutationResult(req, { ...result, data: syncResult.data, engine: engineResult }));
}));

app.post("/api/admin/engine-workflow-access-requests/:engineWorkflowAccessRequestId/reject", requireLauncherAdmin, requireRootLauncherAdmin, asyncRoute(async (req, res) => {
  const result = await controlPlaneStore.rejectEngineWorkflowAccessRequest(
    req.params.engineWorkflowAccessRequestId,
    req.body,
    req.nodedcSession.user
  );

  publishControlPlaneEvent("admin.engine-workflow-access-request.rejected", [
    result.engineWorkflowAccessRequest.requesterUserId,
  ]);
  const targetUser = result.data.users.find((user) => user.id === result.engineWorkflowAccessRequest.targetUserId) ?? null;
  if (targetUser) {
    const isServiceRoleRequest = result.engineWorkflowAccessRequest.requestType === "service_role";
    publishNotificationEvent({
      type: isServiceRoleRequest ? "engine.role_upgrade.rejected" : "engine.workflow_share.rejected",
      sourceService: "launcher",
      actor: { userId: req.nodedcSession.user?.id, email: req.nodedcSession.user?.email },
      subject: {
        type: isServiceRoleRequest ? "engine_role_access_request" : "engine_workflow_access_request",
        id: result.engineWorkflowAccessRequest.id,
      },
      payload: {
        requestId: result.engineWorkflowAccessRequest.id,
        targetUserId: targetUser.id,
        targetEmail: targetUser.email,
        workflowId: result.engineWorkflowAccessRequest.workflowId,
        workflowName: result.engineWorkflowAccessRequest.workflowName,
      },
      idempotencyKey: `launcher:engine-request:${result.engineWorkflowAccessRequest.id}:rejected:${result.engineWorkflowAccessRequest.updatedAt}`,
      deliveries: [
        notificationDeliveryForUser(targetUser, "hub", {
          ...(isServiceRoleRequest
            ? buildEngineRoleHubNotification(result.engineWorkflowAccessRequest)
            : buildEngineWorkflowHubNotification(result.engineWorkflowAccessRequest)),
          entityType: isServiceRoleRequest ? "engine_role_access_request" : "engine_workflow_access_request",
          entityId: result.engineWorkflowAccessRequest.id,
        }),
        notificationDeliveryForUser(targetUser, "engine", {
          ...buildEngineSurfaceNotification(result.engineWorkflowAccessRequest),
          entityType: isServiceRoleRequest ? "engine_role_access_request" : "engine_workflow_access_request",
          entityId: result.engineWorkflowAccessRequest.id,
        }),
      ].filter(Boolean),
    });
  }
  res.json(scopeAdminMutationResult(req, result));
}));

app.post("/api/admin/groups", requireLauncherAdmin, asyncRoute(async (req, res) => {
  if (!assertAdminCanManageClient(req, res, req.body?.clientId)) {
    return;
  }

  if (!assertAdminCanManageProtectedGroupMembers(req, res, [], req.body?.memberIds)) {
    return;
  }

  const result = await controlPlaneStore.createGroup(req.body, req.nodedcSession.user);
  const syncResult = await syncUsersToAuthentik(result.data, result.group.memberIds, req.nodedcSession.user);
  publishControlPlaneEvent("admin.group.created", syncResult.userIds);
  res.status(201).json(scopeAdminMutationResult(req, { ...result, data: syncResult.data }));
}));

app.patch("/api/admin/groups/:groupId", requireLauncherAdmin, asyncRoute(async (req, res) => {
  const beforeSnapshot = controlPlaneStore.getSnapshot(req.nodedcSession.user);
  const group = beforeSnapshot.data.groups.find((candidate) => candidate.id === req.params.groupId);

  if (!group) {
    res.status(404).json({ error: "group_not_found" });
    return;
  }

  if (!assertAdminCanManageClient(req, res, group.clientId)) {
    return;
  }

  const previousMemberIds = group.memberIds;

  if (!assertAdminCanManageProtectedGroupMembers(req, res, previousMemberIds, req.body?.memberIds)) {
    return;
  }

  const result = await controlPlaneStore.updateGroup(req.params.groupId, req.body, req.nodedcSession.user);
  const syncResult = await syncUsersToAuthentik(
    result.data,
    [...previousMemberIds, ...result.group.memberIds],
    req.nodedcSession.user
  );
  publishControlPlaneEvent("admin.group.updated", syncResult.userIds);
  res.json(scopeAdminMutationResult(req, { ...result, data: syncResult.data }));
}));

app.delete("/api/admin/groups/:groupId", requireLauncherAdmin, asyncRoute(async (req, res) => {
  const snapshot = controlPlaneStore.getSnapshot(req.nodedcSession.user);
  const group = snapshot.data.groups.find((candidate) => candidate.id === req.params.groupId);

  if (!group) {
    res.status(404).json({ error: "group_not_found" });
    return;
  }

  if (!assertAdminCanManageClient(req, res, group.clientId)) {
    return;
  }

  if (!assertAdminCanManageProtectedGroupMembers(req, res, group.memberIds, [])) {
    return;
  }

  const result = await controlPlaneStore.deleteGroup(req.params.groupId, req.nodedcSession.user);
  const syncResult = await syncUsersToAuthentik(result.data, result.group.memberIds, req.nodedcSession.user);
  publishControlPlaneEvent("admin.group.deleted", syncResult.userIds);
  res.json(scopeAdminMutationResult(req, { ...result, data: syncResult.data }));
}));

app.post("/api/admin/services", requireLauncherAdmin, requireRootLauncherAdmin, asyncRoute(async (req, res) => {
  const result = await controlPlaneStore.createService(req.body, req.nodedcSession.user);
  publishControlPlaneEvent("admin.service.created");
  res.status(201).json(result);
}));

app.patch("/api/admin/services/reorder", requireLauncherAdmin, requireRootLauncherAdmin, asyncRoute(async (req, res) => {
  const result = await controlPlaneStore.reorderServices(req.body, req.nodedcSession.user);
  publishControlPlaneEvent("admin.service.reordered");
  res.json(result);
}));

app.patch("/api/admin/services/:serviceId", requireLauncherAdmin, requireRootLauncherAdmin, asyncRoute(async (req, res) => {
  const result = await controlPlaneStore.updateService(req.params.serviceId, req.body, req.nodedcSession.user);
  publishControlPlaneEvent("admin.service.updated");
  res.json(result);
}));

app.delete("/api/admin/services/:serviceId", requireLauncherAdmin, requireRootLauncherAdmin, asyncRoute(async (req, res) => {
  const result = await controlPlaneStore.deleteService(req.params.serviceId, req.nodedcSession.user);
  publishControlPlaneEvent("admin.service.deleted");
  res.json(result);
}));

app.post("/api/admin/access/grants", requireLauncherAdmin, asyncRoute(async (req, res) => {
  const snapshot = controlPlaneStore.getSnapshot(req.nodedcSession.user);

  if (req.body?.targetType === "user" && isProtectedLauncherUser(req.body?.targetId)) {
    res.status(403).json({
      ok: false,
      error: "protected_user_access_locked",
      message: "Доступ системного root-пользователя зафиксирован платформой.",
    });
    return;
  }

  if (!assertAdminCanManageGrantTarget(req, res, snapshot.data, req.body?.targetType, req.body?.targetId, { allowProtectedSelf: true })) {
    return;
  }

  const service = snapshot.data.services.find((candidate) => candidate.id === req.body?.serviceId);
  if (isTaskManagerService(service) && (req.body?.appRole === "admin" || req.body?.appRole === "owner")) {
    res.status(403).json({
      ok: false,
      error: "task_manager_admin_role_locked",
      message: "Admin-роль Operational Core назначается только системно.",
    });
    return;
  }

  const result = await controlPlaneStore.upsertGrant(req.body, req.nodedcSession.user);
  const syncResult = await syncUsersToAuthentik(
    result.data,
    resolveGrantTargetUserIds(result.data, result.grant.targetType, result.grant.targetId),
    req.nodedcSession.user
  );
  publishControlPlaneEvent("admin.access.grant.updated", syncResult.userIds);
  res.json(scopeAdminMutationResult(req, { ...result, data: syncResult.data }));
}));

app.post("/api/admin/access/exceptions", requireLauncherAdmin, asyncRoute(async (req, res) => {
  if (!assertAdminCanManageAccessForUser(req, res, req.body?.userId)) {
    return;
  }

  const result = await controlPlaneStore.upsertException(req.body, req.nodedcSession.user);
  const syncResult = await syncUsersToAuthentik(result.data, [result.exception.userId], req.nodedcSession.user);
  publishControlPlaneEvent("admin.access.exception.updated", syncResult.userIds);
  res.json(scopeAdminMutationResult(req, { ...result, data: syncResult.data }));
}));

app.post("/api/admin/access/user-service", requireLauncherAdmin, asyncRoute(async (req, res) => {
  if (!assertAdminCanManageAccessForUser(req, res, req.body?.userId)) {
    return;
  }

  const snapshot = controlPlaneStore.getSnapshot(req.nodedcSession.user);
  const service = snapshot.data.services.find((candidate) => candidate.id === req.body?.serviceId);

  if (isTaskManagerService(service) && (req.body?.value === "admin" || req.body?.value === "owner")) {
    res.status(403).json({
      ok: false,
      error: "task_manager_admin_role_locked",
      message: "Admin-роль Operational Core назначается только системно.",
    });
    return;
  }

  if (isPublicTaskManagerGuestServiceAssignment(snapshot.data, req.body)) {
    res.status(400).json({
      ok: false,
      error: "task_manager_public_guest_not_assignable",
      message: "Workspace Guest выдаётся только через настройки Operational Core.",
    });
    return;
  }

  const result = await controlPlaneStore.setUserServiceAccess(req.body, req.nodedcSession.user);
  const syncResult = await syncUsersToAuthentik(result.data, [req.body?.userId], req.nodedcSession.user);
  publishControlPlaneEvent(
    "admin.access.user-service.updated",
    result.affectedUserIds?.length ? result.affectedUserIds : syncResult.userIds
  );
  if (result.engineWorkflowAccessRequest) {
    const targetUser = syncResult.data.users.find((user) => user.id === req.body?.userId) ?? null;
    if (targetUser) {
      publishNotificationEvent({
        type: "engine.role.changed",
        sourceService: "launcher",
        actor: { userId: req.nodedcSession.user?.id, email: req.nodedcSession.user?.email },
        subject: { type: "engine_role_change", id: result.engineWorkflowAccessRequest.id },
        payload: {
          requestId: result.engineWorkflowAccessRequest.id,
          targetUserId: targetUser.id,
          targetEmail: targetUser.email,
          serviceRole: result.engineWorkflowAccessRequest.requestedServiceRole,
          currentServiceRole: result.engineWorkflowAccessRequest.currentServiceRole,
        },
        idempotencyKey: `launcher:engine-role-change:${result.engineWorkflowAccessRequest.id}`,
        deliveries: [
          notificationDeliveryForUser(targetUser, "hub", {
            ...buildEngineRoleHubNotification(result.engineWorkflowAccessRequest),
            entityType: "engine_role_change",
            entityId: result.engineWorkflowAccessRequest.id,
          }),
          notificationDeliveryForUser(targetUser, "engine", {
            ...buildEngineSurfaceNotification(result.engineWorkflowAccessRequest),
            entityType: "engine_role_change",
            entityId: result.engineWorkflowAccessRequest.id,
          }),
        ].filter(Boolean),
      });
    }
  }
  res.json(scopeAdminMutationResult(req, { ...result, data: syncResult.data }));
}));

app.post("/api/admin/access/service-modules", requireLauncherAdmin, asyncRoute(async (req, res) => {
  if (!assertAdminCanManageClient(req, res, req.body?.clientId) || !assertAdminCanManageAccessForUser(req, res, req.body?.userId)) {
    return;
  }

  const snapshot = controlPlaneStore.getSnapshot(req.nodedcSession.user);
  const service = snapshot.data.services.find((candidate) => candidate.id === req.body?.serviceId);

  if (!service) {
    res.status(404).json({ ok: false, error: "service_not_found" });
    return;
  }

  const result = await controlPlaneStore.setServiceModuleEntitlement(req.body, req.nodedcSession.user);
  publishControlPlaneEvent("admin.access.service-module.updated", [req.body?.userId]);
  res.json(scopeAdminMutationResult(req, result));
}));

app.post("/api/admin/sync/:syncId/retry", requireLauncherAdmin, requireRootLauncherAdmin, asyncRoute(async (req, res) => {
  const retryResult = await controlPlaneStore.retrySync(req.params.syncId, req.nodedcSession.user);
  const affectedUserIds = resolveSyncStatusAffectedUserIds(retryResult.data, retryResult.syncStatus);

  if (!authentikSyncClient.isConfigured()) {
    const result = await controlPlaneStore.updateSyncStatus(
      req.params.syncId,
      { state: "error", error: "Authentik API не настроен на сервере launcher." },
      req.nodedcSession.user
    );
    publishControlPlaneEvent("admin.sync.retry.failed");
    res.json(scopeAdminMutationResult(req, { ...result, ok: false, message: result.syncStatus.error }));
    return;
  }

  try {
    const syncResult = affectedUserIds.length
      ? await syncUsersToAuthentik(retryResult.data, affectedUserIds, req.nodedcSession.user)
      : { data: retryResult.data, userIds: [] };
    const result = await controlPlaneStore.updateSyncStatus(
      req.params.syncId,
      { state: "synced", error: null },
      req.nodedcSession.user
    );

    publishControlPlaneEvent("admin.sync.retry", syncResult.userIds);
    res.json(scopeAdminMutationResult(req, { ...result, ok: true, syncedUserIds: syncResult.userIds }));
  } catch (error) {
    const message = error instanceof Error ? error.message : "Не удалось выполнить синхронизацию Authentik";
    const result = await controlPlaneStore.updateSyncStatus(
      req.params.syncId,
      { state: "error", error: message },
      req.nodedcSession.user
    );

    publishControlPlaneEvent("admin.sync.retry.failed");
    res.json(scopeAdminMutationResult(req, { ...result, ok: false, message }));
  }
}));

app.get("/api/admin/sync/authentik/health", requireLauncherAdmin, requireRootLauncherAdmin, (req, res) => {
  const snapshot = controlPlaneStore.getSnapshot(req.nodedcSession.user);
  res.json(buildAuthentikSyncHealth(snapshot.data));
});

app.get("/api/admin/sync/authentik/plan", requireLauncherAdmin, requireRootLauncherAdmin, (_req, res) => {
  res.json(controlPlaneStore.buildAuthentikSyncPlan());
});

app.post("/api/storage/upload", requireSession, asyncRoute(async (req, res) => {
  const result = await saveUploadedFile(req.body);
  res.json(result);
}));

app.post("/api/storage/data", requireLauncherAdmin, requireRootLauncherAdmin, asyncRoute(async (req, res) => {
  await saveLauncherData(req.body);
  publishControlPlaneEvent("storage.data.updated");
  res.json({ ok: true });
}));

app.get("/storage/launcher-data.json", (_req, res) => {
  setNoStore(res);
  res.status(404).json({ error: "not_found" });
});

for (const uploadRoot of getReadableUploadRoots()) {
  app.use(
    "/storage/uploads",
    express.static(uploadRoot, {
      fallthrough: true,
      immutable: true,
      maxAge: "1y",
      setHeaders(res) {
        res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
      },
    })
  );
}

let fixFrontendStacktrace = () => {};

if (process.env.NODE_ENV === "production") {
  const distRoot = join(projectRoot, "dist");
  const indexHtmlPath = join(distRoot, "index.html");

  if (!existsSync(indexHtmlPath)) {
    throw new Error("Launcher production build is missing. Run npm run build before starting the server.");
  }

  app.use(express.static(distRoot, {
    index: false,
    immutable: true,
    maxAge: "1y",
    setHeaders(res, assetPath) {
      if (assetPath === indexHtmlPath) {
        res.setHeader("Cache-Control", noStoreCacheControl);
        return;
      }

      res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
    },
  }));
  app.use((req, res, next) => {
    if (req.method !== "GET" && req.method !== "HEAD") {
      next();
      return;
    }

    const accept = typeof req.headers.accept === "string" ? req.headers.accept : "";
    if (!accept.includes("text/html")) {
      next();
      return;
    }

    setNoStore(res);
    res.sendFile(indexHtmlPath);
  });
} else {
  const { createServer: createViteServer } = await import("vite");
  const vite = await createViteServer({
    root: projectRoot,
    appType: "spa",
    server: {
      middlewareMode: true,
      hmr: { server: httpServer },
    },
  });

  fixFrontendStacktrace = (error) => vite.ssrFixStacktrace(error);
  app.use(vite.middlewares);
}

app.use((error, _req, res, _next) => {
  fixFrontendStacktrace(error);
  const message = error instanceof Error ? error.message : "Unexpected server error";
  res.status(500).json({ error: message });
});

httpServer.listen(config.port, "0.0.0.0", () => {
  console.log(`NODE.DC launcher BFF listening on http://0.0.0.0:${config.port}`);
});

function readConfig() {
  const issuer = process.env.LAUNCHER_OIDC_ISSUER ?? "";
  const clientId = process.env.LAUNCHER_OIDC_CLIENT_ID ?? "";
  const clientSecret = process.env.LAUNCHER_OIDC_CLIENT_SECRET ?? "";
  const launcherDomain = process.env.LAUNCHER_DOMAIN ?? "localhost:5173";
  const appBaseUrl = process.env.LAUNCHER_BASE_URL ?? `http://${launcherDomain}`;

  return {
    port: Number(process.env.PORT ?? "5173"),
    issuer,
    clientId,
    clientSecret,
    redirectUri: process.env.LAUNCHER_OIDC_REDIRECT_URI ?? `${appBaseUrl}/auth/callback`,
    appBaseUrl,
    scope: process.env.LAUNCHER_OIDC_SCOPE ?? "openid email profile groups offline_access",
    cookieDomain: process.env.LAUNCHER_COOKIE_DOMAIN || undefined,
    cookieSecure: process.env.COOKIE_SECURE === "true",
    oidcConfigured: Boolean(issuer && clientId && clientSecret),
    authentikBaseUrl:
      process.env.NODEDC_AUTHENTIK_BASE_URL ??
      process.env.AUTHENTIK_BASE_URL ??
      (process.env.AUTH_DOMAIN ? `http://${process.env.AUTH_DOMAIN}` : ""),
    authentikApiToken:
      process.env.NODEDC_AUTHENTIK_SERVICE_TOKEN ??
      process.env.AUTHENTIK_SERVICE_TOKEN ??
      process.env.AUTHENTIK_BOOTSTRAP_TOKEN ??
      "",
    internalAccessToken:
      process.env.NODEDC_INTERNAL_ACCESS_TOKEN ??
      process.env.NODEDC_PLATFORM_SERVICE_TOKEN ??
      "",
    taskLogoutUrl:
      process.env.TASK_LOGOUT_URL ??
      `${(process.env.TASK_BASE_URL ?? `http://${process.env.TASK_DOMAIN ?? "task.local.nodedc"}`).replace(/\/$/, "")}/logout`,
    taskInternalLogoutUrl:
      process.env.TASK_INTERNAL_LOGOUT_URL ??
      `${(process.env.TASK_BASE_URL ?? `http://${process.env.TASK_DOMAIN ?? "task.local.nodedc"}`).replace(/\/$/, "")}/api/internal/nodedc/logout/`,
    engineBaseUrl:
      process.env.NODEDC_ENGINE_INTERNAL_URL ??
      process.env.NODEDC_ENGINE_BASE_URL ??
      process.env.ENGINE_BASE_URL ??
      "https://engine.nodedc.ru",
    notificationCoreBaseUrl:
      process.env.NODEDC_NOTIFICATION_CORE_URL ??
      process.env.NOTIFICATION_CORE_URL ??
      "",
  };
}

function ensureOidcConfigured() {
  if (!config.oidcConfigured) {
    throw new Error("Launcher OIDC is not configured. Set LAUNCHER_OIDC_ISSUER, LAUNCHER_OIDC_CLIENT_ID and LAUNCHER_OIDC_CLIENT_SECRET.");
  }
}

async function getOidcDiscovery() {
  if (discoveryCache && discoveryCache.expiresAt > Date.now()) {
    return discoveryCache.discovery;
  }

  const discoveryUrl = new URL("./.well-known/openid-configuration", ensureTrailingSlash(config.issuer));
  const response = await fetch(discoveryUrl, { headers: { Accept: "application/json" } });

  if (!response.ok) {
    throw new Error(`Unable to load OIDC discovery from ${discoveryUrl}: HTTP ${response.status}`);
  }

  const discovery = await response.json();
  discoveryCache = { discovery, expiresAt: Date.now() + 5 * 60 * 1000 };
  return discovery;
}

function buildOidcAuthorizationUrl(discovery, { state, prompt, pendingLogin }) {
  const codeChallenge = createHash("sha256").update(pendingLogin.codeVerifier).digest("base64url");
  const authorizationUrl = new URL(discovery.authorization_endpoint);

  authorizationUrl.searchParams.set("response_type", "code");
  authorizationUrl.searchParams.set("client_id", config.clientId);
  authorizationUrl.searchParams.set("redirect_uri", config.redirectUri);
  authorizationUrl.searchParams.set("scope", config.scope);
  authorizationUrl.searchParams.set("state", state);
  authorizationUrl.searchParams.set("nonce", pendingLogin.nonce);
  authorizationUrl.searchParams.set("code_challenge", codeChallenge);
  authorizationUrl.searchParams.set("code_challenge_method", "S256");

  if (prompt) {
    authorizationUrl.searchParams.set("prompt", prompt);
  }

  if (prompt === "login") {
    authorizationUrl.searchParams.set("max_age", "0");
  }

  return authorizationUrl;
}

async function exchangeCodeForTokens(discovery, code, codeVerifier) {
  const body = new URLSearchParams({
    grant_type: "authorization_code",
    code,
    redirect_uri: config.redirectUri,
    code_verifier: codeVerifier,
  });
  const response = await fetch(discovery.token_endpoint, {
    method: "POST",
    headers: {
      Authorization: `Basic ${Buffer.from(`${config.clientId}:${config.clientSecret}`).toString("base64")}`,
      "Content-Type": "application/x-www-form-urlencoded",
      Accept: "application/json",
    },
    body,
  });

  if (!response.ok) {
    const errorText = await response.text();
    throw new Error(`OIDC token exchange failed: HTTP ${response.status} ${errorText}`);
  }

  const tokenSet = await response.json();

  if (!tokenSet.id_token) {
    throw new Error("OIDC token response does not contain id_token");
  }

  return tokenSet;
}

async function verifyIdToken(discovery, idToken, nonce) {
  if (!jwksCache || jwksCache.uri !== discovery.jwks_uri) {
    jwksCache = {
      uri: discovery.jwks_uri,
      jwks: createRemoteJWKSet(new URL(discovery.jwks_uri)),
    };
  }

  const { payload } = await jwtVerify(idToken, jwksCache.jwks, {
    issuer: discovery.issuer ?? config.issuer,
    audience: config.clientId,
  });

  if (payload.nonce !== nonce) {
    throw new Error("OIDC nonce validation failed");
  }

  return payload;
}

function normalizeUser(claims) {
  const groups = normalizeGroups(claims.groups);
  const email = typeof claims.email === "string" ? claims.email : "";
  const avatarUrl = firstStringClaim(claims.picture, claims.avatar_url, claims.avatar);
  const name =
    typeof claims.name === "string" && claims.name
      ? claims.name
      : typeof claims.preferred_username === "string" && claims.preferred_username
        ? claims.preferred_username
        : email || String(claims.sub);

  return {
    sub: String(claims.sub),
    email,
    name,
    preferredUsername: typeof claims.preferred_username === "string" ? claims.preferred_username : null,
    avatarUrl,
    groups,
  };
}

function normalizeControlPlaneSessionUser(user, groups) {
  return {
    sub: String(user.authentikUserId || user.id),
    email: user.email,
    name: user.name,
    preferredUsername: user.email,
    avatarUrl: user.avatarUrl ?? null,
    groups,
  };
}

function createLauncherSession(res, user, tokenSet = {}) {
  const sessionId = randomBase64Url(48);
  const session = {
    id: sessionId,
    user,
    tokenSet: {
      idToken: tokenSet.idToken ?? null,
      accessToken: tokenSet.accessToken ?? null,
      expiresAt: tokenSet.expiresAt ?? null,
    },
    createdAt: Date.now(),
    expiresAt: Date.now() + sessionTtlMs,
  };

  pruneExpiredSessions();
  sessions.set(sessionId, session);
  res.cookie(sessionCookieName, sessionId, cookieOptions(sessionTtlMs));
  return session;
}

function firstStringClaim(...values) {
  for (const value of values) {
    if (typeof value === "string" && value) return value;
  }

  return null;
}

function sanitizePassword(value) {
  return typeof value === "string" && value.length >= 8 ? value : null;
}

function sanitizeNewPassword(value) {
  if (typeof value !== "string" || value.length < 8) {
    throw new Error("Новый пароль должен быть не короче 8 символов");
  }

  return value;
}

function sanitizeInviteRegistrationPayload(payload) {
  const email = typeof payload?.email === "string" ? payload.email.trim().toLowerCase() : "";
  const name = typeof payload?.name === "string" ? payload.name.trim() : "";

  if (!isValidInviteRegistrationEmail(email)) {
    throw new Error("Введите почту, на которую выписан инвайт");
  }

  if (!name) {
    throw new Error("Введите имя");
  }

  return {
    email,
    name: name.slice(0, 120),
    password: sanitizeNewPassword(payload?.password),
  };
}

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

function sendInviteApiError(res, error) {
  const message = error instanceof Error ? error.message : "Инвайт недоступен";
  const status =
    message.includes("не найден")
      ? 404
      : message.includes("другую почту") || message.includes("нет активного инвайта")
        ? 403
        : message.includes("истёк") || message.includes("отозван")
          ? 410
          : 400;

  res.status(status).json({ error: message });
}

function sendAccessRequestApiError(res, error) {
  const message = error instanceof Error ? error.message : "Заявка недоступна";
  const status =
    message.includes("Unknown access_request") || message.includes("не найден")
      ? 404
      : message.includes("нельзя")
        ? 409
        : message.includes("required") || message.includes("Введите")
          ? 400
          : 400;

  res.status(status).json({ error: message });
}

function sanitizeSelfProfilePatch(payload) {
  return {
    name: payload?.name,
    email: payload?.email,
    phone: payload?.phone,
    position: payload?.position,
    avatarUrl: payload?.avatarUrl,
  };
}

function sanitizeTaskerProfilePatch(payload) {
  const patch = {};
  const changedFields = Array.isArray(payload?.changedFields) ? payload.changedFields : [];
  const taskerFullName = joinTaskerProfileName(
    payload?.firstName ?? payload?.first_name,
    payload?.lastName ?? payload?.last_name
  );
  const taskerDisplayName = firstNonEmptyString(payload?.displayName, payload?.display_name, payload?.name);
  const name =
    hasChangedField(changedFields, ["display_name"]) || !hasChangedField(changedFields, ["first_name", "last_name"])
      ? firstNonEmptyString(taskerDisplayName, taskerFullName)
      : firstNonEmptyString(taskerFullName, taskerDisplayName);
  const hasAvatar =
    Object.hasOwn(payload ?? {}, "avatarUrl") ||
    Object.hasOwn(payload ?? {}, "avatar_url") ||
    Object.hasOwn(payload ?? {}, "avatar");

  if (name) {
    patch.name = name;
  }

  if (hasAvatar) {
    patch.avatarUrl = nullableProfileUrl(payload?.avatarUrl ?? payload?.avatar_url ?? payload?.avatar);
  }

  return patch;
}

function firstNonEmptyString(...values) {
  for (const value of values) {
    if (typeof value === "string" && value.trim()) return value.trim();
  }

  return null;
}

function joinTaskerProfileName(firstName, lastName) {
  return [firstName, lastName].filter((value) => typeof value === "string" && value.trim()).join(" ").trim();
}

function splitTaskerProfileName(name) {
  const trimmedName = typeof name === "string" ? name.trim() : "";
  const parts = trimmedName.split(/\s+/, 2);

  return {
    firstName: parts[0] ?? "",
    lastName: parts.length > 1 ? trimmedName.slice(parts[0].length).trim() : "",
  };
}

function hasChangedField(changedFields, fields) {
  return fields.some((field) => changedFields.includes(field));
}

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

function toProvisioningResponse(provisionedUser) {
  return {
    authentikUserId: provisionedUser.authentikUserId,
    email: provisionedUser.email,
    name: provisionedUser.name,
    groups: provisionedUser.groups,
    created: provisionedUser.created,
    temporaryPassword: provisionedUser.temporaryPassword,
  };
}

async function syncUsersToAuthentik(data, userIds, identity) {
  let latestData = data;
  const uniqueUserIds = [...new Set(userIds.filter((userId) => typeof userId === "string" && userId))];

  for (const userId of uniqueUserIds) {
    if (!latestData.users.some((user) => user.id === userId)) {
      continue;
    }

    const provisionedUser = await authentikSyncClient.provisionUser({ data: latestData, userId });
    const result = await controlPlaneStore.markUserAuthentikProvisioned(userId, provisionedUser, identity);
    latestData = result.data;
  }

  return { data: latestData, userIds: uniqueUserIds };
}

function buildAuthentikSyncHealth(data) {
  const rows = data.syncStatuses.filter((syncStatus) => syncStatus.target === "authentik");
  const errors = rows.filter((syncStatus) => syncStatus.state === "error");
  const pending = rows.filter((syncStatus) => syncStatus.state === "pending");
  const freshPending = pending.filter(isFreshPendingSyncStatus);
  const stalePending = pending.filter((syncStatus) => !isFreshPendingSyncStatus(syncStatus));
  const state = !authentikSyncClient.isConfigured()
    ? "not_configured"
    : errors.length > 0
      ? "error"
      : freshPending.length > 0
        ? "pending"
        : "ok";

  return {
    configured: authentikSyncClient.isConfigured(),
    state,
    checkedAt: new Date().toISOString(),
    counts: {
      total: rows.length,
      synced: rows.filter((syncStatus) => syncStatus.state === "synced").length,
      pending: pending.length,
      freshPending: freshPending.length,
      stalePending: stalePending.length,
      errors: errors.length,
      disabled: rows.filter((syncStatus) => syncStatus.state === "disabled").length,
    },
  };
}

function isFreshPendingSyncStatus(syncStatus) {
  if (syncStatus.state !== "pending") return false;
  const timestamp = Date.parse(syncStatus.updatedAt ?? syncStatus.lastSyncAt ?? "");
  if (!Number.isFinite(timestamp)) return false;
  return Date.now() - timestamp <= 5 * 60 * 1000;
}

function resolveSyncStatusAffectedUserIds(data, syncStatus) {
  if (!syncStatus || syncStatus.target !== "authentik") {
    return [];
  }

  if (syncStatus.objectType === "user") {
    return data.users.some((user) => user.id === syncStatus.objectId) ? [syncStatus.objectId] : [];
  }

  if (syncStatus.objectType === "group") {
    return data.groups.find((group) => group.id === syncStatus.objectId)?.memberIds ?? [];
  }

  if (syncStatus.objectType === "client") {
    return data.memberships.filter((membership) => membership.clientId === syncStatus.objectId).map((membership) => membership.userId);
  }

  if (syncStatus.objectType === "service") {
    return resolveServiceSyncUserIds(data, syncStatus.objectId);
  }

  if (syncStatus.objectType === "grant") {
    return resolveGrantSyncUserIds(data, syncStatus);
  }

  if (syncStatus.objectType === "invite") {
    const invite = data.invites.find((candidate) => candidate.id === syncStatus.objectId);
    return invite ? resolveUserIdsByEmail(data, invite.email) : resolveUserIdsByEmail(data, syncStatus.objectName);
  }

  return [];
}

function resolveServiceSyncUserIds(data, serviceId) {
  const userIds = new Set();

  for (const grant of data.grants.filter((candidate) => candidate.serviceId === serviceId && candidate.status === "active")) {
    for (const userId of resolveGrantTargetUserIds(data, grant.targetType, grant.targetId)) {
      userIds.add(userId);
    }
  }

  for (const exception of data.exceptions.filter((candidate) => candidate.serviceId === serviceId)) {
    userIds.add(exception.userId);
  }

  return [...userIds];
}

function resolveGrantSyncUserIds(data, syncStatus) {
  const existingGrant = data.grants.find((grant) => grant.id === syncStatus.objectId);

  if (existingGrant) {
    return resolveGrantTargetUserIds(data, existingGrant.targetType, existingGrant.targetId);
  }

  const [serviceId, userId] = String(syncStatus.objectId).split(":");
  if (serviceId && userId && data.users.some((user) => user.id === userId)) {
    return [userId];
  }

  return resolveUserIdsByEmail(data, syncStatus.objectName);
}

function resolveUserIdsByEmail(data, value) {
  const normalizedValue = String(value ?? "").toLowerCase();
  if (!normalizedValue) return [];

  return data.users
    .filter((user) => normalizedValue.includes(String(user.email ?? "").toLowerCase()))
    .map((user) => user.id);
}

function resolveGrantTargetUserIds(data, targetType, targetId) {
  if (targetType === "user") {
    return [targetId];
  }

  if (targetType === "group") {
    return data.groups.find((group) => group.id === targetId)?.memberIds ?? [];
  }

  if (targetType === "client") {
    return data.memberships.filter((membership) => membership.clientId === targetId).map((membership) => membership.userId);
  }

  return [];
}

function publishControlPlaneEvent(action, affectedUserIds = []) {
  publishRuntimeEvent({
    type: "control-plane.updated",
    action,
    affectedUserIds: [...new Set(affectedUserIds.filter((userId) => typeof userId === "string" && userId))],
    emittedAt: new Date().toISOString(),
  });
}

function publishRuntimeEvent(payload) {
  const message = `event: nodedc-runtime\ndata: ${JSON.stringify(payload)}\n\n`;

  for (const client of runtimeEventClients) {
    try {
      client.res.write(message);
    } catch {
      runtimeEventClients.delete(client);
    }
  }
}

function normalizeGroups(groupsClaim) {
  if (Array.isArray(groupsClaim)) {
    return [...new Set(groupsClaim.filter((group) => typeof group === "string"))];
  }

  if (typeof groupsClaim === "string" && groupsClaim) {
    return [groupsClaim];
  }

  return [];
}

function getRuntimeSessionContext(session) {
  const fallback = {
    user: session.user,
    groups: session.user.groups,
  };

  try {
    const snapshot = controlPlaneStore.getSnapshot(session.user);

    if (snapshot.actor.source !== "launcher") {
      return fallback;
    }

    const user = snapshot.data.users.find((candidate) => candidate.id === snapshot.actor.id);

    if (!user) {
      return fallback;
    }

    return buildRuntimeSessionContext(session, snapshot.data, user);
  } catch (error) {
    console.warn(error instanceof Error ? error.message : "Не удалось рассчитать runtime контекст Launcher");
    return fallback;
  }
}

function buildRuntimeSessionContext(session, data, user) {
  const groups = resolveRequiredGroups(data, user);

  return {
    groups,
    user: {
      ...session.user,
      email: user.email,
      name: user.name,
      avatarUrl: user.avatarUrl ?? session.user.avatarUrl,
      groups,
    },
  };
}

function getSessionAccessState(session) {
  try {
    const snapshot = controlPlaneStore.getSnapshot(session.user);
    const sessionEmail = typeof session.user?.email === "string" ? session.user.email.toLowerCase() : "";

    if (snapshot.actor.source !== "launcher") {
      const revokedAccount = snapshot.data.revokedAccounts.find((account) => account.email.toLowerCase() === sessionEmail);
      return {
        ok: false,
        status: 401,
        error: revokedAccount ? "account_revoked" : "account_not_found",
        message: revokedAccount
          ? "Аккаунт больше не активен. Запросите доступ, если хотите подключиться снова."
          : "Профиль пользователя не найден в Launcher control-plane.",
      };
    }

    const user = snapshot.data.users.find((candidate) => candidate.id === snapshot.actor.id);

    if (!user) {
      return {
        ok: false,
        status: 401,
        error: "account_not_found",
        message: "Профиль пользователя не найден в Launcher control-plane.",
      };
    }

    if (user.globalStatus === "blocked") {
      return {
        ok: false,
        status: 401,
        error: "account_blocked",
        message: "Аккаунт заблокирован. Обратитесь к администратору NODE.DC.",
      };
    }

    return {
      ok: true,
      runtimeContext: buildRuntimeSessionContext(session, snapshot.data, user),
    };
  } catch (error) {
    console.warn(error instanceof Error ? error.message : "Не удалось проверить Launcher-сессию");
    return {
      ok: false,
      status: 401,
      error: "session_invalid",
      message: "Не удалось проверить Launcher-сессию.",
    };
  }
}

function getAppsForUser(userGroups) {
  const groupSet = new Set(userGroups);
  const catalog = getAppCatalog();

  return catalog.map((app) => {
    const matchedGroups = app.requiredGroups.filter((group) => groupSet.has(group));
    const isSuperAdmin = groupSet.has("nodedc:superadmin");
    const isPublic = app.requiredGroups.length === 0;
    const hasAccess = isSuperAdmin || isPublic || matchedGroups.length > 0;

    return {
      ...app,
      matchedGroups: isSuperAdmin ? ["nodedc:superadmin", ...matchedGroups] : matchedGroups,
      hasAccess,
      accessReason: hasAccess ? "Доступ подтверждён" : "Нет доступа",
    };
  });
}

function getAppCatalog() {
  const launcherData = readLauncherData();
  const services = Array.isArray(launcherData?.services) ? launcherData.services : [];
  const serviceCatalog = services.map((service) => {
    const specialGroups = specialRequiredGroups(service);
    const requiredGroups = specialGroups.length
      ? specialGroups
      : service.authentikGroupName
        ? [service.authentikGroupName]
        : [];

    return {
      id: service.id,
      slug: service.slug,
      title: service.title,
      description: service.description,
      url: getServiceUrl(service),
      openUrl: getServiceUrl(service),
      launchTargetUrl: getServiceLaunchTargetUrl(service),
      status: service.status ?? "disabled",
      provider: "authentik",
      requiredGroups,
      media: {
        icon: service.iconUrl ?? null,
        coverImage: service.coverImageUrl ?? null,
        accentColor: service.accentColor ?? null,
      },
    };
  });

  return [
    {
      id: "launcher",
      slug: "launcher",
      title: "NODE.DC Launcher",
      description: "Единая точка входа в приложения NODE.DC.",
      url: config.appBaseUrl,
      openUrl: config.appBaseUrl,
      status: "active",
      provider: "authentik",
      requiredGroups: ["nodedc:launcher:admin", "nodedc:launcher:user"],
    },
    ...serviceCatalog.filter((service) => service.slug !== "launcher"),
  ];
}

const engineRequiredGroups = [
  "nodedc_admin",
  "nodedc_editor",
  "nodedc_viewer",
];

function isEngineServiceSlug(serviceSlug) {
  const slug = sanitizeServiceSlug(serviceSlug);
  return slug === "nodedc" || slug === "engine" || slug === "nodedc-engine";
}

function isBimViewerServiceSlug(serviceSlug) {
  const slug = sanitizeServiceSlug(serviceSlug);
  return slug === "bim-viewer" || slug === "beam-viewer" || slug === "bim";
}

function specialRequiredGroups(serviceOrSlug) {
  const slug = typeof serviceOrSlug === "string" ? serviceOrSlug : serviceOrSlug?.slug;
  const applicationSlug = typeof serviceOrSlug === "string" ? null : serviceOrSlug?.authentikApplicationSlug;
  const serviceId = typeof serviceOrSlug === "string" ? null : serviceOrSlug?.id;

  if (slug === "launcher") return ["nodedc:launcher:admin", "nodedc:launcher:user"];
  if (slug === "task-manager") return ["nodedc:taskmanager:admin", "nodedc:taskmanager:user"];
  if (slug === "nodedc" || slug === "engine" || slug === "nodedc-engine" || applicationSlug === "nodedc-engine" || serviceId === "service_nodedc") {
    return engineRequiredGroups;
  }

  return [];
}

function getServiceUrl(service) {
  if (service.slug === "task-manager" || isEngineServiceSlug(service.slug) || isBimViewerServiceSlug(service.slug)) {
    return `/api/services/${encodeURIComponent(service.slug)}/launch`;
  }

  return service.launchUrl || service.url || "#";
}

function getServiceLaunchTargetUrl(service) {
  if (isEngineServiceSlug(service?.slug)) {
    return service.launchUrl || service.url || getEnginePublicBaseUrl();
  }

  return service?.launchUrl || service?.url || "#";
}

function getTaskBaseUrl() {
  const taskBaseUrl = process.env.TASK_BASE_URL ?? `http://${process.env.TASK_DOMAIN ?? "task.local.nodedc"}`;
  return taskBaseUrl.replace(/\/$/, "");
}

function getBimViewerBaseUrl(app = null) {
  return String(
    process.env.NODEDC_BIM_VIEWER_PUBLIC_URL ||
      process.env.BIM_VIEWER_BASE_URL ||
      app?.launchTargetUrl ||
      app?.openUrl ||
      app?.url ||
      "https://bim.nodedc.tech"
  ).replace(/\/$/, "");
}

function findServiceCatalogAppBySlug(serviceSlug) {
  const slug = sanitizeServiceSlug(serviceSlug);

  try {
    const snapshot = controlPlaneStore.getSnapshot({ name: "NODE.DC optional service fallback" });
    return snapshot.data.services.find((candidate) => sanitizeServiceSlug(candidate.slug) === slug) || null;
  } catch {
    return null;
  }
}

function buildOptionalServiceFallbackUrl(serviceSlug, app, fallbackPath) {
  const path = sanitizeReturnTo(fallbackPath || "/");
  const serviceApp = app || findServiceCatalogAppBySlug(serviceSlug);
  let baseUrl = config.appBaseUrl;

  if (isBimViewerServiceSlug(serviceSlug)) {
    baseUrl = getBimViewerBaseUrl(serviceApp);
  } else if (isEngineServiceSlug(serviceSlug)) {
    baseUrl = getEngineLaunchTargetUrl(serviceApp);
  } else if (serviceSlug === "task-manager") {
    baseUrl = getTaskBaseUrl();
  } else if (serviceApp?.openUrl || serviceApp?.url) {
    baseUrl = serviceApp.openUrl || serviceApp.url;
  }

  try {
    return new URL(path, String(baseUrl || config.appBaseUrl).replace(/\/$/, "")).toString();
  } catch {
    return new URL("/", config.appBaseUrl).toString();
  }
}

function redirectToServiceLaunchTarget(res, serviceSlug, runtimeContext, app, returnTo = "/") {
  if (isEngineServiceSlug(serviceSlug)) {
    const handoffToken = createServiceHandoff("nodedc", runtimeContext.user);
    const targetUrl = new URL("/auth/nodedc/handoff/", getEngineLaunchTargetUrl(app));
    const nextPath = sanitizeReturnTo(returnTo);

    targetUrl.searchParams.set("token", handoffToken);

    if (nextPath && nextPath !== "/") {
      targetUrl.searchParams.set("next_path", nextPath);
    }

    res.redirect(targetUrl.toString());
    return;
  }

  if (isBimViewerServiceSlug(serviceSlug)) {
    const handoffToken = createServiceHandoff(serviceSlug, runtimeContext.user);
    const targetUrl = new URL("/auth/nodedc/handoff", getBimViewerBaseUrl(app));
    const nextPath = sanitizeReturnTo(returnTo);

    targetUrl.searchParams.set("token", handoffToken);

    if (nextPath && nextPath !== "/") {
      targetUrl.searchParams.set("next_path", nextPath);
    }

    res.redirect(targetUrl.toString());
    return;
  }

  if (serviceSlug !== "task-manager") {
    res.redirect(app.openUrl || app.url || "/");
    return;
  }

  const handoffToken = createServiceHandoff(serviceSlug, runtimeContext.user);
  const targetUrl = new URL("/auth/nodedc/handoff/", getTaskBaseUrl());
  const nextPath = sanitizeReturnTo(returnTo);

  targetUrl.searchParams.set("token", handoffToken);

  if (nextPath && nextPath !== "/") {
    targetUrl.searchParams.set("next_path", nextPath);
  }

  res.redirect(targetUrl.toString());
}

function getEnginePublicBaseUrl() {
  return String(
    process.env.NODEDC_ENGINE_PUBLIC_URL ||
      process.env.NODEDC_ENGINE_BASE_URL ||
      process.env.ENGINE_BASE_URL ||
      "https://engine.nodedc.ru"
  ).replace(/\/$/, "");
}

function getEngineBaseUrl() {
  return String(config.engineBaseUrl || "https://engine.nodedc.ru").replace(/\/$/, "");
}

function getEngineLaunchTargetUrl(service) {
  return String(service?.launchTargetUrl || getEnginePublicBaseUrl()).replace(/\/$/, "");
}

function buildAssistantActionProxyCommand(body, context) {
  const source = isPlainRecord(body) ? body : {};
  const sourceInput = isPlainRecord(source.input) ? source.input : source;
  const input = stripClientAssistantContext(sourceInput);
  const ownerContext = resolveAssistantActionOwnerContext(context, input);
  const phase = normalizeAssistantActionPhase(source.phase || source.mode);
  const confirmationToken = normalizeOptionalText(source.confirmationToken || sourceInput.confirmationToken || source.confirmation?.token);

  return {
    phase,
    input: {
      ...input,
      ...ownerContext,
    },
    ...(confirmationToken ? { confirmationToken } : {}),
  };
}

function stripClientAssistantContext(input) {
  const result = {};
  const blockedKeys = new Set([
    "actorUserId",
    "actorEmail",
    "actorSubject",
    "groups",
    "launcherGlobalRole",
    "launcherUserStatus",
    "globalStatus",
    "membershipRole",
    "membershipStatus",
    "assistantRole",
    "coreAssistantRole",
    "isRoot",
    "isSuperAdmin",
    "removesOwnAccess",
    "hasAlternativeAdminPath",
  ]);

  for (const [key, value] of Object.entries(isPlainRecord(input) ? input : {})) {
    if (blockedKeys.has(key)) continue;
    result[key] = value;
  }

  return result;
}

function resolveAssistantActionOwnerContext({ actor, data, groups }, input) {
  const groupSet = new Set(groups || []);
  const isRoot = groupSet.has("nodedc:superadmin") || groupSet.has("nodedc:launcher:admin");
  const actorMembership = isRoot ? null : resolveAssistantActionActorMembership(data, actor, input);
  const assistantRole = isRoot ? "admin" : actorMembership?.coreAssistantRole || "member";
  const membershipRole = isRoot ? "client_owner" : actorMembership?.role || "member";
  const membershipStatus = isRoot ? "active" : actorMembership?.status || "disabled";
  const selfTarget = isAssistantActionSelfTarget(data, actor, input);
  const removesOwnAccess = selfTarget && assistantActionRemovesOwnAccess(input);

  return {
    assistantRole,
    launcherGlobalRole: isRoot ? "root_admin" : membershipRole,
    launcherUserStatus: actor.globalStatus || "active",
    membershipRole,
    membershipStatus,
    removesOwnAccess,
    hasAlternativeAdminPath: removesOwnAccess ? hasAlternativeAssistantAdminPath(data, actor, groups, input) : false,
  };
}

function resolveAssistantActionActorMembership(data, actor, input) {
  const activeMemberships = data.memberships.filter((membership) => membership.userId === actor.id && membership.status === "active");
  const targetClientId = resolveAssistantActionTargetClientId(data, input);

  if (targetClientId) {
    const exact = activeMemberships.find((membership) => membership.clientId === targetClientId);
    if (exact) return exact;
  }

  return activeMemberships
    .slice()
    .sort((left, right) => assistantMembershipWeight(right) - assistantMembershipWeight(left))[0] || null;
}

function resolveAssistantActionTargetClientId(data, input) {
  const explicitClientId = normalizeOptionalText(input.clientId);
  if (explicitClientId) return explicitClientId;

  const targetMembershipId = normalizeOptionalText(input.membershipId);
  if (targetMembershipId) {
    return data.memberships.find((membership) => membership.id === targetMembershipId)?.clientId || null;
  }

  const targetUserId = normalizeOptionalText(input.targetUserId);
  if (targetUserId) {
    return data.memberships.find((membership) => membership.userId === targetUserId && membership.status === "active")?.clientId || null;
  }

  return null;
}

function isAssistantActionSelfTarget(data, actor, input) {
  const targetUserId = normalizeOptionalText(input.targetUserId);
  if (targetUserId && targetUserId === actor.id) return true;

  const targetMembershipId = normalizeOptionalText(input.membershipId);
  if (!targetMembershipId) return false;

  return data.memberships.some((membership) => membership.id === targetMembershipId && membership.userId === actor.id);
}

function assistantActionRemovesOwnAccess(input) {
  const actionId = normalizeOptionalText(input.actionId);
  if (actionId === "hub.user.block" || actionId === "hub.membership.disable") return true;

  if (actionId === "hub.assistant_access.change_role") {
    const targetAssistantRole = normalizeOptionalText(input.targetAssistantRole);
    return targetAssistantRole === "blocked" || targetAssistantRole === "member";
  }

  if (actionId === "hub.membership.change_role") {
    return normalizeOptionalText(input.targetRole) === "member";
  }

  return false;
}

function hasAlternativeAssistantAdminPath(data, actor, groups, input) {
  const groupSet = new Set(groups || []);
  if (groupSet.has("nodedc:superadmin") || groupSet.has("nodedc:launcher:admin")) {
    return normalizeOptionalText(input.actionId) !== "hub.user.block";
  }

  const targetMembershipId = normalizeOptionalText(input.membershipId);
  return data.memberships.some(
    (membership) =>
      membership.userId === actor.id &&
      membership.id !== targetMembershipId &&
      membership.status === "active" &&
      membership.coreAssistantRole === "admin" &&
      clientAdminMembershipRoles.has(membership.role)
  );
}

function assistantMembershipWeight(membership) {
  const assistantWeight = membership.coreAssistantRole === "admin" ? 100 : membership.coreAssistantRole === "blocked" ? -100 : 0;
  const roleWeight = membership.role === "client_owner" ? 30 : membership.role === "client_admin" ? 20 : 10;
  return assistantWeight + roleWeight;
}

function normalizeAssistantActionPhase(value) {
  const phase = String(value || "preview").trim().toLowerCase().replace(/_/g, "-");
  return ["preview", "dry-run", "execute"].includes(phase) ? phase : "preview";
}

function getAiWorkspaceAssistantBaseUrl() {
  return String(
    process.env.NDC_AI_WORKSPACE_ASSISTANT_INTERNAL_URL ||
      process.env.AI_WORKSPACE_ASSISTANT_INTERNAL_URL ||
      process.env.NDC_AI_WORKSPACE_ASSISTANT_URL ||
      process.env.AI_WORKSPACE_ASSISTANT_URL ||
      "http://localhost:18082"
  ).replace(/\/$/, "");
}

async function requestAiWorkspaceAssistantJson(pathname, init = {}) {
  if (!config.internalAccessToken) {
    throw new Error("NODE.DC internal access token is not configured");
  }

  const targetUrl = new URL(pathname, `${getAiWorkspaceAssistantBaseUrl()}/`);
  const response = await fetch(targetUrl, {
    method: "POST",
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
      Authorization: `Bearer ${config.internalAccessToken}`,
      "X-NODEDC-User-Id": init.actor?.id || "",
      "X-NODEDC-User-Email": init.actor?.email || "",
      "X-NODEDC-User-Role": init.role || "",
      "X-NODEDC-User-Groups": Array.isArray(init.groups) ? init.groups.join(",") : "",
      ...(init.headers ?? {}),
    },
    body: JSON.stringify(init.body ?? {}),
  });
  const text = await response.text();
  const payload = text ? parseJsonResponse(text, targetUrl.toString()) : {};

  if (!response.ok) {
    const error = typeof payload?.error === "string" ? payload.error : `AI Workspace Assistant API failed: ${response.status}`;
    throw new Error(error);
  }

  return payload;
}

async function requestTaskManagerInternalJson(pathname, init = {}) {
  if (!config.internalAccessToken) {
    throw new Error("NODE.DC internal access token is not configured");
  }

  const targetUrl = new URL(pathname, `${getTaskBaseUrl()}/`);
  const hasBody = typeof init.body === "object" && init.body !== null;
  const response = await fetch(targetUrl, {
    method: init.method ?? (hasBody ? "POST" : "GET"),
    headers: {
      Accept: "application/json",
      Authorization: `Bearer ${config.internalAccessToken}`,
      ...(hasBody ? { "Content-Type": "application/json" } : {}),
      ...(init.headers ?? {}),
    },
    body: hasBody ? JSON.stringify(init.body) : undefined,
  });
  const text = await response.text();
  const payload = text ? parseJsonResponse(text, targetUrl.toString()) : {};

  if (!response.ok) {
    const error = typeof payload?.error === "string" ? payload.error : `Task Manager internal API failed: ${response.status}`;
    throw new Error(error);
  }

  return payload;
}

async function requestEngineInternalJson(pathname, init = {}) {
  if (!config.internalAccessToken) {
    throw new Error("NODE.DC internal access token is not configured");
  }

  const targetUrl = new URL(pathname, `${getEngineBaseUrl()}/`);
  const hasBody = typeof init.body === "object" && init.body !== null;
  const response = await fetch(targetUrl, {
    method: init.method ?? (hasBody ? "POST" : "GET"),
    headers: {
      Accept: "application/json",
      Authorization: `Bearer ${config.internalAccessToken}`,
      "X-Authentik-Groups": "nodedc_admin",
      "X-Authentik-Email": "launcher-internal@nodedc.ru",
      "X-Authentik-Username": "launcher-internal@nodedc.ru",
      ...(hasBody ? { "Content-Type": "application/json" } : {}),
      ...(init.headers ?? {}),
    },
    body: hasBody ? JSON.stringify(init.body) : undefined,
  });
  const text = await response.text();
  const payload = text ? parseJsonResponse(text, targetUrl.toString()) : {};

  if (!response.ok) {
    const error = typeof payload?.error === "string" ? payload.error : `Engine internal API failed: ${response.status}`;
    throw new Error(error);
  }

  return payload;
}

async function syncTaskManagerUserProfile(user) {
  if (!user?.email || !config.internalAccessToken) {
    return null;
  }

  const taskerNameParts = splitTaskerProfileName(user.name);

  try {
    return await requestTaskManagerInternalJson("/api/internal/nodedc/users/profile-sync/", {
      method: "POST",
      body: {
        email: user.email,
        subject: user.authentikUserId ?? undefined,
        name: user.name,
        displayName: user.name,
        firstName: taskerNameParts.firstName,
        lastName: taskerNameParts.lastName,
        avatarUrl: resolveUserAvatarPublicUrl(user),
      },
    });
  } catch (error) {
    console.warn(
      error instanceof Error
        ? `Task Manager profile sync failed: ${error.message}`
        : "Task Manager profile sync failed"
    );
    return null;
  }
}

async function cleanupTaskManagerUserAccess(user, options = {}) {
  if (!user?.email || !config.internalAccessToken) {
    return null;
  }

  try {
    return await requestTaskManagerInternalJson("/api/internal/nodedc/logout/", {
      method: "POST",
      body: {
        source: normalizeOptionalText(options.source) ?? "launcher-user-access-revoked",
        subject: user.authentikUserId ?? undefined,
        email: user.email,
        revokeIdentityLinks: options.revokeIdentityLinks === true,
        revokeTaskerAccess: options.revokeTaskerAccess !== false,
      },
    });
  } catch (error) {
    const message = error instanceof Error ? error.message : "Task Manager user cleanup failed";
    console.warn(`Task Manager user cleanup failed: ${message}`);
    return { ok: false, error: message };
  }
}

function parseJsonResponse(text, url) {
  try {
    return JSON.parse(text);
  } catch {
    throw new Error(`Task Manager internal API returned non-JSON response: ${url}`);
  }
}

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

function isPlainRecord(value) {
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
}

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

function isPublicTaskManagerGuestServiceAssignment(data, payload) {
  if (payload?.value !== "viewer") return false;

  const service = data.services.find((candidate) => candidate.id === payload?.serviceId);
  if (!service || !isTaskManagerService(service)) return false;

  return data.memberships.some(
    (membership) =>
      membership.userId === payload?.userId &&
      membership.clientId === publicPoolClientId &&
      membership.status === "active"
  );
}

function isTaskManagerService(service) {
  return service?.slug === "task-manager" || service?.authentikApplicationSlug === "task-manager";
}

function resolveTaskManagerRoleForMembership(role) {
  return role === "client_owner" || role === "client_admin" ? "admin" : "member";
}

function normalizeTaskManagerWorkspaceManagedBy(value) {
  return value === "tasker" ? "tasker" : "launcher";
}

function getClientTaskManagerWorkspaces(client) {
  const taskManager = client?.integrations?.taskManager;
  const workspaces = Array.isArray(taskManager?.workspaces) ? taskManager.workspaces : [];
  const legacySlug = normalizeOptionalText(taskManager?.workspaceSlug);

  if (!legacySlug || workspaces.some((workspace) => normalizeOptionalText(workspace?.slug) === legacySlug)) {
    return workspaces;
  }

  return [
    ...workspaces,
    {
      slug: legacySlug,
      name: normalizeOptionalText(taskManager?.workspaceName),
      isPrimary: true,
      managedBy: "launcher",
    },
  ];
}

function resolveTaskManagerWorkspaceBinding(client, workspaceSlug) {
  const normalizedWorkspaceSlug = normalizeOptionalText(workspaceSlug);
  if (!normalizedWorkspaceSlug) return null;

  return (
    getClientTaskManagerWorkspaces(client).find((workspace) => normalizeOptionalText(workspace?.slug) === normalizedWorkspaceSlug) ?? null
  );
}

function resolveTaskManagerWorkspaceManagedByForClient(client, workspaceSlug) {
  return normalizeTaskManagerWorkspaceManagedBy(resolveTaskManagerWorkspaceBinding(client, workspaceSlug)?.managedBy);
}

function resolveTaskManagerWorkspaceAssignments(data, user) {
  if (!user?.id) return [];

  const bySlug = new Map();
  for (const membership of data.taskManagerMemberships ?? []) {
    if (membership.userId !== user.id) continue;
    const workspaceSlug = normalizeOptionalText(membership.workspaceSlug);
    if (!workspaceSlug) continue;

    const client = data.clients.find((candidate) => candidate.id === membership.clientId);
    const managedBy = normalizeTaskManagerWorkspaceManagedBy(
      membership.managedBy ?? resolveTaskManagerWorkspaceBinding(client, workspaceSlug)?.managedBy
    );
    const current = bySlug.get(workspaceSlug);
    if (current && current.managedBy === "launcher") continue;

    bySlug.set(workspaceSlug, {
      slug: workspaceSlug,
      name: normalizeOptionalText(membership.workspaceName ?? resolveTaskManagerWorkspaceBinding(client, workspaceSlug)?.name),
      managedBy,
      clientId: client?.id ?? membership.clientId ?? null,
      clientName: client?.name ?? null,
      role: normalizeTaskManagerRole(membership.role) ?? "member",
    });
  }

  return [...bySlug.values()];
}

function createServiceHandoff(serviceSlug, user) {
  pruneExpiredServiceHandoffs();

  const token = randomBase64Url(48);
  serviceHandoffs.set(token, {
    serviceSlug,
    user: {
      sub: user.sub,
      email: user.email,
      name: user.name,
      preferredUsername: user.preferredUsername ?? user.email,
      avatarUrl: user.avatarUrl ?? null,
      groups: user.groups ?? [],
    },
    expiresAt: Date.now() + serviceHandoffTtlMs,
  });

  return token;
}

function consumeServiceHandoff(token, serviceSlug) {
  pruneExpiredServiceHandoffs();

  if (!token) {
    return null;
  }

  const handoff = serviceHandoffs.get(token);
  serviceHandoffs.delete(token);

  if (!handoff || handoff.expiresAt < Date.now() || handoff.serviceSlug !== serviceSlug) {
    return null;
  }

  return handoff;
}

function pruneExpiredServiceHandoffs() {
  const now = Date.now();

  for (const [token, handoff] of serviceHandoffs.entries()) {
    if (!handoff || handoff.expiresAt < now) {
      serviceHandoffs.delete(token);
    }
  }
}

function resolveTaskManagerWorkspacePolicy(data, groups, hasTaskManagerAccess, user, workspaceSlug = null, serviceModules = {}) {
  const mode = data.settings?.taskManager?.workspaceCreationPolicy ?? "any_authorized_user";
  const groupSet = new Set(groups);
  const isSuperAdmin = groupSet.has("nodedc:superadmin");
  const isTaskManagerAdmin = groupSet.has("nodedc:taskmanager:admin");
  const workspaces = resolveTaskManagerWorkspaceAssignments(data, user);
  const hasLauncherManagedWorkspace = workspaces.some((workspace) => workspace.managedBy === "launcher");
  const isPublicPoolUser = data.memberships.some(
    (membership) => membership.userId === user?.id && membership.clientId === publicPoolClientId && membership.status === "active"
  );
  const defaultManagedBy = hasLauncherManagedWorkspace && !isSuperAdmin ? "launcher" : "tasker";
  const defaultInviteApproval = hasLauncherManagedWorkspace && !isSuperAdmin ? "launcher" : isPublicPoolUser ? "nodedc" : "tasker";
  const workspaceAssignment =
    typeof workspaceSlug === "string" && workspaceSlug.trim()
      ? workspaces.find((workspace) => workspace.slug === workspaceSlug.trim())
      : null;

  if (!hasTaskManagerAccess) {
    return {
      mode,
      managedBy: defaultManagedBy,
      defaultManagedBy,
      inviteApproval: "disabled",
      defaultInviteApproval,
      workspaces,
      serviceModules,
      canCreateWorkspace: false,
      reason: "Нет доступа к Operational Core.",
    };
  }

  if (mode === "disabled") {
    return {
      mode,
      managedBy: defaultManagedBy,
      defaultManagedBy,
      inviteApproval: "disabled",
      defaultInviteApproval,
      workspaces,
      serviceModules,
      canCreateWorkspace: false,
      reason: "Создание рабочих пространств отключено на уровне платформы.",
    };
  }

  if (hasLauncherManagedWorkspace && !isSuperAdmin && !isTaskManagerAdmin) {
    if (workspaceAssignment?.managedBy === "launcher") {
      return {
        mode,
        managedBy: "launcher",
        defaultManagedBy: "launcher",
        inviteApproval: "launcher",
        defaultInviteApproval: "launcher",
        workspaces,
        serviceModules,
        canCreateWorkspace: false,
        reason: "Рабочие пространства этого пользователя управляются через Launcher.",
      };
    }

    if (workspaceSlug) {
      return {
        mode,
        managedBy: "tasker",
        defaultManagedBy: "launcher",
        inviteApproval: defaultInviteApproval,
        defaultInviteApproval,
        workspaces,
        serviceModules,
        canCreateWorkspace: false,
        reason: "Self-service workspace работает через Tasker, approve инвайтов выполняет Launcher.",
      };
    }

    return {
      mode,
      managedBy: "launcher",
      defaultManagedBy: "launcher",
      inviteApproval: "launcher",
      defaultInviteApproval: "launcher",
      workspaces,
      serviceModules,
      canCreateWorkspace: false,
      reason: "Рабочие пространства этого пользователя управляются через Launcher.",
    };
  }

  if (mode === "task_admins_only" && !isSuperAdmin && !isTaskManagerAdmin) {
    return {
      mode,
      managedBy: defaultManagedBy,
      defaultManagedBy,
      inviteApproval: defaultInviteApproval,
      defaultInviteApproval,
      workspaces,
      serviceModules,
      canCreateWorkspace: false,
      reason: "Создание рабочих пространств доступно только администраторам Operational Core.",
    };
  }

  return {
    mode,
    managedBy: "tasker",
    defaultManagedBy: "tasker",
    inviteApproval: defaultInviteApproval,
    defaultInviteApproval,
    workspaces,
    serviceModules,
    canCreateWorkspace: true,
    reason: "Создание рабочих пространств разрешено платформенной policy.",
  };
}

function resolveTaskManagerWorkspaceServiceModules(data, user, serviceSlug, workspaceSlug) {
  const normalizedWorkspaceSlug = normalizeOptionalText(workspaceSlug);

  if (!normalizedWorkspaceSlug) {
    return {};
  }

  const workspaces = resolveTaskManagerWorkspaceAssignments(data, user);
  const workspaceAssignment = workspaces.find((workspace) => workspace.slug === normalizedWorkspaceSlug);
  const boundClientId = resolveTaskManagerWorkspaceClientId(data, normalizedWorkspaceSlug);
  const clientId =
    workspaceAssignment?.clientId ??
    (boundClientId ? null : isPublicPoolUser(data, user) ? publicPoolClientId : null);

  return resolveUserServiceModules(data, user, serviceSlug, clientId);
}

function resolveTaskManagerWorkspaceClientId(data, workspaceSlug) {
  const normalizedWorkspaceSlug = normalizeOptionalText(workspaceSlug);
  if (!normalizedWorkspaceSlug) return null;

  const client = data.clients.find((candidate) => resolveTaskManagerWorkspaceBinding(candidate, normalizedWorkspaceSlug));
  return client?.id ?? null;
}

function isPublicPoolUser(data, user) {
  return data.memberships.some(
    (membership) => membership.userId === user?.id && membership.clientId === publicPoolClientId && membership.status === "active"
  );
}

function resolveUserServiceModules(data, user, serviceSlug, clientId) {
  if (!user?.id) return {};
  const service = data.services.find(
    (candidate) => candidate.slug === serviceSlug || candidate.authentikApplicationSlug === serviceSlug
  );
  if (!service?.id) return {};
  if (hasSystemServiceModuleEntitlement(user, service, "codex_agents")) {
    return { codex_agents: true };
  }

  return Object.fromEntries(
    (data.serviceModuleEntitlements ?? [])
      .filter(
        (entitlement) =>
          entitlement.userId === user.id &&
          entitlement.serviceId === service.id &&
          entitlement.enabled
      )
      .map((entitlement) => [entitlement.moduleId, true])
  );
}

function hasSystemServiceModuleEntitlement(user, service, moduleId) {
  return user?.id === "user_root" && moduleId === "codex_agents" && service?.slug === "task-manager";
}

function getFrontchannelLogoutUrls() {
  const urls = [config.taskLogoutUrl];
  const launcherData = readLauncherData();
  const services = Array.isArray(launcherData?.services) ? launcherData.services : [];

  for (const service of services) {
    if (typeof service.logoutUrl === "string" && service.logoutUrl.trim()) {
      urls.push(service.logoutUrl.trim());
    }
  }

  return [...new Set(urls.map(normalizeLogoutUrl).filter(Boolean))];
}

async function notifyTaskSessionLogout(session) {
  if (!session?.user || !config.internalAccessToken || !config.taskInternalLogoutUrl) {
    return;
  }

  const runtimeContext = getRuntimeSessionContext(session);
  const user = runtimeContext.user ?? session.user;
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), 1500);

  try {
    const response = await fetch(config.taskInternalLogoutUrl, {
      method: "POST",
      headers: {
        "Accept": "application/json",
        "Authorization": `Bearer ${config.internalAccessToken}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        source: "launcher-global-logout",
        subject: session.user.sub,
        email: user.email || session.user.email || null,
      }),
      signal: controller.signal,
    });

    if (!response.ok) {
      console.warn(`Task internal logout returned ${response.status}`);
    }
  } catch (error) {
    if (error?.name !== "AbortError") {
      console.warn(error instanceof Error ? error.message : "Task internal logout failed");
    }
  } finally {
    clearTimeout(timeout);
  }
}

function normalizeLogoutUrl(value) {
  try {
    const url = new URL(value);
    if (url.protocol !== "http:" && url.protocol !== "https:") return null;
    return url.toString();
  } catch {
    return null;
  }
}

function renderGlobalLogoutPage(frontchannelLogoutUrls, finalRedirectUrl) {
  const logoutUrlsJson = JSON.stringify(frontchannelLogoutUrls);
  const redirectUrlJson = JSON.stringify(finalRedirectUrl);

  return `<!doctype html>
<html lang="ru">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>NODE.DC</title>
    <style>
      html,body{height:100%;margin:0;background:#0b0f0e;color:#0b0f0e}
    </style>
  </head>
  <body aria-busy="true">
    <script>
      const eventPayload = {
        type: "nodedc:session:logout",
        id: globalThis.crypto?.randomUUID ? globalThis.crypto.randomUUID() : String(Date.now()) + "-" + Math.random().toString(36).slice(2),
        source: "launcher-global-logout",
        createdAt: Date.now()
      };
      try {
        const channel = new BroadcastChannel("nodedc-platform-session");
        channel.postMessage(eventPayload);
        channel.close();
      } catch {}
      try {
        localStorage.setItem("nodedc:platform-session-event", JSON.stringify(eventPayload));
      } catch {}
      const logoutUrls = ${logoutUrlsJson};
      const finalRedirectUrl = ${redirectUrlJson};
      for (const logoutUrl of logoutUrls) {
        fetch(logoutUrl, { mode: "no-cors", credentials: "include", keepalive: true }).catch(() => undefined);
        const image = new Image();
        image.referrerPolicy = "no-referrer";
        image.src = logoutUrl;
      }
      window.setTimeout(() => window.location.replace(finalRedirectUrl), 50);
    </script>
  </body>
</html>`;
}

function renderSessionSyncBridgePage(allowedOrigins) {
  const allowedOriginsJson = JSON.stringify(allowedOrigins);

  return `<!doctype html>
<html lang="ru">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>NODE.DC session sync</title>
  </head>
  <body>
    <script>
      const allowedOrigins = new Set(${allowedOriginsJson});
      const parentOrigin = (() => {
        try {
          return new URL(document.referrer).origin;
        } catch {
          return "";
        }
      })();
      const channelName = "nodedc-platform-session";
      const storageKey = "nodedc:platform-session-event";
      let lastEventId = null;

      function isAllowedOrigin(origin) {
        return allowedOrigins.has(origin);
      }

      function isLogoutEvent(payload) {
        return payload && payload.type === "nodedc:session:logout" && typeof payload.id === "string";
      }

      function forwardToParent(payload) {
        if (!isLogoutEvent(payload) || payload.id === lastEventId || !isAllowedOrigin(parentOrigin)) return;
        lastEventId = payload.id;
        window.parent.postMessage(payload, parentOrigin);
      }

      function publish(payload) {
        if (!isLogoutEvent(payload)) return;
        try {
          const channel = new BroadcastChannel(channelName);
          channel.postMessage(payload);
          channel.close();
        } catch {}
        try {
          localStorage.setItem(storageKey, JSON.stringify(payload));
        } catch {}
      }

      window.addEventListener("message", (event) => {
        if (!isAllowedOrigin(event.origin) || !isLogoutEvent(event.data)) return;
        publish(event.data);
      });

      try {
        const channel = new BroadcastChannel(channelName);
        channel.addEventListener("message", (event) => forwardToParent(event.data));
      } catch {}

      window.addEventListener("storage", (event) => {
        if (event.key !== storageKey || !event.newValue) return;
        try {
          forwardToParent(JSON.parse(event.newValue));
        } catch {}
      });
    </script>
  </body>
</html>`;
}

function getSessionSyncAllowedOrigins() {
  const origins = new Set([new URL(config.appBaseUrl).origin]);

  for (const logoutUrl of getFrontchannelLogoutUrls()) {
    try {
      origins.add(new URL(logoutUrl).origin);
    } catch {
      void 0;
    }
  }

  return [...origins];
}

function readLauncherData() {
  try {
    return controlPlaneStore.readData();
  } catch {
    return null;
  }
}

function buildPublicBrandResponse(settings) {
  const configuredLogoLinkUrl = settings?.brand?.logoLinkUrl || "/";

  return {
    logoLinkUrl: resolvePublicUrl(configuredLogoLinkUrl, config.appBaseUrl),
  };
}

function resolvePublicUrl(value, baseUrl) {
  try {
    return new URL(value || "/", baseUrl).toString();
  } catch {
    return new URL("/", baseUrl).toString();
  }
}

function resolveUserAvatarPublicUrl(user) {
  if (!user?.avatarUrl) return null;
  return resolvePublicUrl(user.avatarUrl, config.appBaseUrl);
}

async function saveUploadedFile(payload) {
  if (!isUploadPayload(payload)) {
    throw new Error("Некорректный payload загрузки");
  }

  const match = /^data:([^;,]+)?(?:;[^,]*)?;base64,(.*)$/s.exec(payload.dataUrl);

  if (!match) {
    throw new Error("Файл должен прийти data-url с base64");
  }

  const mimeType = payload.mimeType || match[1] || "application/octet-stream";
  const storedName = buildStoredFileName(payload.fileName, mimeType);
  const fileBuffer = Buffer.from(match[2], "base64");

  await Promise.all(
    getWritableUploadRoots().map(async (uploadRoot) => {
      await mkdir(uploadRoot, { recursive: true });
      await writeFile(join(uploadRoot, storedName), fileBuffer);
    })
  );

  return {
    ok: true,
    url: `/storage/uploads/${storedName}`,
    fileName: storedName,
    originalFileName: payload.fileName,
    mimeType,
  };
}

async function saveLauncherData(payload) {
  await controlPlaneStore.writeData(payload);
}

function getWritableUploadRoots() {
  return [runtimeUploadsRoot];
}

function getReadableUploadRoots() {
  const roots = [
    runtimeUploadsRoot,
    join(projectRoot, "public", "storage", "uploads"),
  ];
  const distRoot = join(projectRoot, "dist");

  if (existsSync(distRoot)) {
    roots.push(join(distRoot, "storage", "uploads"));
  }

  return [...new Set(roots)];
}

function buildStoredFileName(fileName, mimeType) {
  const extension = extname(fileName) || extensionFromMimeType(mimeType);
  const rawBase = fileName.slice(0, extension ? -extension.length : undefined);
  const safeBase =
    rawBase
      .normalize("NFKD")
      .replace(/[^\w.-]+/g, "-")
      .replace(/^-+|-+$/g, "")
      .slice(0, 80) || "upload";

  return `${Date.now()}-${randomUUID().slice(0, 8)}-${safeBase}${extension.toLowerCase()}`;
}

function extensionFromMimeType(mimeType) {
  if (mimeType === "image/jpeg") return ".jpg";
  if (mimeType === "image/png") return ".png";
  if (mimeType === "image/gif") return ".gif";
  if (mimeType === "image/webp") return ".webp";
  if (mimeType === "video/mp4") return ".mp4";
  if (mimeType === "video/webm") return ".webm";
  if (mimeType === "video/quicktime") return ".mov";
  return "";
}

function resolveRuntimeStorageRoot(root) {
  const configuredRoot = process.env.NODEDC_LAUNCHER_STORAGE_DIR;

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

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

function resolveRuntimeUploadsRoot(root, storageRoot) {
  const configuredRoot = process.env.NODEDC_LAUNCHER_UPLOADS_DIR;

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

  return join(storageRoot, "uploads");
}

function isUploadPayload(payload) {
  return Boolean(
    payload &&
      typeof payload === "object" &&
      typeof payload.fileName === "string" &&
      typeof payload.mimeType === "string" &&
      typeof payload.dataUrl === "string"
  );
}

function getCurrentSession(req) {
  const sessionId = parseCookies(req.headers.cookie)[sessionCookieName];

  if (!sessionId) return null;

  const session = sessions.get(sessionId);

  if (!session || session.expiresAt < Date.now()) {
    sessions.delete(sessionId);
    return null;
  }

  return session;
}

function expireSession(res, session) {
  sessions.delete(session.id);
  res.clearCookie(sessionCookieName, clearCookieOptions());
}

function rejectInactiveSession(res, session, sessionAccess) {
  expireSession(res, session);
  res.status(sessionAccess.status ?? 401).json({
    authenticated: false,
    loginUrl: "/auth/login",
    error: sessionAccess.error,
    message: sessionAccess.message,
  });
}

function pruneExpiredSessions() {
  for (const [sessionId, session] of sessions) {
    if (session.expiresAt < Date.now()) {
      sessions.delete(sessionId);
    }
  }
}

function pruneExpiredState() {
  for (const [state, pendingLogin] of pendingLogins) {
    if (pendingLogin.expiresAt < Date.now()) {
      pendingLogins.delete(state);
    }
  }
}

function getValidOidcCookieStates(req) {
  const rawValue = parseCookies(req.headers.cookie)[oidcStateCookieName];

  if (!rawValue) return [];

  const seen = new Set();

  return rawValue
    .split(".")
    .filter((state) => /^[A-Za-z0-9_-]{32,256}$/.test(state))
    .filter((state) => {
      if (seen.has(state)) return false;
      seen.add(state);
      return true;
    })
    .filter((state) => {
      const pendingLogin = pendingLogins.get(state);
      return Boolean(pendingLogin && pendingLogin.expiresAt >= Date.now());
    });
}

function setOidcStateCookie(res, states) {
  if (!states.length) {
    res.clearCookie(oidcStateCookieName, clearCookieOptions());
    return;
  }

  res.cookie(oidcStateCookieName, states.join("."), cookieOptions(pendingLoginTtlMs));
}

function parseCookies(cookieHeader) {
  if (!cookieHeader) return {};

  return Object.fromEntries(
    cookieHeader.split(";").flatMap((part) => {
      const separatorIndex = part.indexOf("=");
      if (separatorIndex === -1) return [];
      const key = part.slice(0, separatorIndex).trim();
      const value = part.slice(separatorIndex + 1).trim();
      return [[key, decodeURIComponent(value)]];
    })
  );
}

function requireLauncherAdmin(req, res, next) {
  const session = getCurrentSession(req);

  if (!session) {
    const assistantGatewayContext = resolveAssistantGatewayAdminContext(req);

    if (assistantGatewayContext?.ok) {
      req.nodedcSession = assistantGatewayContext.session;
      req.nodedcAdminScope = assistantGatewayContext.adminScope;
      req.nodedcAssistantGateway = assistantGatewayContext.gateway;
      next();
      return;
    }

    if (assistantGatewayContext) {
      res.status(assistantGatewayContext.status).json({
        ok: false,
        error: assistantGatewayContext.error,
        message: assistantGatewayContext.message,
      });
      return;
    }

    res.status(401).json({ authenticated: false, loginUrl: "/auth/login" });
    return;
  }

  const sessionAccess = getSessionAccessState(session);

  if (!sessionAccess.ok) {
    rejectInactiveSession(res, session, sessionAccess);
    return;
  }

  const runtimeContext = sessionAccess.runtimeContext;
  const adminScope = resolveAdminScope(runtimeContext.user, runtimeContext.groups);

  if (!adminScope.isRoot && adminScope.clientIds.size === 0) {
    res.status(403).json({ error: "Недостаточно прав Launcher admin" });
    return;
  }

  req.nodedcSession = { ...session, user: runtimeContext.user };
  req.nodedcAdminScope = adminScope;
  next();
}

function requireRootLauncherAdmin(req, res, next) {
  if (!req.nodedcAdminScope?.isRoot) {
    res.status(403).json({ error: "Действие доступно только суперпользователю NODE.DC" });
    return;
  }

  next();
}

function requireSession(req, res, next) {
  const session = getCurrentSession(req);

  if (!session) {
    res.status(401).json({ authenticated: false, loginUrl: "/auth/login" });
    return;
  }

  const sessionAccess = getSessionAccessState(session);

  if (!sessionAccess.ok) {
    rejectInactiveSession(res, session, sessionAccess);
    return;
  }

  const runtimeContext = sessionAccess.runtimeContext;
  req.nodedcSession = { ...session, user: runtimeContext.user };
  next();
}

function resolveAssistantGatewayAdminContext(req) {
  const gateway = readRequestHeader(req, "x-nodedc-assistant-gateway");
  const actorUserId = readRequestHeader(req, "x-nodedc-assistant-actor-user-id");
  const actorEmail = readRequestHeader(req, "x-nodedc-assistant-actor-email");
  const actorSubject = readRequestHeader(req, "x-nodedc-assistant-actor-subject");
  const hasGatewaySignal = Boolean(gateway || actorUserId || actorEmail || actorSubject);

  if (!hasGatewaySignal) {
    return null;
  }

  if (gateway !== assistantGatewayName) {
    return {
      ok: false,
      status: 401,
      error: "assistant_gateway_invalid",
      message: "Unknown assistant gateway.",
    };
  }

  if (!isInternalRequestAuthorized(req)) {
    return {
      ok: false,
      status: config.internalAccessToken ? 401 : 503,
      error: config.internalAccessToken ? "assistant_gateway_unauthorized" : "assistant_gateway_not_configured",
      message: "Assistant gateway internal token is missing or invalid.",
    };
  }

  if (!isAssistantGatewayAdminRoute(req)) {
    return {
      ok: false,
      status: 403,
      error: "assistant_gateway_route_not_allowlisted",
      message: "Assistant gateway can only call explicitly allowlisted admin routes.",
    };
  }

  const snapshot = controlPlaneStore.getSnapshot({ name: "NDC Core Assistant Gateway", source: "assistant-gateway" });
  const actor = findInternalAccessUser(snapshot.data, {
    userId: actorUserId,
    email: actorEmail,
    subject: actorSubject,
  });

  if (!actor) {
    return {
      ok: false,
      status: 404,
      error: "assistant_gateway_actor_not_found",
      message: "Assistant gateway actor was not found in Launcher control-plane.",
    };
  }

  if (actor.globalStatus === "blocked") {
    return {
      ok: false,
      status: 403,
      error: "assistant_gateway_actor_blocked",
      message: "Assistant gateway actor is blocked.",
    };
  }

  const groups = resolveRequiredGroups(snapshot.data, actor);
  const user = buildAssistantGatewaySessionUser(actor, groups);
  const adminScope = resolveAdminScope(user, groups);

  if (!adminScope.isRoot && adminScope.clientIds.size === 0) {
    return {
      ok: false,
      status: 403,
      error: "assistant_gateway_actor_not_admin",
      message: "Assistant gateway actor has no Launcher admin scope.",
    };
  }

  return {
    ok: true,
    session: {
      id: `assistant-gateway:${actor.id}`,
      expiresAt: Date.now() + 60_000,
      user,
    },
    adminScope,
    gateway: {
      name: gateway,
      actorUserId: actor.id,
    },
  };
}

function isAssistantGatewayAdminRoute(req) {
  const method = String(req.method || "").toUpperCase();
  const path = req.path || String(req.url || "").split("?")[0] || "";

  return assistantGatewayAdminRouteAllowlist.some((route) => route.method === method && route.pattern.test(path));
}

function buildAssistantGatewaySessionUser(actor, groups) {
  return {
    sub: actor.authentikUserId || `launcher:${actor.id}`,
    email: actor.email,
    name: actor.name,
    avatarUrl: actor.avatarUrl ?? null,
    groups,
  };
}

function readRequestHeader(req, name) {
  const raw = req.headers[String(name).toLowerCase()];
  const value = Array.isArray(raw) ? raw[0] : raw;
  return typeof value === "string" ? value.trim() : "";
}

function isInternalRequestAuthorized(req) {
  if (!config.internalAccessToken) {
    return false;
  }

  const authorization = typeof req.headers.authorization === "string" ? req.headers.authorization : "";
  const bearerToken = authorization.match(/^Bearer\s+(.+)$/i)?.[1] ?? "";
  const headerToken = typeof req.headers["x-nodedc-internal-token"] === "string" ? req.headers["x-nodedc-internal-token"] : "";
  const requestToken = bearerToken || headerToken;

  return safeTokenEquals(requestToken, config.internalAccessToken);
}

function safeTokenEquals(actual, expected) {
  if (!actual || !expected) {
    return false;
  }

  const actualBuffer = Buffer.from(String(actual));
  const expectedBuffer = Buffer.from(String(expected));

  return actualBuffer.length === expectedBuffer.length && timingSafeEqual(actualBuffer, expectedBuffer);
}

function findInternalAccessUser(data, payload) {
  const subject = typeof payload?.subject === "string" ? payload.subject : "";
  const email = typeof payload?.email === "string" ? payload.email.toLowerCase() : "";
  const canonicalEmail = canonicalInternalAccessEmail(email);
  const userId = typeof payload?.userId === "string" ? payload.userId : "";

  return (
    data.users.find((user) => userId && user.id === userId) ??
    data.users.find((user) => subject && user.authentikUserId === subject) ??
    data.users.find((user) => email && user.email.toLowerCase() === email) ??
    data.users.find((user) => canonicalEmail && canonicalInternalAccessEmail(user.email) === canonicalEmail) ??
    null
  );
}

function canonicalInternalAccessEmail(value) {
  const email = String(value || "").trim().toLowerCase();
  const [local, domain] = email.split("@");
  if (!local || !domain) return email;
  if (domain === "gmail.com" || domain === "googlemail.com") {
    return `${local.replace(/\./g, "")}@gmail.com`;
  }
  return email;
}

function sanitizeServiceSlug(value) {
  return typeof value === "string" && value ? value : "task-manager";
}

function getLauncherProfileContext(session) {
  const snapshot = controlPlaneStore.getSnapshot(session.user);

  if (snapshot.actor.source !== "launcher") {
    throw new Error("Профиль пользователя не найден в Launcher control-plane");
  }

  return {
    actor: snapshot.actor,
    data: snapshot.data,
  };
}

function findLauncherUser(data, userId) {
  const user = data.users.find((candidate) => candidate.id === userId);

  if (!user) {
    throw new Error(`Unknown Launcher user: ${userId}`);
  }

  return user;
}

function notificationSubjectFromUser(user) {
  return {
    userId: user?.id ?? null,
    email: typeof user?.email === "string" ? user.email.toLowerCase() : null,
  };
}

function notificationDeliveryForUser(user, surface, notification) {
  if (!user?.id && !user?.email) return null;
  return {
    recipientUserId: user?.id ?? null,
    recipientEmail: typeof user?.email === "string" ? user.email.toLowerCase() : null,
    surface,
    title: notification.title,
    body: notification.body ?? "",
    meta: notification.meta ?? {},
    actionable: notification.actionable === true,
    actionType: notification.actionType ?? null,
    actionUrl: notification.actionUrl ?? null,
    entityType: notification.entityType ?? null,
    entityId: notification.entityId ?? null,
  };
}

function rootAdminNotificationDelivery(data, notification) {
  const rootUser = data.users.find((user) => user.id === "user_root") ?? data.users[0] ?? null;
  return notificationDeliveryForUser(rootUser, "hub", notification);
}

function notificationUsersForTaskerInvite(data, request) {
  const users = [];
  const seen = new Set();
  const addUser = (user) => {
    if (!user?.id || seen.has(user.id)) return;
    seen.add(user.id);
    users.push(user);
  };

  addUser(data.users.find((user) => user.id === request?.inviterUserId));
  addUser(data.users.find((user) => normalizeNotificationEmail(user.email) === normalizeNotificationEmail(request?.inviterEmail)));
  addUser(data.users.find((user) => normalizeNotificationEmail(user.email) === normalizeNotificationEmail(request?.inviteeEmail)));

  return users;
}

function buildAccessRequestHubNotification(accessRequest) {
  const applicantName = [accessRequest?.lastName, accessRequest?.firstName].filter(Boolean).join(" ") || accessRequest?.email || "NODE.DC user";
  const status = accessRequest?.status || "new";
  return {
    title: status === "new" ? "Входящий запрос доступа" : "Запрос доступа обновлён",
    body: `${applicantName} · ${accessRequest?.email || ""}`,
    meta: {
      displayStatus: platformNotificationStatusLabel(status),
      meta: accessRequest?.company || formatNotificationDate(accessRequest?.createdAt),
    },
  };
}

function buildTaskerInviteHubNotification(request) {
  const status = request?.status || "new";
  return {
    title: status === "new" ? "Запрос доступа Operational Core" : "Operational Core: заявка обновлена",
    body: `${request?.workspaceName || request?.workspaceSlug || "Operational Core"} · ${request?.inviteeEmail || ""}`,
    meta: {
      displayStatus: platformNotificationStatusLabel(status),
      meta: request?.inviterName || formatNotificationDate(request?.createdAt),
    },
  };
}

function buildEngineRoleHubNotification(request) {
  const target = request?.targetName || request?.targetEmail || "Engine user";
  const status = request?.status || "new";
  return {
    title: engineRoleHubNotificationTitle(request),
    body: engineRoleHubNotificationBody(request, target),
    meta: {
      displayStatus: platformNotificationStatusLabel(status),
      meta: `Запрошено: ${engineServiceRoleLabel(request?.requestedServiceRole)}`,
      requestId: request?.id,
      requestedServiceRole: request?.requestedServiceRole ?? null,
      currentServiceRole: request?.currentServiceRole ?? null,
    },
  };
}

function buildEngineWorkflowHubNotification(request) {
  const status = request?.status || "new";
  return {
    title: status === "new" ? "Запрос доступа к Engine workflow" : "Engine: заявка обновлена",
    body: `${request?.workflowName || request?.workflowId || "Engine workflow"} · ${request?.targetEmail || ""}`,
    meta: {
      displayStatus: platformNotificationStatusLabel(status),
      meta: request?.requesterName || formatNotificationDate(request?.createdAt),
      requestId: request?.id,
    },
  };
}

function buildEngineSurfaceNotification(request) {
  if (request?.requestType === "service_role") {
    return {
      title: request?.workflowName || "NODE.DC Engine",
      body: engineRoleSurfaceNotificationBody(request),
      meta: {
        displayStatus: engineSurfaceStatusLabel(request),
        meta: engineSurfaceMeta(request),
        requestId: request?.id,
        requestedServiceRole: request?.requestedServiceRole ?? null,
        currentServiceRole: request?.currentServiceRole ?? null,
      },
    };
  }

  return {
    title: request?.workflowName || request?.workflowId || "Engine workflow",
    body: `${request?.requesterName || request?.requesterEmail || "NODE.DC Engine"} просит роль ${engineWorkflowRoleLabel(request?.role)}`,
    meta: {
      displayStatus: engineSurfaceStatusLabel(request),
      meta: engineSurfaceMeta(request),
      requestId: request?.id,
    },
  };
}

function engineRoleHubNotificationTitle(request) {
  if (request?.status === "approved") return request?.comment === "admin_role_changed" ? "Engine: роль изменена" : "Engine: роль обновлена";
  if (request?.status === "rejected") return "Engine: повышение роли отклонено";
  return "Engine: запрос повышения роли";
}

function engineRoleHubNotificationBody(request, target) {
  if (request?.status === "rejected") return `${target} · администратор отклонил заявку`;
  if (request?.status === "approved") {
    return request?.comment === "admin_role_changed" ? `${target} · роль изменена администратором` : `${target} · новая роль применена`;
  }
  return `${target} · текущая роль ${engineServiceRoleLabel(request?.currentServiceRole)}`;
}

function engineRoleSurfaceNotificationBody(request) {
  const requestedRole = engineServiceRoleLabel(request?.requestedServiceRole);
  const requestedByTarget = normalizeNotificationEmail(request?.requesterEmail) === normalizeNotificationEmail(request?.targetEmail);
  if (request?.status === "approved") {
    return requestedByTarget && request?.comment !== "admin_role_changed"
      ? `Администратор подтвердил повышение роли: ${requestedRole}`
      : `Администратор изменил роль: ${requestedRole}`;
  }
  if (request?.status === "rejected") {
    return "Администратор отклонил заявку на повышение роли";
  }
  return `${request?.requesterName || request?.requesterEmail || "NODE.DC Engine"} просит роль ${requestedRole}`;
}

function engineSurfaceStatusLabel(request) {
  if (request?.requestType === "service_role" && request?.status === "approved" && request?.comment === "admin_role_changed") {
    return "Изменено";
  }
  if (request?.status === "approved") return "Одобрено";
  if (request?.status === "rejected") return "Отклонено";
  return "Новая заявка";
}

function engineSurfaceMeta(request) {
  const createdAt = formatShortNotificationDate(request?.createdAt);
  return ["Ваш запрос", createdAt].filter(Boolean).join(" · ");
}

function engineServiceRoleLabel(role) {
  if (role === "member") return "Участник";
  if (role === "viewer") return "Гость";
  return "не назначена";
}

function engineWorkflowRoleLabel(role) {
  if (role === "admin") return "Соавтор";
  if (role === "editor") return "Редактор";
  return "Просмотр";
}

function platformNotificationStatusLabel(status) {
  const normalized = String(status || "").trim().toLowerCase();
  if (normalized === "approved") return "Подтверждено";
  if (normalized === "rejected") return "Отклонено";
  if (normalized === "cancelled") return "Отменено";
  return "Входящее";
}

function formatNotificationDate(value) {
  const timestamp = Date.parse(String(value || ""));
  if (!Number.isFinite(timestamp)) return "";
  return new Intl.DateTimeFormat("ru-RU", {
    day: "2-digit",
    month: "2-digit",
    year: "numeric",
    hour: "2-digit",
    minute: "2-digit",
  }).format(new Date(timestamp));
}

function formatShortNotificationDate(value) {
  const timestamp = Date.parse(String(value || ""));
  if (!Number.isFinite(timestamp)) return "";
  return new Intl.DateTimeFormat("ru-RU", {
    day: "2-digit",
    month: "2-digit",
    hour: "2-digit",
    minute: "2-digit",
  }).format(new Date(timestamp));
}

function normalizeNotificationEmail(value) {
  return String(value || "").trim().toLowerCase();
}

function sanitizeNotificationSurface(value, fallback = "hub") {
  const surface = typeof value === "string" && value.trim() ? value.trim().toLowerCase() : fallback;
  if (surface === "hub" || surface === "engine" || surface === "operational-core") return surface;
  return fallback;
}

function publishNotificationEvent(command) {
  if (!notificationCoreClient.isConfigured()) {
    return;
  }
  if (!Array.isArray(command?.deliveries) || command.deliveries.length === 0) {
    return;
  }

  notificationCoreClient.createEvent(command).catch((error) => {
    console.warn("[notification-core] event publish failed:", error?.message || error);
  });
}

function isLauncherAdmin(groups) {
  return groups.includes("nodedc:superadmin") || groups.includes("nodedc:launcher:admin");
}

const clientAdminMembershipRoles = new Set(["client_owner", "client_admin"]);
const protectedLauncherUserIds = new Set(["user_root"]);

function resolveAdminScope(identity, groups) {
  const snapshot = controlPlaneStore.getSnapshot(identity);
  const actorId = snapshot.actor.source === "launcher" ? snapshot.actor.id : null;
  const isRoot = isLauncherAdmin(groups);
  const clientIds = new Set(
    isRoot
      ? snapshot.data.clients.map((client) => client.id)
      : snapshot.data.memberships
          .filter((membership) => membership.userId === actorId && membership.status === "active" && clientAdminMembershipRoles.has(membership.role))
          .map((membership) => membership.clientId)
  );

  return {
    actorId,
    clientIds,
    isRoot,
    snapshot,
  };
}

function canAdminManageClient(req, clientId) {
  return Boolean(req.nodedcAdminScope?.isRoot || req.nodedcAdminScope?.clientIds.has(clientId));
}

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

function isProtectedSelfManageRequest(req, userId) {
  return isProtectedLauncherUser(userId) && req.nodedcAdminScope?.actorId === userId;
}

function canAdminManageUser(req, userId, options = {}) {
  if (isProtectedLauncherUser(userId) && !(options.allowProtectedSelf && isProtectedSelfManageRequest(req, userId))) {
    return false;
  }

  if (req.nodedcAdminScope?.isRoot) {
    return true;
  }

  return req.nodedcAdminScope?.snapshot.data.memberships.some(
    (membership) => membership.userId === userId && req.nodedcAdminScope.clientIds.has(membership.clientId)
  );
}

function assertAdminCanManageClient(req, res, clientId) {
  if (canAdminManageClient(req, clientId)) {
    return true;
  }

  res.status(403).json({ error: "Недостаточно прав для управления этим клиентом" });
  return false;
}

function assertAdminCanManageUser(req, res, userId, options = {}) {
  if (canAdminManageUser(req, userId, options)) {
    return true;
  }

  res.status(403).json({ error: "Недостаточно прав для управления этим пользователем" });
  return false;
}

function assertAdminCanManageAccessForUser(req, res, userId) {
  return assertAdminCanManageUser(req, res, userId);
}

function assertAdminCanManageProtectedGroupMembers(req, res, previousMemberIds = [], nextMemberIds = []) {
  if (!Array.isArray(nextMemberIds)) {
    return true;
  }

  const previous = new Set(previousMemberIds);
  const next = new Set(nextMemberIds);
  const changedProtectedUserIds = [...protectedLauncherUserIds].filter((userId) => previous.has(userId) !== next.has(userId));
  const blockedUserId = changedProtectedUserIds.find((userId) => !isProtectedSelfManageRequest(req, userId));

  if (!blockedUserId) {
    return true;
  }

  res.status(403).json({
    error: "protected_user_group_membership_locked",
    message: "Защищённого пользователя может добавлять в группы или удалять из групп только он сам.",
  });
  return false;
}

function assertAdminCanManageMembership(req, res, membership, options = {}) {
  if (!assertAdminCanManageClient(req, res, membership.clientId)) {
    return false;
  }

  return assertAdminCanManageUser(req, res, membership.userId, options);
}

function assertAdminCanManageGrantTarget(req, res, data, targetType, targetId, options = {}) {
  if (req.nodedcAdminScope?.isRoot) {
    if (targetType === "client") {
      return true;
    }

    if (targetType === "user" && (!isProtectedLauncherUser(targetId) || isProtectedSelfManageRequest(req, targetId) || !options.allowProtectedSelf)) {
      return true;
    }
  }

  if (targetType === "client") {
    return assertAdminCanManageClient(req, res, targetId);
  }

  if (targetType === "group") {
    const group = data.groups.find((candidate) => candidate.id === targetId);

    if (!group) {
      res.status(404).json({ error: "group_not_found" });
      return false;
    }

    const blockedProtectedUserId = group.memberIds.find((userId) => isProtectedLauncherUser(userId) && !isProtectedSelfManageRequest(req, userId));

    if (blockedProtectedUserId) {
      res.status(403).json({
        error: "protected_group_access_locked",
        message: "Гранты группы с защищённым пользователем может менять только сам защищённый пользователь.",
      });
      return false;
    }

    return assertAdminCanManageClient(req, res, group.clientId);
  }

  if (targetType === "user") {
    return assertAdminCanManageUser(req, res, targetId, options);
  }

  res.status(403).json({ error: "Недостаточно прав для управления этим доступом" });
  return false;
}

function scopeAdminSnapshot(req, snapshot = controlPlaneStore.getSnapshot(req.nodedcSession.user)) {
  return {
    ...snapshot,
    data: scopeControlPlaneData(snapshot.data, req.nodedcAdminScope),
  };
}

function scopeAdminMutationResult(req, result) {
  if (!result?.data) {
    return result;
  }

  return {
    ...result,
    data: scopeControlPlaneData(result.data, req.nodedcAdminScope),
  };
}

function scopeSessionControlPlaneData(req, data, actorId) {
  const runtimeContext = getRuntimeSessionContext(req.nodedcSession);
  const adminScope = resolveAdminScope(runtimeContext.user, runtimeContext.groups);

  if (adminScope.isRoot || adminScope.clientIds.size > 0) {
    return scopeControlPlaneData(data, adminScope);
  }

  return scopeRuntimeControlPlaneData(data, actorId);
}

function scopeControlPlaneData(data, scope) {
  if (!scope || scope.isRoot) {
    return data;
  }

  const clientIds = scope.clientIds;
  const memberships = data.memberships.filter((membership) => clientIds.has(membership.clientId));
  const userIds = new Set(memberships.map((membership) => membership.userId));

  if (scope.actorId) {
    userIds.add(scope.actorId);
  }

  const groupIds = new Set(data.groups.filter((group) => clientIds.has(group.clientId)).map((group) => group.id));
  const userEmails = new Set(
    data.users
      .filter((user) => userIds.has(user.id))
      .map((user) => String(user.email || "").toLowerCase())
      .filter(Boolean)
  );

  return {
    ...data,
    clients: data.clients.filter((client) => clientIds.has(client.id)),
    users: data.users.filter((user) => userIds.has(user.id)),
    memberships,
    groups: data.groups.filter((group) => clientIds.has(group.clientId)),
    invites: data.invites.filter((invite) => clientIds.has(invite.clientId)),
    accessRequests: [],
    taskerInviteRequests: [],
    engineWorkflowAccessRequests: data.engineWorkflowAccessRequests.filter((request) =>
      userIds.has(request.requesterUserId) ||
      userIds.has(request.targetUserId) ||
      userEmails.has(String(request.requesterEmail || "").toLowerCase()) ||
      userEmails.has(String(request.targetEmail || "").toLowerCase())
    ),
    grants: data.grants.filter((grant) => {
      if (grant.targetType === "client") return clientIds.has(grant.targetId);
      if (grant.targetType === "group") return groupIds.has(grant.targetId);
      if (grant.targetType === "user") return userIds.has(grant.targetId);
      return false;
    }),
    exceptions: data.exceptions.filter((exception) => userIds.has(exception.userId)),
    serviceModuleEntitlements: data.serviceModuleEntitlements.filter((entitlement) => userIds.has(entitlement.userId)),
    taskManagerMemberships: data.taskManagerMemberships.filter(
      (membership) => clientIds.has(membership.clientId) && userIds.has(membership.userId)
    ),
    taskManagerProjectMemberships: data.taskManagerProjectMemberships.filter(
      (membership) => clientIds.has(membership.clientId) && userIds.has(membership.userId)
    ),
    syncStatuses: data.syncStatuses.filter(
      (syncStatus) => clientIds.has(syncStatus.objectId) || userIds.has(syncStatus.objectId) || groupIds.has(syncStatus.objectId)
    ),
    auditEvents: data.auditEvents.filter((event) => !event.clientId || clientIds.has(event.clientId)),
  };
}

function scopeRuntimeControlPlaneData(data, userId) {
  const user = data.users.find((candidate) => candidate.id === userId);

  if (!user) {
    return {
      ...data,
      clients: [],
      users: [],
      memberships: [],
      groups: [],
      invites: [],
      accessRequests: [],
      revokedAccounts: [],
      taskerInviteRequests: [],
      engineWorkflowAccessRequests: [],
      grants: [],
      exceptions: [],
      serviceModuleEntitlements: [],
      syncStatuses: [],
      auditEvents: [],
      taskManagerMemberships: [],
      taskManagerProjectMemberships: [],
    };
  }

  const memberships = data.memberships.filter((membership) => membership.userId === user.id);
  const clientIds = new Set(memberships.map((membership) => membership.clientId));
  const groups = data.groups
    .filter((group) => clientIds.has(group.clientId) && group.memberIds.includes(user.id))
    .map((group) => ({ ...group, memberIds: group.memberIds.filter((memberId) => memberId === user.id) }));
  const groupIds = new Set(groups.map((group) => group.id));
  const userEmail = String(user.email || "").toLowerCase();
  const relatedEngineRequests = data.engineWorkflowAccessRequests.filter((request) =>
    request.requesterUserId === user.id ||
    request.targetUserId === user.id ||
    String(request.requesterEmail || "").toLowerCase() === userEmail ||
    String(request.targetEmail || "").toLowerCase() === userEmail
  );

  return {
    ...data,
    clients: data.clients.filter((client) => clientIds.has(client.id)),
    users: [user],
    memberships,
    groups,
    invites: [],
    accessRequests: [],
    revokedAccounts: [],
    taskerInviteRequests: [],
    engineWorkflowAccessRequests: relatedEngineRequests,
    grants: data.grants.filter((grant) => {
      if (grant.targetType === "client") return clientIds.has(grant.targetId);
      if (grant.targetType === "group") return groupIds.has(grant.targetId);
      if (grant.targetType === "user") return grant.targetId === user.id;
      return false;
    }),
    exceptions: data.exceptions.filter((exception) => exception.userId === user.id),
    serviceModuleEntitlements: data.serviceModuleEntitlements.filter((entitlement) => entitlement.userId === user.id),
    syncStatuses: [],
    auditEvents: [],
    taskManagerMemberships: data.taskManagerMemberships.filter(
      (membership) => membership.userId === user.id && clientIds.has(membership.clientId)
    ),
    taskManagerProjectMemberships: data.taskManagerProjectMemberships.filter(
      (membership) => membership.userId === user.id && clientIds.has(membership.clientId)
    ),
  };
}

function cookieOptions(maxAgeMs) {
  const options = {
    httpOnly: true,
    sameSite: "lax",
    secure: config.cookieSecure,
    path: "/",
    maxAge: maxAgeMs,
  };

  if (config.cookieDomain) {
    options.domain = config.cookieDomain;
  }

  return options;
}

function clearCookieOptions() {
  const options = {
    httpOnly: true,
    sameSite: "lax",
    secure: config.cookieSecure,
    path: "/",
  };

  if (config.cookieDomain) {
    options.domain = config.cookieDomain;
  }

  return options;
}

function shouldDisableHttpCache(req) {
  if (req.path.startsWith("/api/") || req.path.startsWith("/auth/")) {
    return true;
  }

  if (req.method !== "GET" && req.method !== "HEAD") {
    return false;
  }

  const accept = typeof req.headers.accept === "string" ? req.headers.accept : "";
  return accept.includes("text/html");
}

function lockNoStoreHeaders(res) {
  const setHeader = res.setHeader.bind(res);

  setNoStore(res);

  res.setHeader = (name, value) => {
    const normalizedName = String(name).toLowerCase();

    if (normalizedName === "cache-control") {
      return setHeader(name, noStoreCacheControl);
    }

    if (normalizedName === "pragma") {
      return setHeader(name, "no-cache");
    }

    if (normalizedName === "expires") {
      return setHeader(name, "0");
    }

    return setHeader(name, value);
  };
}

function setNoStore(res) {
  res.setHeader("Cache-Control", noStoreCacheControl);
  res.setHeader("Pragma", "no-cache");
  res.setHeader("Expires", "0");
}

function buildLoginRedirectUrl(returnTo, { forceLogin = false, includeReturnTo = false } = {}) {
  const loginUrl = new URL("/auth/login", config.appBaseUrl);
  const cleanReturnTo = sanitizeReturnTo(returnTo);

  if (forceLogin) {
    loginUrl.searchParams.set("prompt", "login");
  }

  if (includeReturnTo || cleanReturnTo !== "/") {
    loginUrl.searchParams.set("returnTo", cleanReturnTo);
  }

  return loginUrl.toString();
}

function buildPlatformInviteUrl(invite) {
  return new URL(`/invite/${encodeURIComponent(invite.token)}`, config.appBaseUrl).toString();
}

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

  return "/";
}

function buildOidcLogoutUrl(discovery, returnTo = "/", idToken = null) {
  const issuerUrl = new URL(discovery.issuer || config.issuer);
  const logoutUrl = new URL("/if/flow/default-invalidation-flow/", issuerUrl.origin);

  logoutUrl.searchParams.set("client_id", config.clientId);
  logoutUrl.searchParams.set("post_logout_redirect_uri", buildLoggedOutRedirectUrl(returnTo));

  if (idToken) {
    logoutUrl.searchParams.set("id_token_hint", idToken);
  }

  return logoutUrl;
}

function buildLoggedOutRedirectUrl(returnTo = "/") {
  const loggedOutUrl = new URL("/auth/logged-out", config.appBaseUrl);
  const cleanReturnTo = sanitizeReturnTo(returnTo);

  if (cleanReturnTo !== "/") {
    loggedOutUrl.searchParams.set("returnTo", cleanReturnTo);
  }

  return loggedOutUrl.toString();
}

function randomBase64Url(size) {
  return randomBytes(size).toString("base64url");
}

function sanitizeReturnTo(returnTo) {
  if (typeof returnTo !== "string" || !returnTo.startsWith("/") || returnTo.startsWith("//")) {
    return "/";
  }

  return returnTo;
}

function sanitizePrompt(prompt) {
  if (prompt === "login" || prompt === "none" || prompt === "consent" || prompt === "select_account") {
    return prompt;
  }

  return null;
}

function ensureTrailingSlash(value) {
  return value.endsWith("/") ? value : `${value}/`;
}

async function sendAdminIdempotentJson(req, res, operation) {
  const idempotencyKey = normalizeAdminMutationIdempotencyKey(req);

  if (idempotencyKey === false) {
    res.status(400).json({
      ok: false,
      error: "idempotency_key_invalid",
      message: "Idempotency-Key must be 8-256 visible characters.",
    });
    return;
  }

  if (!idempotencyKey) {
    res.json(await operation());
    return;
  }

  pruneAdminMutationIdempotencyRecords();

  const recordKey = buildAdminMutationIdempotencyRecordKey(req, idempotencyKey);
  const fingerprint = buildAdminMutationFingerprint(req);
  const existingRecord = adminMutationIdempotencyRecords.get(recordKey);

  if (existingRecord && existingRecord.fingerprint !== fingerprint) {
    res.status(409).json({
      ok: false,
      error: "idempotency_key_conflict",
      message: "Idempotency-Key was already used for a different admin mutation.",
    });
    return;
  }

  if (existingRecord?.status === "completed") {
    res.setHeader("Idempotency-Key", idempotencyKey);
    res.setHeader("Idempotency-Replayed", "true");
    res.status(existingRecord.statusCode).json(existingRecord.body);
    return;
  }

  if (existingRecord?.status === "pending") {
    const response = await existingRecord.promise;
    res.setHeader("Idempotency-Key", idempotencyKey);
    res.setHeader("Idempotency-Replayed", "true");
    res.status(response.statusCode).json(response.body);
    return;
  }

  const promise = Promise.resolve(operation()).then((body) => ({ statusCode: 200, body }));
  adminMutationIdempotencyRecords.set(recordKey, {
    status: "pending",
    createdAt: Date.now(),
    fingerprint,
    promise,
  });

  try {
    const response = await promise;
    adminMutationIdempotencyRecords.set(recordKey, {
      status: "completed",
      createdAt: Date.now(),
      fingerprint,
      statusCode: response.statusCode,
      body: response.body,
    });
    res.setHeader("Idempotency-Key", idempotencyKey);
    res.status(response.statusCode).json(response.body);
  } catch (error) {
    adminMutationIdempotencyRecords.delete(recordKey);
    throw error;
  }
}

function normalizeAdminMutationIdempotencyKey(req) {
  const rawHeader = req.headers["idempotency-key"];
  const rawValue = Array.isArray(rawHeader) ? rawHeader[0] : rawHeader;

  if (rawValue === undefined || rawValue === null || rawValue === "") {
    return null;
  }

  const value = String(rawValue).trim();
  if (!/^[\x21-\x7E]{8,256}$/.test(value)) {
    return false;
  }

  return value;
}

function buildAdminMutationIdempotencyRecordKey(req, idempotencyKey) {
  const actorId =
    req.nodedcAdminScope?.actorId ||
    req.nodedcSession?.user?.sub ||
    req.nodedcSession?.user?.email ||
    "unknown";
  return `${actorId}:${idempotencyKey}`;
}

function buildAdminMutationFingerprint(req) {
  const routePath = req.route?.path || req.path;
  const bodyHash = createHash("sha256").update(stableJson(req.body ?? null)).digest("hex");
  return `${req.method}:${routePath}:${req.path}:${bodyHash}`;
}

function pruneAdminMutationIdempotencyRecords() {
  const expiresBefore = Date.now() - adminMutationIdempotencyTtlMs;
  for (const [key, record] of adminMutationIdempotencyRecords.entries()) {
    if ((record.createdAt ?? 0) < expiresBefore) {
      adminMutationIdempotencyRecords.delete(key);
    }
  }
}

function stableJson(value) {
  if (Array.isArray(value)) {
    return `[${value.map((item) => stableJson(item)).join(",")}]`;
  }

  if (value && typeof value === "object") {
    return `{${Object.keys(value)
      .sort()
      .map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`)
      .join(",")}}`;
  }

  return JSON.stringify(value);
}

function asyncRoute(handler) {
  return (req, res, next) => {
    Promise.resolve(handler(req, res, next)).catch(next);
  };
}

function loadEnvFiles(candidates) {
  for (const candidate of candidates) {
    if (!candidate) continue;

    const envPath = resolve(projectRoot, candidate);

    if (!existsSync(envPath)) continue;

    const lines = readFileSync(envPath, "utf8").split(/\r?\n/);

    for (const line of lines) {
      const trimmed = line.trim();

      if (!trimmed || trimmed.startsWith("#") || !trimmed.includes("=")) continue;

      const separatorIndex = trimmed.indexOf("=");
      const key = trimmed.slice(0, separatorIndex).trim();
      const value = stripEnvQuotes(trimmed.slice(separatorIndex + 1).trim());

      if (!process.env[key]) {
        process.env[key] = value;
      }
    }
  }
}

function stripEnvQuotes(value) {
  if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
    return value.slice(1, -1);
  }

  return value;
}
