import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type { Client } from "../entities/client/types";
import type { Invite } from "../entities/invite/types";
import { syncServiceLaunchLink } from "../entities/service/links";
import type { LauncherServiceView, Service } from "../entities/service/types";
import type { ClientGroup, ClientMembership, LauncherUser } from "../entities/user/types";
import {
  approveAdminAccessRequest,
  approveAdminTaskerInviteRequest,
  createAdminClient,
  createAdminGroup,
  createAdminInvite,
  createAdminService,
  createAdminUser,
  deleteAdminClient,
  deleteAdminGroup,
  deleteAdminInvite,
  deleteAdminMembership,
  deleteAdminService,
  deleteAdminUser,
  ensureAdminTaskManagerProjectMembership,
  ensureAdminTaskManagerWorkspaceMembership,
  fetchAdminTaskManagerWorkspaces,
  fetchControlPlaneSnapshot,
  reorderAdminServices,
  retryAdminSync,
  rejectAdminAccessRequest,
  rejectAdminTaskerInviteRequest,
  removeAdminTaskManagerProjectMembership,
  removeAdminTaskManagerWorkspaceMembership,
  setAdminUserServiceAccess,
  updateAdminClient,
  updateAdminAccessRequest,
  updateAdminGroup,
  updateAdminInvite,
  updateAdminMembership,
  updateAdminService,
  updateAdminSettings,
  updateAdminUserProfile,
  type ControlPlaneMutationResult,
  type TaskManagerWorkspaceMemberRole,
  type TaskManagerWorkspaceSummary,
} from "../shared/api/adminApi";
import { createAccessRequest, type CreateAccessRequestResponse } from "../shared/api/accessRequestApi";
import {
  buildLauncherServices,
  buildMe,
  initialLauncherData,
  normalizeLauncherData,
  profileOptions,
  type LauncherData,
  type LauncherSettings,
} from "../shared/api/mockApi";
import {
  fetchAuthSession,
  fetchAvailableApps,
  type AuthenticatedSession,
  type AuthSession,
  type LauncherAuthApp,
} from "../shared/api/authApi";
import { updateOwnPassword, updateOwnProfile } from "../shared/api/profileApi";
import { acceptInvite, fetchPublicInvite, registerInvite, type PublicInviteResponse, type RegisterInviteCommand } from "../shared/api/inviteApi";
import type { AccessRequest, CreateAccessRequestCommand } from "../entities/access-request/types";
import { subscribeToNodeDCLogoutEvents } from "../shared/session/sessionSync";
import { loadPersistedLauncherData } from "../shared/api/storageApi";
import {
  AdminOverlay,
  type AccessAssignmentValue,
  type CreateUserCommand,
  type EnsureTaskManagerProjectMemberCommand,
  type SetUserServiceAccessCommand,
} from "../widgets/admin-overlay/AdminOverlay";
import { ProfileSettingsPanel } from "../widgets/profile-settings-panel/ProfileSettingsPanel";
import { ServiceRail } from "../widgets/service-rail/ServiceRail";
import { ServiceStage } from "../widgets/service-stage/ServiceStage";
import { TopBar, type LauncherAdminMode } from "../widgets/top-bar/TopBar";

let lastAuthRedirect: { url: string; startedAt: number } | null = null;

type InviteFlowState =
  | { status: "loading" }
  | { status: "ready"; payload: PublicInviteResponse }
  | { status: "accepting"; payload: PublicInviteResponse }
  | { status: "accepted"; payload: PublicInviteResponse }
  | { status: "registering"; payload: PublicInviteResponse }
  | { status: "registered"; payload: PublicInviteResponse; loginUrl: string }
  | { status: "error"; message: string; payload?: PublicInviteResponse };

type ControlPlaneMutationOutcome = { ok: true; data: LauncherData } | { ok: false; message: string };

export function LauncherApp() {
  const inviteToken = useMemo(() => parseInviteToken(window.location.pathname), []);
  const isAccessRequestRoute = useMemo(() => isAccessRequestPath(window.location.pathname), []);
  const [data, setData] = useState<LauncherData>(() => syncLauncherServiceLinks(initialLauncherData));
  const [activeProfileId, setActiveProfileId] = useState(profileOptions[0].userId);
  const [activeClientId, setActiveClientId] = useState(profileOptions[0].defaultClientId);
  const [selectedServiceId, setSelectedServiceId] = useState<string | undefined>();
  const [adminOpen, setAdminOpen] = useState(false);
  const [adminMode, setAdminMode] = useState<LauncherAdminMode>("admin");
  const [authSession, setAuthSession] = useState<AuthSession | null>(null);
  const [authApps, setAuthApps] = useState<LauncherAuthApp[] | null>(null);
  const [profileSettingsOpen, setProfileSettingsOpen] = useState(false);
  const [pendingAccessAssignments, setPendingAccessAssignments] = useState<Record<string, AccessAssignmentValue>>({});
  const [pendingTaskManagerMemberships, setPendingTaskManagerMemberships] = useState<Record<string, boolean>>({});
  const [pendingTaskManagerProjectMemberships, setPendingTaskManagerProjectMemberships] = useState<Record<string, boolean>>({});
  const [taskManagerWorkspaces, setTaskManagerWorkspaces] = useState<TaskManagerWorkspaceSummary[]>([]);
  const [taskManagerWorkspacesLoading, setTaskManagerWorkspacesLoading] = useState(false);
  const [taskManagerWorkspacesError, setTaskManagerWorkspacesError] = useState<string | null>(null);
  const [inviteFlow, setInviteFlow] = useState<InviteFlowState | null>(() => (inviteToken ? { status: "loading" } : null));
  const runtimeDataRef = useRef(data);
  const runtimeProfileIdRef = useRef(activeProfileId);
  const runtimeClientIdRef = useRef(activeClientId);
  const resolvedProfileId = useMemo(
    () => resolveRuntimeProfileId(data, authSession, activeProfileId),
    [activeProfileId, authSession, data]
  );

  useEffect(() => {
    runtimeDataRef.current = data;
    runtimeProfileIdRef.current = resolvedProfileId;
    runtimeClientIdRef.current = activeClientId;
  }, [activeClientId, data, resolvedProfileId]);

  const me = useMemo(() => buildMe(data, resolvedProfileId, activeClientId), [data, resolvedProfileId, activeClientId]);
  const activeProfileUser = data.users.find((user) => user.id === resolvedProfileId) ?? data.users[0];
  const currentAccessRequest = useMemo(() => {
    if (!authSession?.authenticated || !authSession.user.email) return null;

    const sessionEmail = authSession.user.email.toLowerCase();
    return data.accessRequests.find((request) => request.email.toLowerCase() === sessionEmail && request.status !== "approved") ?? null;
  }, [authSession, data.accessRequests]);
  const runtimeMe = useMemo(() => {
    if (!authSession?.authenticated) return me;

    return {
      ...me,
      user: {
        ...me.user,
        authentikUserId: authSession.user.sub,
        email: me.user.email || authSession.user.email,
        name: me.user.name || authSession.user.name,
        avatarUrl: me.user.avatarUrl ?? authSession.user.avatarUrl,
      },
      mockAuthentikClaims: {
        ...me.mockAuthentikClaims,
        sub: authSession.user.sub,
        email: authSession.user.email || me.mockAuthentikClaims.email,
        name: authSession.user.name || me.mockAuthentikClaims.name,
        avatarUrl: authSession.user.avatarUrl ?? null,
        groups: authSession.groups,
      },
    };
  }, [authSession, me]);
  const resolvedClientId = me.activeClientId;
  const canOpenAdminApi = Boolean(authSession?.authenticated && runtimeMe.permissions.canOpenAdmin);
  const authAppsBySlug = useMemo(() => new Map((authApps ?? []).map((app) => [app.slug, app])), [authApps]);
  const launcherServices = useMemo(
    () => {
      const services = buildLauncherServices(data, resolvedProfileId, resolvedClientId);

      if (!authSession?.authenticated || authApps === null) {
        return [];
      }

      return services.map((service) => {
        const app = authAppsBySlug.get(service.slug);

        if (!app) {
          return {
            ...service,
            userAccess: "denied" as const,
            openUrl: null,
            effectiveAccess: {
              ...service.effectiveAccess,
              allowed: false,
              visible: true,
              openEnabled: false,
              reason: "Нет доступа",
            },
          };
        }

        const appVisible = app.status !== "hidden" && app.status !== "disabled";
        const allowed = app.hasAccess && appVisible && service.effectiveAccess.allowed;
        const openEnabled = allowed && app.status === "active" && service.effectiveAccess.openEnabled;

        return {
          ...service,
          title: app.title || service.title,
          description: app.description || service.description,
          openUrl: openEnabled ? app.openUrl || app.url || service.openUrl : null,
          userAccess: allowed ? ("allowed" as const) : ("denied" as const),
          effectiveAccess: {
            ...service.effectiveAccess,
            allowed,
            visible: appVisible,
            openEnabled,
            reason: !app.hasAccess ? app.accessReason || "Нет доступа" : service.effectiveAccess.reason,
          },
        };
      }).filter((service) => service.effectiveAccess.visible);
    },
    [authApps, authAppsBySlug, authSession, data, resolvedProfileId, resolvedClientId]
  );

  useEffect(() => {
    if (!launcherServices.length) {
      setSelectedServiceId(undefined);
      return;
    }

    if (selectedServiceId && !launcherServices.some((service) => service.id === selectedServiceId)) {
      setSelectedServiceId(undefined);
    }
  }, [launcherServices, selectedServiceId]);

  const selectedService = launcherServices.find((service) => service.id === selectedServiceId);

  useEffect(() => {
    let isMounted = true;

    fetchAuthSession()
      .then(async (session) => {
        if (!isMounted) return;

        setAuthSession(session);

        if (!session.authenticated) {
          setAuthApps([]);
          return;
        }

        const apps = await fetchAvailableApps();

        if (isMounted) {
          setAuthApps(apps);
        }
      })
      .catch((error: unknown) => {
        if (!isMounted) return;

        setAuthSession({ authenticated: false, loginUrl: "/auth/login" });
        setAuthApps([]);
        console.warn(error instanceof Error ? error.message : "Не удалось проверить сессию платформы");
      });

    return () => {
      isMounted = false;
    };
  }, []);

  useEffect(() => {
    if (!authSession || authSession.authenticated) return;
    if (inviteToken || isAccessRequestRoute) return;

    redirectToLogin(authSession.loginUrl);
  }, [authSession, inviteToken, isAccessRequestRoute]);

  useEffect(() => {
    if (!inviteToken) return;

    let isMounted = true;
    setInviteFlow({ status: "loading" });

    fetchPublicInvite(inviteToken)
      .then((payload) => {
        if (!isMounted) return;
        setInviteFlow({ status: "ready", payload });
      })
      .catch((error: unknown) => {
        if (!isMounted) return;
        setInviteFlow({ status: "error", message: error instanceof Error ? error.message : "Инвайт не найден" });
      });

    return () => {
      isMounted = false;
    };
  }, [inviteToken]);

  useEffect(() => {
    let isRedirecting = false;

    return subscribeToNodeDCLogoutEvents(() => {
      if (isRedirecting) return;

      isRedirecting = true;
      redirectToLogin("/auth/login?prompt=login");
    });
  }, []);

  useEffect(() => {
    let isMounted = true;

    const validateRestoredSession = (event: PageTransitionEvent) => {
      if (!event.persisted) return;

      fetchAuthSession()
        .then((session) => {
          if (!isMounted) return;

          if (!session.authenticated) {
            if (inviteToken || isAccessRequestRoute) return;
            redirectToLogin(session.loginUrl);
            return;
          }

          setAuthSession(session);
        })
        .catch(() => {
          if (isMounted && !inviteToken && !isAccessRequestRoute) {
            redirectToLogin("/auth/login");
          }
        });
    };

    window.addEventListener("pageshow", validateRestoredSession);

    return () => {
      isMounted = false;
      window.removeEventListener("pageshow", validateRestoredSession);
    };
  }, [inviteToken, isAccessRequestRoute]);

  useEffect(() => {
    if (!authSession?.authenticated) return;

    const nextContext = resolveAuthenticatedContext(data, authSession, activeProfileId, activeClientId);

    if (activeProfileId !== nextContext.profileId) {
      setActiveProfileId(nextContext.profileId);
    }

    if (activeClientId !== nextContext.clientId) {
      setActiveClientId(nextContext.clientId);
    }
  }, [activeClientId, activeProfileId, authSession, data]);

  useEffect(() => {
    let isMounted = true;

    loadPersistedLauncherData()
      .then((persistedData) => {
        if (isMounted && persistedData) {
          setData(syncLauncherServiceLinks(persistedData));
        }
      });

    return () => {
      isMounted = false;
    };
  }, []);

  useEffect(() => {
    if (!canOpenAdminApi) return;

    let isMounted = true;

    fetchControlPlaneSnapshot()
      .then((snapshot) => {
        if (isMounted) {
          setData(syncLauncherServiceLinks(snapshot.data));
        }
      })
      .catch((error: unknown) => {
        console.warn(error instanceof Error ? error.message : "Не удалось загрузить control-plane snapshot");
      });

    return () => {
      isMounted = false;
    };
  }, [canOpenAdminApi]);

  useEffect(() => {
    if (!adminOpen || !canOpenAdminApi) return;
    void refreshTaskManagerWorkspaces();
  }, [adminOpen, canOpenAdminApi]);

  const refreshRuntimeState = useCallback(async () => {
    try {
      const nextSession = await fetchAuthSession();

      setAuthSession(nextSession);

      if (!nextSession.authenticated) {
        setAuthApps([]);
        return;
      }

      const currentData = runtimeDataRef.current;
      const nextContext = resolveAuthenticatedContext(
        currentData,
        nextSession,
        runtimeProfileIdRef.current,
        runtimeClientIdRef.current
      );
      const nextMe = buildMe(currentData, nextContext.profileId, nextContext.clientId);
      const [persistedData, apps] = await Promise.all([
        nextSession.isSuperAdmin || nextMe.permissions.canOpenAdmin
          ? fetchControlPlaneSnapshot().then((snapshot) => snapshot.data)
          : loadPersistedLauncherData(),
        fetchAvailableApps(),
      ]);

      if (persistedData) {
        setData(syncLauncherServiceLinks(persistedData));
      }

      setAuthApps(apps);
    } catch (error: unknown) {
      console.warn(error instanceof Error ? error.message : "Не удалось обновить runtime состояние Launcher");
    }
  }, []);

  useEffect(() => {
    if (!authSession?.authenticated) return;

    let isMounted = true;

    const refreshMountedRuntimeState = async () => {
      await refreshRuntimeState();
      if (!isMounted) return;
    };

    const eventSource = new EventSource("/api/events");

    eventSource.addEventListener("nodedc-ready", () => {
      void refreshMountedRuntimeState();
    });

    eventSource.addEventListener("nodedc-runtime", () => {
      void refreshMountedRuntimeState();
    });

    eventSource.onerror = () => {
      console.warn("Launcher event stream disconnected; browser will retry automatically");
    };

    return () => {
      isMounted = false;
      eventSource.close();
    };
  }, [authSession?.authenticated, refreshRuntimeState]);

  useEffect(() => {
    if (!authSession?.authenticated) return;

    const refreshVisibleRuntimeState = () => {
      if (document.visibilityState === "visible") {
        void refreshRuntimeState();
      }
    };

    window.addEventListener("focus", refreshVisibleRuntimeState);
    document.addEventListener("visibilitychange", refreshVisibleRuntimeState);

    return () => {
      window.removeEventListener("focus", refreshVisibleRuntimeState);
      document.removeEventListener("visibilitychange", refreshVisibleRuntimeState);
    };
  }, [authSession?.authenticated, refreshRuntimeState]);

  function handleProfileChange(userId: string) {
    const profile = profileOptions.find((option) => option.userId === userId);
    setActiveProfileId(userId);
    setActiveClientId(profile?.defaultClientId ?? activeClientId);
    setAdminOpen(false);
  }

  function handleLaunch(service: LauncherServiceView) {
    if (!service.openUrl || !service.effectiveAccess.openEnabled) return;
    window.open(service.openUrl, "_blank", "noopener,noreferrer");
  }

  function handleServiceSelect(serviceId: string) {
    setSelectedServiceId((current) => (current === serviceId ? undefined : serviceId));
  }

  function handleStageStep(direction: "previous" | "next") {
    if (!launcherServices.length) return;

    setSelectedServiceId((current) => {
      const currentIndex = current ? launcherServices.findIndex((service) => service.id === current) : -1;
      const fallbackIndex = direction === "next" ? 0 : launcherServices.length - 1;
      const nextIndex =
        currentIndex === -1
          ? fallbackIndex
          : direction === "next"
            ? (currentIndex + 1) % launcherServices.length
            : (currentIndex - 1 + launcherServices.length) % launcherServices.length;

      return launcherServices[nextIndex]?.id;
    });
  }

  async function applyControlPlaneMutation(request: Promise<ControlPlaneMutationResult>): Promise<ControlPlaneMutationOutcome> {
    try {
      const result = await request;
      setData(syncLauncherServiceLinks(result.data));
      return { ok: true, data: result.data };
    } catch (error: unknown) {
      const message = error instanceof Error ? error.message : "Не удалось выполнить admin API операцию";
      console.warn(message);
      return { ok: false, message };
    }
  }

  async function refreshTaskManagerWorkspaces() {
    setTaskManagerWorkspacesLoading(true);
    setTaskManagerWorkspacesError(null);

    try {
      const result = await fetchAdminTaskManagerWorkspaces();
      setTaskManagerWorkspaces(result.workspaces ?? []);
    } catch (error: unknown) {
      setTaskManagerWorkspacesError(error instanceof Error ? error.message : "Не удалось загрузить workspace Operational Core");
    } finally {
      setTaskManagerWorkspacesLoading(false);
    }
  }

  function handleSetUserServiceAccess({ userId, serviceId, value }: SetUserServiceAccessCommand) {
    const assignmentKey = accessAssignmentKey(userId, serviceId);

    if (pendingAccessAssignments[assignmentKey]) {
      return;
    }

    setPendingAccessAssignments((current) => ({ ...current, [assignmentKey]: value }));
    setAdminUserServiceAccess({ userId, serviceId, value })
      .then((result) => {
        setData(syncLauncherServiceLinks(result.data));
      })
      .catch((error: unknown) => {
        console.warn(error instanceof Error ? error.message : "Не удалось выполнить admin API операцию");
      })
      .finally(() => {
        setPendingAccessAssignments((current) => {
          const { [assignmentKey]: _completed, ...rest } = current;
          return rest;
        });
      });
  }

  function handleSetTaskManagerWorkspaceMemberRole(command: { clientId: string; userId: string; role: TaskManagerWorkspaceMemberRole; workspaceSlug?: string }) {
    const membershipKey = `${command.clientId}:${command.userId}:${command.workspaceSlug ?? "primary"}`;

    if (pendingTaskManagerMemberships[membershipKey]) {
      return;
    }

    setPendingTaskManagerMemberships((current) => ({ ...current, [membershipKey]: true }));
    const request =
      command.role === "unset"
        ? removeAdminTaskManagerWorkspaceMembership({ clientId: command.clientId, userId: command.userId, workspaceSlug: command.workspaceSlug })
        : ensureAdminTaskManagerWorkspaceMembership({
            clientId: command.clientId,
            userId: command.userId,
            workspaceSlug: command.workspaceSlug,
            role: command.role,
            setLastWorkspace: true,
          });

    request
      .then((result) => {
        setData(syncLauncherServiceLinks(result.data));
      })
      .catch((error: unknown) => {
        console.warn(error instanceof Error ? error.message : "Не удалось синхронизировать Tasker");
      })
      .finally(() => {
        setPendingTaskManagerMemberships((current) => {
          const { [membershipKey]: _completed, ...rest } = current;
          return rest;
        });
      });
  }

  function handleSetTaskManagerProjectMemberRole(command: EnsureTaskManagerProjectMemberCommand) {
    const membershipKey = `${command.clientId}:${command.userId}:${command.workspaceSlug}:${command.projectId}`;

    if (pendingTaskManagerProjectMemberships[membershipKey]) {
      return;
    }

    setPendingTaskManagerProjectMemberships((current) => ({ ...current, [membershipKey]: true }));
    const request =
      command.role === "unset"
        ? removeAdminTaskManagerProjectMembership({
            clientId: command.clientId,
            userId: command.userId,
            workspaceSlug: command.workspaceSlug,
            projectId: command.projectId,
          })
        : ensureAdminTaskManagerProjectMembership({
            clientId: command.clientId,
            userId: command.userId,
            workspaceSlug: command.workspaceSlug,
            projectId: command.projectId,
            role: command.role,
          });

    request
      .then((result) => {
        setData(syncLauncherServiceLinks(result.data));
      })
      .catch((error: unknown) => {
        console.warn(error instanceof Error ? error.message : "Не удалось синхронизировать проект Tasker");
      })
      .finally(() => {
        setPendingTaskManagerProjectMemberships((current) => {
          const { [membershipKey]: _completed, ...rest } = current;
          return rest;
        });
      });
  }

  function handleCreateInvite(invite: Pick<Invite, "clientId" | "email" | "role">) {
    applyControlPlaneMutation(createAdminInvite(invite));
  }

  async function handleAcceptInvite() {
    if (!inviteToken || inviteFlow?.status !== "ready") return;

    setInviteFlow({ status: "accepting", payload: inviteFlow.payload });

    try {
      const result = await acceptInvite(inviteToken);
      setData(syncLauncherServiceLinks(result.data));
      if (result.redirectUrl && result.redirectUrl !== "/") {
        window.location.assign(result.redirectUrl);
        return;
      }
      setInviteFlow({ status: "accepted", payload: inviteFlow.payload });
    } catch (error) {
      setInviteFlow({
        status: "error",
        payload: inviteFlow.payload,
        message: error instanceof Error ? error.message : "Не удалось принять инвайт",
      });
    }
  }

  async function handleRegisterInvite(command: RegisterInviteCommand) {
    if (!inviteToken || !inviteFlow || !("payload" in inviteFlow) || !inviteFlow.payload || (inviteFlow.status !== "ready" && inviteFlow.status !== "error")) return;

    const payload = inviteFlow.payload;

    setInviteFlow({ status: "registering", payload });

    try {
      const result = await registerInvite(inviteToken, command);

      setData(syncLauncherServiceLinks(result.data));
      setInviteFlow({ status: "registered", payload, loginUrl: result.loginUrl });
      window.location.replace(result.redirectUrl || "/");
    } catch (error) {
      setInviteFlow({
        status: "error",
        message: error instanceof Error ? error.message : "Не удалось зарегистрироваться по инвайту",
        payload,
      });
    }
  }

  function handleUpdateInvite(inviteId: string, patch: Partial<Invite>) {
    applyControlPlaneMutation(updateAdminInvite(inviteId, patch));
  }

  function handleDeleteInvite(inviteId: string) {
    applyControlPlaneMutation(deleteAdminInvite(inviteId));
  }

  function handleUpdateAccessRequest(accessRequestId: string, patch: Parameters<typeof updateAdminAccessRequest>[1]) {
    applyControlPlaneMutation(updateAdminAccessRequest(accessRequestId, patch));
  }

  function handleApproveAccessRequest(accessRequestId: string, patch: Parameters<typeof approveAdminAccessRequest>[1]) {
    applyControlPlaneMutation(approveAdminAccessRequest(accessRequestId, patch));
  }

  function handleRejectAccessRequest(accessRequestId: string, patch: Parameters<typeof rejectAdminAccessRequest>[1]) {
    applyControlPlaneMutation(rejectAdminAccessRequest(accessRequestId, patch));
  }

  function handleApproveTaskerInviteRequest(
    taskerInviteRequestId: string,
    patch: Parameters<typeof approveAdminTaskerInviteRequest>[1]
  ) {
    applyControlPlaneMutation(approveAdminTaskerInviteRequest(taskerInviteRequestId, patch));
  }

  function handleRejectTaskerInviteRequest(
    taskerInviteRequestId: string,
    patch: Parameters<typeof rejectAdminTaskerInviteRequest>[1]
  ) {
    applyControlPlaneMutation(rejectAdminTaskerInviteRequest(taskerInviteRequestId, patch));
  }

  function handleRetrySync(syncId: string) {
    applyControlPlaneMutation(retryAdminSync(syncId));
  }

  function handleUpdateSettings(patch: Partial<LauncherSettings>) {
    return applyControlPlaneMutation(updateAdminSettings(patch));
  }

  function handleUpdateService(serviceId: string, patch: Partial<Service>) {
    return applyControlPlaneMutation(updateAdminService(serviceId, patch));
  }

  function handleCreateClient() {
    const index = data.clients.length + 1;

    applyControlPlaneMutation(
      createAdminClient({
        type: "company",
        name: `Новый клиент ${index}`,
        legalName: `Новый клиент ${index}`,
        status: "demo",
        demoEndsAt: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000).toISOString(),
        contactName: "",
        contactEmail: "",
        notes: "",
      })
    );
  }

  function handleUpdateClient(clientId: string, patch: Partial<Client>) {
    applyControlPlaneMutation(updateAdminClient(clientId, patch));
  }

  function handleDeleteClient(clientId: string) {
    const nextClientId = data.clients.find((client) => client.id !== clientId)?.id ?? activeClientId;

    applyControlPlaneMutation(deleteAdminClient(clientId));

    if (activeClientId === clientId) {
      setActiveClientId(nextClientId);
    }
  }

  function handleUpdateUser(userId: string, patch: Partial<LauncherUser>) {
    applyControlPlaneMutation(updateAdminUserProfile(userId, patch));
  }

  function handleDeleteUser(userId: string) {
    applyControlPlaneMutation(deleteAdminUser(userId));
  }

  async function handleUpdateOwnProfile(patch: Partial<LauncherUser>) {
    const result = await updateOwnProfile(patch);
    setData(syncLauncherServiceLinks(result.data));
  }

  async function handleUpdateOwnPassword(newPassword: string) {
    const result = await updateOwnPassword(newPassword);
    setData(syncLauncherServiceLinks(result.data));
  }

  function handleCreateUser(command: CreateUserCommand) {
    createAdminUser(command)
      .then((result) => {
        setData(syncLauncherServiceLinks(result.data));

        if (result.provisioning?.temporaryPassword) {
          window.alert(`Пользователь создан. Временный пароль: ${result.provisioning.temporaryPassword}`);
        }
      })
      .catch((error: unknown) => {
        console.warn(error instanceof Error ? error.message : "Не удалось создать пользователя");
      });
  }

  function handleUpdateMembership(membershipId: string, patch: Partial<ClientMembership>) {
    applyControlPlaneMutation(updateAdminMembership(membershipId, patch));
  }

  function handleDeleteMembership(membershipId: string) {
    applyControlPlaneMutation(deleteAdminMembership(membershipId));
  }

  function handleCreateGroup(clientId: string) {
    applyControlPlaneMutation(createAdminGroup({ clientId, name: "Новая группа", description: "Описание группы", memberIds: [] }));
  }

  function handleUpdateGroup(groupId: string, patch: Partial<ClientGroup>) {
    applyControlPlaneMutation(updateAdminGroup(groupId, patch));
  }

  function handleDeleteGroup(groupId: string) {
    applyControlPlaneMutation(deleteAdminGroup(groupId));
  }

  function handleReorderServices(orderedServiceIds: string[]) {
    applyControlPlaneMutation(reorderAdminServices(orderedServiceIds));
  }

  function handleCreateService() {
    applyControlPlaneMutation(createAdminService());
  }

  function handleDeleteService(serviceId: string) {
    applyControlPlaneMutation(deleteAdminService(serviceId));

    setSelectedServiceId((current) => (current === serviceId ? undefined : current));
  }

  if (isAccessRequestRoute) {
    return (
      <AccessRequestScreen
        onSubmit={createAccessRequest}
        onLogin={() => redirectToLogin(authSession?.authenticated ? "/auth/login?prompt=login" : authSession?.loginUrl)}
      />
    );
  }

  if (inviteToken) {
    return (
      <InviteFlowScreen
        state={inviteFlow ?? { status: "loading" }}
        authenticatedEmail={authSession?.authenticated ? authSession.user.email : null}
        onAccept={() => void handleAcceptInvite()}
        onRegister={(command) => void handleRegisterInvite(command)}
        onLogin={() => redirectToLogin(authSession?.authenticated ? "/auth/login?prompt=login" : authSession?.loginUrl)}
        onSwitchAccount={() => {
          const returnTo = `${window.location.pathname}${window.location.search}${window.location.hash}`;
          window.location.replace(`/auth/logout?global=1&returnTo=${encodeURIComponent(returnTo)}`);
        }}
        onGoHome={() => {
          window.history.replaceState(null, "", "/");
          window.location.replace("/");
        }}
      />
    );
  }

  if (!authSession) {
    return null;
  }

  if (!authSession.authenticated) {
    return null;
  }

  const handleLogout = () => {
    window.location.replace(authSession.logoutUrl);
  };

  if (currentAccessRequest) {
    return <AccessRequestPendingScreen accessRequest={currentAccessRequest} onLogout={handleLogout} />;
  }

  return (
    <div className="launcher-app">
      <TopBar
        me={runtimeMe}
        clients={data.clients}
        profileOptions={profileOptions}
        activeProfileId={resolvedProfileId}
        activeClientId={resolvedClientId}
        adminOpen={adminOpen}
        adminMode={runtimeMe.launcherRole === "root_admin" ? adminMode : "admin"}
        onProfileChange={handleProfileChange}
        onClientChange={setActiveClientId}
        onOpenAdmin={() => {
          setAdminMode("admin");
          setAdminOpen((current) => !(current && adminMode === "admin"));
        }}
        onOpenPlatform={() => {
          if (runtimeMe.launcherRole !== "root_admin") return;
          setAdminMode("platform");
          setAdminOpen((current) => !(current && adminMode === "platform"));
        }}
        onOpenShowcase={() => setAdminOpen(false)}
        onOpenProfileSettings={() => setProfileSettingsOpen(true)}
        onLogout={handleLogout}
        brandLinkUrl={data.settings.brand.logoLinkUrl}
      />

      <main className="launcher-main">
        <ServiceStage
          service={selectedService}
          hasServices={launcherServices.length > 0}
          onLaunch={handleLaunch}
          onSelectPrevious={() => handleStageStep("previous")}
          onSelectNext={() => handleStageStep("next")}
        />
        {adminOpen && me.permissions.canOpenAdmin ? (
          <AdminOverlay
            data={data}
            me={runtimeMe}
            mode={runtimeMe.launcherRole === "root_admin" ? adminMode : "admin"}
            activeClientId={resolvedClientId}
            onClose={() => setAdminOpen(false)}
            onSetUserServiceAccess={handleSetUserServiceAccess}
            onCreateInvite={handleCreateInvite}
            onUpdateInvite={handleUpdateInvite}
            onDeleteInvite={handleDeleteInvite}
            onUpdateAccessRequest={handleUpdateAccessRequest}
            onApproveAccessRequest={handleApproveAccessRequest}
            onRejectAccessRequest={handleRejectAccessRequest}
            onApproveTaskerInviteRequest={handleApproveTaskerInviteRequest}
            onRejectTaskerInviteRequest={handleRejectTaskerInviteRequest}
            onRetrySync={handleRetrySync}
            onCreateClient={handleCreateClient}
            onUpdateClient={handleUpdateClient}
            onDeleteClient={handleDeleteClient}
            onCreateUser={handleCreateUser}
            onUpdateUser={handleUpdateUser}
            onDeleteUser={handleDeleteUser}
            onUpdateMembership={handleUpdateMembership}
            onDeleteMembership={handleDeleteMembership}
            pendingAccessAssignments={pendingAccessAssignments}
            onCreateGroup={handleCreateGroup}
            onUpdateGroup={handleUpdateGroup}
            onDeleteGroup={handleDeleteGroup}
            onUpdateService={handleUpdateService}
            onReorderServices={handleReorderServices}
            onCreateService={handleCreateService}
            onDeleteService={handleDeleteService}
            onUpdateSettings={handleUpdateSettings}
            taskManagerWorkspaces={taskManagerWorkspaces}
            taskManagerWorkspacesLoading={taskManagerWorkspacesLoading}
            taskManagerWorkspacesError={taskManagerWorkspacesError}
            pendingTaskManagerMemberships={pendingTaskManagerMemberships}
            pendingTaskManagerProjectMemberships={pendingTaskManagerProjectMemberships}
            onRefreshTaskManagerWorkspaces={() => void refreshTaskManagerWorkspaces()}
            onSetTaskManagerWorkspaceMemberRole={handleSetTaskManagerWorkspaceMemberRole}
            onSetTaskManagerProjectMemberRole={handleSetTaskManagerProjectMemberRole}
          />
        ) : null}
        {profileSettingsOpen && activeProfileUser ? (
          <ProfileSettingsPanel
            user={activeProfileUser}
            onClose={() => setProfileSettingsOpen(false)}
            onSaveProfile={handleUpdateOwnProfile}
            onChangePassword={handleUpdateOwnPassword}
          />
        ) : null}
        <ServiceRail services={launcherServices} selectedServiceId={selectedServiceId} onSelect={handleServiceSelect} />
      </main>
    </div>
  );
}

function syncLauncherServiceLinks(data: Partial<LauncherData>): LauncherData {
  const normalizedData = normalizeLauncherData(data);

  return {
    ...normalizedData,
    services: normalizedData.services.map(syncServiceLaunchLink),
  };
}

function accessAssignmentKey(userId: string, serviceId: string) {
  return `${userId}:${serviceId}`;
}

function AccessRequestScreen({
  onSubmit,
  onLogin,
}: {
  onSubmit: (command: CreateAccessRequestCommand) => Promise<CreateAccessRequestResponse>;
  onLogin: () => void;
}) {
  const [values, setValues] = useState<CreateAccessRequestCommand & { passwordConfirm: string }>({
    email: "",
    firstName: "",
    lastName: "",
    middleName: "",
    phone: "",
    company: "",
    password: "",
    passwordConfirm: "",
  });
  const [status, setStatus] = useState<"idle" | "submitting" | "submitted" | "error">("idle");
  const [message, setMessage] = useState<string | null>(null);
  const isSubmitted = status === "submitted";
  const normalizedEmail = values.email.trim().toLowerCase();
  const passwordMismatch = Boolean(values.passwordConfirm && values.password !== values.passwordConfirm);
  const canSubmit = Boolean(
    normalizedEmail.includes("@") &&
      values.firstName.trim() &&
      values.lastName.trim() &&
      values.middleName.trim() &&
      values.phone.trim() &&
      values.company.trim() &&
      values.password.length >= 8 &&
      values.password === values.passwordConfirm &&
      status !== "submitting"
  );

  const updateField = (field: keyof typeof values, value: string) => {
    setValues((current) => ({ ...current, [field]: value }));
  };

  return (
    <div className="launcher-app nodedc-auth-page">
      <NodeDcAuthBrandHeader />
      <main className="nodedc-auth-page__main">
        <section className="nodedc-auth-card nodedc-access-request-card" aria-live="polite">
          <div className="nodedc-auth-card__copy">
            <h1>NODE.DC.</h1>
            <p>{isSubmitted ? "Вы запросили доступ." : "Работайте во всех измерениях."}</p>
          </div>

          {!isSubmitted ? (
            <p className="nodedc-auth-card__status">
              Заполните обязательные поля и задайте пароль. После подтверждения вы войдёте в NODE.DC по этой почте и паролю.
            </p>
          ) : null}
          {message ? <p className="nodedc-auth-card__status">{message}</p> : null}
          {passwordMismatch ? <p className="nodedc-auth-card__status">Пароли не совпадают.</p> : null}

          {isSubmitted ? (
            <div className="nodedc-auth-card__form">
              <button className="button button--primary" type="button" onClick={onLogin}>
                Войти в NODE.DC
              </button>
            </div>
          ) : (
            <form
              className="nodedc-auth-card__form"
              onSubmit={(event) => {
                event.preventDefault();
                if (!canSubmit) return;

                setStatus("submitting");
                setMessage(null);
                onSubmit({
                  email: normalizedEmail,
                  firstName: values.firstName.trim(),
                  lastName: values.lastName.trim(),
                  middleName: values.middleName.trim(),
                  phone: values.phone.trim(),
                  company: values.company.trim(),
                  password: values.password,
                })
                  .then(() => {
                    setStatus("submitted");
                    setMessage("Заявка отправлена администратору. После подтверждения войдите в NODE.DC по указанному паролю.");
                  })
                  .catch((error) => {
                    setStatus("error");
                    setMessage(error instanceof Error ? error.message : "Не удалось отправить заявку.");
                  });
              }}
            >
              <label className="nodedc-auth-card__field">
                <span>Эл. почта</span>
                <input
                  value={values.email}
                  type="email"
                  placeholder="email@company.ru"
                  autoComplete="email"
                  onChange={(event) => updateField("email", event.target.value)}
                />
              </label>
              <div className="nodedc-auth-card__field-grid">
                <label className="nodedc-auth-card__field">
                  <span>Фамилия</span>
                  <input
                    value={values.lastName}
                    placeholder="Иванов"
                    autoComplete="family-name"
                    onChange={(event) => updateField("lastName", event.target.value)}
                  />
                </label>
                <label className="nodedc-auth-card__field">
                  <span>Имя</span>
                  <input
                    value={values.firstName}
                    placeholder="Иван"
                    autoComplete="given-name"
                    onChange={(event) => updateField("firstName", event.target.value)}
                  />
                </label>
              </div>
              <label className="nodedc-auth-card__field">
                <span>Отчество</span>
                <input value={values.middleName} placeholder="Иванович" onChange={(event) => updateField("middleName", event.target.value)} />
              </label>
              <label className="nodedc-auth-card__field">
                <span>Телефон</span>
                <input
                  value={values.phone}
                  type="tel"
                  placeholder="+7 999 000-00-00"
                  autoComplete="tel"
                  onChange={(event) => updateField("phone", event.target.value)}
                />
              </label>
              <label className="nodedc-auth-card__field">
                <span>Компания</span>
                <input
                  value={values.company}
                  placeholder="Название компании"
                  autoComplete="organization"
                  onChange={(event) => updateField("company", event.target.value)}
                />
              </label>
              <div className="nodedc-auth-card__field-grid">
                <label className="nodedc-auth-card__field">
                  <span>Пароль</span>
                  <input
                    value={values.password}
                    type="password"
                    placeholder="Минимум 8 символов"
                    autoComplete="new-password"
                    onChange={(event) => updateField("password", event.target.value)}
                  />
                </label>
                <label className="nodedc-auth-card__field">
                  <span>Повторите пароль</span>
                  <input
                    value={values.passwordConfirm}
                    type="password"
                    placeholder="Ещё раз"
                    autoComplete="new-password"
                    onChange={(event) => updateField("passwordConfirm", event.target.value)}
                  />
                </label>
              </div>
              <button className="button button--primary" type="submit" disabled={!canSubmit}>
                {status === "submitting" ? "Отправляем заявку" : "Запросить доступ"}
              </button>
              <button className="button button--secondary" type="button" onClick={onLogin}>
                Уже есть аккаунт
              </button>
            </form>
          )}
        </section>
      </main>
    </div>
  );
}

function AccessRequestPendingScreen({
  accessRequest,
  onLogout,
}: {
  accessRequest: AccessRequest;
  onLogout: () => void;
}) {
  const isRejected = accessRequest.status === "rejected";

  return (
    <div className="launcher-app nodedc-auth-page">
      <NodeDcAuthBrandHeader />
      <main className="nodedc-auth-page__main">
        <section className="nodedc-auth-card nodedc-access-request-card" aria-live="polite">
          <div className="nodedc-auth-card__copy">
            <h1>NODE.DC.</h1>
            <p>{isRejected ? "Заявка отклонена." : "Заявка ожидает подтверждения."}</p>
          </div>
          <div className="nodedc-invite-card__details">
            <span>Почта: {accessRequest.email}</span>
            <span>Компания: {accessRequest.company}</span>
          </div>
          <p className="nodedc-auth-card__status">
            {isRejected
              ? "Администратор отклонил заявку. Если это ошибка, отправьте новый запрос или свяжитесь с NODE.DC."
              : "Администратор проверит данные. После подтверждения вы попадёте в Launcher без отдельной регистрации по инвайту."}
          </p>
          <div className="nodedc-auth-card__form">
            <button className="button button--primary" type="button" onClick={onLogout}>
              Вернуться ко входу
            </button>
          </div>
        </section>
      </main>
    </div>
  );
}

function resolveAuthenticatedContext(
  data: LauncherData,
  session: AuthenticatedSession,
  currentProfileId: string,
  currentClientId: string
): { profileId: string; clientId: string } {
  const sessionEmail = session.user.email?.toLowerCase();
  const sessionSub = session.user.sub;
  const profile =
    data.users.find(
      (user) =>
        (sessionSub && user.authentikUserId === sessionSub) ||
        (sessionEmail && user.email.toLowerCase() === sessionEmail)
    ) ??
    (session.isSuperAdmin ? data.users.find((user) => user.id === "user_root") : undefined) ??
    data.users.find((user) => user.id === currentProfileId) ??
    data.users[0];

  if (!profile) {
    return { profileId: currentProfileId, clientId: currentClientId };
  }

  return {
    profileId: profile.id,
    clientId: resolveDefaultClientId(data, profile.id, currentClientId),
  };
}

function resolveRuntimeProfileId(data: LauncherData, session: AuthSession | null, currentProfileId: string): string {
  if (data.users.some((user) => user.id === currentProfileId)) {
    return currentProfileId;
  }

  if (session?.authenticated) {
    const sessionEmail = session.user.email?.toLowerCase();
    const sessionSub = session.user.sub;
    const sessionUser = data.users.find(
      (user) =>
        (sessionSub && user.authentikUserId === sessionSub) ||
        (sessionEmail && user.email.toLowerCase() === sessionEmail)
    );

    if (sessionUser) {
      return sessionUser.id;
    }
  }

  return data.users[0]?.id ?? currentProfileId;
}

function resolveDefaultClientId(data: LauncherData, userId: string, requestedClientId: string): string {
  const user = data.users.find((item) => item.id === userId);
  const isRoot = user?.id === "user_root";
  const availableClientIds = isRoot
    ? data.clients.map((client) => client.id)
    : data.memberships.filter((membership) => membership.userId === userId && membership.status === "active").map((membership) => membership.clientId);

  if (requestedClientId && availableClientIds.includes(requestedClientId)) {
    return requestedClientId;
  }

  const defaultClientId = profileOptions.find((profile) => profile.userId === userId)?.defaultClientId;

  if (defaultClientId && availableClientIds.includes(defaultClientId)) {
    return defaultClientId;
  }

  return availableClientIds[0] ?? data.clients[0]?.id ?? requestedClientId;
}

function InviteFlowScreen({
  state,
  authenticatedEmail,
  onAccept,
  onRegister,
  onLogin,
  onSwitchAccount,
  onGoHome,
}: {
  state: InviteFlowState;
  authenticatedEmail: string | null;
  onAccept: () => void;
  onRegister: (command: RegisterInviteCommand) => void;
  onLogin: () => void;
  onSwitchAccount: () => void;
  onGoHome: () => void;
}) {
  const [email, setEmail] = useState("");
  const [name, setName] = useState("");
  const [password, setPassword] = useState("");
  const [passwordConfirm, setPasswordConfirm] = useState("");
  const payload = "payload" in state ? state.payload : undefined;
  const inviteStatus = payload?.invite.status;
  const inviteEmail = payload?.account.email ?? payload?.invite.email ?? "";
  const normalizedInviteEmail = inviteEmail.toLowerCase();
  const existingAccount = Boolean(payload?.account.exists);
  const isAuthenticated = Boolean(authenticatedEmail);
  const isAuthenticatedAsInvitee = Boolean(
    authenticatedEmail &&
      normalizedInviteEmail &&
      authenticatedEmail.toLowerCase() === normalizedInviteEmail
  );
  const isAuthenticatedAsDifferentUser = Boolean(
    authenticatedEmail &&
      normalizedInviteEmail &&
      authenticatedEmail.toLowerCase() !== normalizedInviteEmail
  );
  const isAccepting = state.status === "accepting";
  const isRegistering = state.status === "registering";
  const inviteTargetUrl = payload?.redirectUrl;
  const canOpenInviteTarget = Boolean(
    payload?.invite.source === "tasker_workspace_invite" &&
      inviteTargetUrl &&
      inviteTargetUrl !== "/" &&
      (state.status === "accepted" || inviteStatus === "accepted")
  );
  const requiresAccountSwitch = state.status === "error" && state.message.includes("другую почту");
  const canAccept = Boolean(
    state.status === "ready" &&
      isAuthenticatedAsInvitee &&
      inviteStatus !== "accepted" &&
      inviteStatus !== "expired" &&
      inviteStatus !== "revoked"
  );
  const isTerminalInvite = inviteStatus === "accepted" || inviteStatus === "expired" || inviteStatus === "revoked";
  const canShowRegistrationForm = Boolean(
    payload &&
      !isAuthenticated &&
      !existingAccount &&
      !isTerminalInvite &&
      (state.status === "ready" || state.status === "registering" || state.status === "error")
  );
  const passwordMismatch = Boolean(passwordConfirm && password !== passwordConfirm);
  const normalizedEmail = email.trim();
  const canRegister = Boolean(
    canShowRegistrationForm &&
      state.status !== "registering" &&
      normalizedEmail.includes("@") &&
      name.trim() &&
      password.length >= 8 &&
      password === passwordConfirm
  );
  const details = payload
    ? payload.invite.source === "tasker_workspace_invite"
      ? [
          `Контур: ${payload.client.name}`,
          `Workspace: ${payload.invite.sourceWorkspaceName ?? payload.invite.sourceWorkspaceSlug ?? "Operational Core"}`,
          `Роль: ${membershipRoleLabel(payload.invite.role)}`,
        ]
      : [
          `Рабочая область: ${payload.client.name}`,
          `Роль: ${membershipRoleLabel(payload.invite.role)}`,
        ]
    : ["Проверяем приглашение и платформенную сессию"];
  const statusMessage = resolveInviteStatusMessage(state, {
    existingAccount,
    inviteEmail,
    inviteStatus,
    isAuthenticated,
    isAuthenticatedAsInvitee,
    isAuthenticatedAsDifferentUser,
  });

  return (
    <div className="launcher-app nodedc-auth-page">
      <NodeDcAuthBrandHeader />
      <main className="nodedc-auth-page__main">
        <section className="nodedc-auth-card nodedc-invite-card" aria-live="polite">
          <div className="nodedc-auth-card__copy">
            <h1>Работайте во всех измерениях.</h1>
            <p>Приглашение в NODE.DC.</p>
          </div>

          <div className="nodedc-invite-card__details">
            {details.map((detail) => (
              <span key={detail}>{detail}</span>
            ))}
          </div>

          {statusMessage ? <p className="nodedc-auth-card__status">{statusMessage}</p> : null}
          {state.status === "error" ? <p className="nodedc-auth-card__status">{state.message}</p> : null}
          {passwordMismatch ? <p className="nodedc-auth-card__status">Пароли не совпадают.</p> : null}

          {canShowRegistrationForm ? (
            <form
              className="nodedc-auth-card__form"
              onSubmit={(event) => {
                event.preventDefault();
                if (!canRegister) return;
                onRegister({ email: normalizedEmail, name: name.trim(), password });
              }}
            >
              <label className="nodedc-auth-card__field">
                <span>Эл. почта</span>
                <input
                  value={email}
                  type="email"
                  placeholder="email@company.ru"
                  autoComplete="email"
                  onChange={(event) => setEmail(event.target.value)}
                />
              </label>
              <label className="nodedc-auth-card__field">
                <span>Имя</span>
                <input value={name} placeholder="Ваше имя" autoComplete="name" onChange={(event) => setName(event.target.value)} />
              </label>
              <label className="nodedc-auth-card__field">
                <span>Пароль</span>
                <input
                  value={password}
                  type="password"
                  placeholder="Минимум 8 символов"
                  autoComplete="new-password"
                  onChange={(event) => setPassword(event.target.value)}
                />
              </label>
              <label className="nodedc-auth-card__field">
                <span>Повторите пароль</span>
                <input
                  value={passwordConfirm}
                  type="password"
                  placeholder="Повторите пароль"
                  autoComplete="new-password"
                  onChange={(event) => setPasswordConfirm(event.target.value)}
                />
              </label>
              <button className="button button--primary" type="submit" disabled={!canRegister || isRegistering}>
                {isRegistering ? "Создаём аккаунт" : "Создать аккаунт"}
              </button>
              <button className="button button--secondary" type="button" onClick={onLogin}>
                Уже есть аккаунт
              </button>
            </form>
          ) : existingAccount && !isAuthenticated && !isTerminalInvite ? (
            <button className="button button--primary" type="button" onClick={onLogin}>
              Войти и принять приглашение
            </button>
          ) : (existingAccount && isAuthenticatedAsDifferentUser && !isTerminalInvite) || requiresAccountSwitch ? (
            <button className="button button--primary" type="button" onClick={onSwitchAccount}>
              Сменить аккаунт
            </button>
          ) : state.status === "registered" ? (
            <button className="button button--primary" type="button" onClick={() => redirectToLogin(state.loginUrl)}>
              Войти в NODE.DC
            </button>
          ) : state.status === "error" || state.status === "accepted" || isTerminalInvite ? (
            <button
              className="button button--primary"
              type="button"
              onClick={() => {
                if (canOpenInviteTarget && inviteTargetUrl) {
                  window.location.assign(inviteTargetUrl);
                  return;
                }
                onGoHome();
              }}
            >
              {canOpenInviteTarget ? "Перейти в workspace" : "Перейти в витрину"}
            </button>
          ) : (
            <button className="button button--primary" type="button" disabled={!canAccept || isAccepting} onClick={onAccept}>
              {state.status === "loading" ? "Проверяем" : isAccepting ? "Подключаем доступ" : "Принять приглашение"}
            </button>
          )}
        </section>
      </main>
    </div>
  );
}

function NodeDcAuthBrandHeader() {
  return (
    <header className="nodedc-auth-brand-shell">
      <a href="/" className="nodedc-expanded-brand-link" aria-label="NODE.DC">
        <img src="/nodedc-logo.svg" alt="NODE DC" className="nodedc-expanded-brand-logo" />
      </a>
    </header>
  );
}

function resolveInviteStatusMessage(
  state: InviteFlowState,
  context: {
    existingAccount: boolean;
    inviteEmail: string;
    inviteStatus?: Invite["status"];
    isAuthenticated: boolean;
    isAuthenticatedAsInvitee: boolean;
    isAuthenticatedAsDifferentUser: boolean;
  }
) {
  const {
    existingAccount,
    inviteEmail,
    inviteStatus,
    isAuthenticated,
    isAuthenticatedAsInvitee,
    isAuthenticatedAsDifferentUser,
  } = context;

  if (state.status === "loading") return "Проверяем приглашение.";
  if (state.status === "accepting") return "Подключаем доступ к рабочей области.";
  if (state.status === "registering") return "Создаём аккаунт и подключаем доступ.";
  if (state.status === "registered") return "Аккаунт создан. Теперь войдите в NODE.DC.";
  if (state.status === "accepted" || inviteStatus === "accepted") return "Доступ уже подключён.";
  if (inviteStatus === "expired") return "Срок действия приглашения истёк.";
  if (inviteStatus === "revoked") return "Приглашение отозвано.";
  if (existingAccount && !isAuthenticated) return `Аккаунт ${inviteEmail} уже есть в NODE.DC. Войдите под этой почтой, чтобы принять приглашение.`;
  if (existingAccount && isAuthenticatedAsDifferentUser) return `Сейчас открыт другой аккаунт. Смените пользователя и войдите под ${inviteEmail}.`;
  if (existingAccount && isAuthenticatedAsInvitee) return "Аккаунт найден. Подтвердите подключение к workspace.";
  if (!isAuthenticated) return "Введите почту, имя и пароль для регистрации по приглашению.";
  return null;
}

function AuthStateScreen({
  title,
  description,
  error,
  loginUrl,
}: {
  title: string;
  description: string;
  error?: string | null;
  loginUrl?: string;
}) {
  return (
    <div className="launcher-app">
      <main
        style={{
          display: "grid",
          minHeight: "100vh",
          placeItems: "center",
          padding: "2rem",
        }}
      >
        <section
          style={{
            display: "grid",
            width: "min(34rem, 100%)",
            gap: "1rem",
            padding: "2rem",
            borderRadius: "1.75rem",
            background: "rgba(255, 255, 255, 0.08)",
            textAlign: "center",
          }}
        >
          <img src="/nodedc-logo.svg" alt="NODE.DC" style={{ justifySelf: "center", width: "11rem" }} />
          <h1 style={{ margin: 0 }}>{title}</h1>
          <p style={{ margin: 0, color: "var(--text-secondary)", lineHeight: 1.5 }}>{description}</p>
          {error ? <p style={{ margin: 0, color: "var(--warning)", lineHeight: 1.45 }}>{error}</p> : null}
          {loginUrl ? (
            <button className="button button--primary" type="button" onClick={() => redirectToLogin(loginUrl)}>
              Войти
            </button>
          ) : null}
        </section>
      </main>
    </div>
  );
}

function parseInviteToken(pathname: string) {
  const match = /^\/invite\/([^/?#]+)\/?$/.exec(pathname);
  return match?.[1] ? decodeURIComponent(match[1]) : null;
}

function isAccessRequestPath(pathname: string) {
  return /^\/(?:request-access|access-request)\/?$/.test(pathname);
}

function membershipRoleLabel(role: ClientMembership["role"]) {
  return {
    client_owner: "Владелец клиента",
    client_admin: "Администратор клиента",
    member: "Участник",
  }[role];
}

function buildLoginRedirectUrl(loginUrl?: string) {
  const url = new URL(loginUrl || "/auth/login", window.location.origin);

  if (!url.searchParams.has("returnTo")) {
    const returnTo = `${window.location.pathname}${window.location.search}${window.location.hash}`;

    if (returnTo && returnTo !== "/") {
      url.searchParams.set("returnTo", returnTo);
    }
  }

  return url.origin === window.location.origin ? `${url.pathname}${url.search}${url.hash}` : url.toString();
}

function redirectToLogin(loginUrl?: string) {
  const redirectUrl = buildLoginRedirectUrl(loginUrl);
  const now = Date.now();

  if (lastAuthRedirect && now - lastAuthRedirect.startedAt < 1500) {
    return;
  }

  lastAuthRedirect = { url: redirectUrl, startedAt: now };
  window.location.replace(redirectUrl);
}
