import { useEffect, useMemo, 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 {
  createAdminClient,
  createAdminGroup,
  createAdminInvite,
  createAdminService,
  createAdminUser,
  deleteAdminClient,
  deleteAdminGroup,
  deleteAdminInvite,
  deleteAdminMembership,
  deleteAdminService,
  ensureAdminTaskManagerProjectMembership,
  ensureAdminTaskManagerWorkspaceMembership,
  fetchAdminTaskManagerWorkspaces,
  fetchControlPlaneSnapshot,
  reorderAdminServices,
  retryAdminSync,
  removeAdminTaskManagerProjectMembership,
  removeAdminTaskManagerWorkspaceMembership,
  setAdminUserServiceAccess,
  updateAdminClient,
  updateAdminGroup,
  updateAdminInvite,
  updateAdminMembership,
  updateAdminService,
  updateAdminSettings,
  updateAdminUserProfile,
  type ControlPlaneMutationResult,
  type TaskManagerWorkspaceMemberRole,
  type TaskManagerWorkspaceSummary,
} from "../shared/api/adminApi";
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 { 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 } 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 };

export function LauncherApp() {
  const inviteToken = useMemo(() => parseInviteToken(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 [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 me = useMemo(() => buildMe(data, activeProfileId, activeClientId), [data, activeProfileId, activeClientId]);
  const activeProfileUser = data.users.find((user) => user.id === activeProfileId) ?? data.users[0];
  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, activeProfileId, resolvedClientId);

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

      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 openEnabled = app.hasAccess && app.status === "active";

        return {
          ...service,
          title: app.title || service.title,
          description: app.description || service.description,
          openUrl: openEnabled ? app.openUrl || app.url || service.openUrl : null,
          userAccess: openEnabled ? ("allowed" as const) : ("denied" as const),
          effectiveAccess: {
            ...service.effectiveAccess,
            allowed: app.hasAccess,
            visible: true,
            openEnabled,
            reason: app.accessReason || (app.hasAccess ? "Доступ подтверждён" : "Нет доступа"),
          },
        };
      });
    },
    [authApps, authAppsBySlug, authSession, data, activeProfileId, 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) return;

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

  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) {
            redirectToLogin(session.loginUrl);
            return;
          }

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

    window.addEventListener("pageshow", validateRestoredSession);

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

  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]);

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

    let isMounted = true;

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

        if (!isMounted) return;

        setAuthSession(nextSession);

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

        const nextContext = resolveAuthenticatedContext(data, nextSession, activeProfileId, activeClientId);
        const nextMe = buildMe(data, nextContext.profileId, nextContext.clientId);
        const [persistedData, apps] = await Promise.all([
          nextMe.permissions.canOpenAdmin
            ? fetchControlPlaneSnapshot().then((snapshot) => snapshot.data)
            : loadPersistedLauncherData(),
          fetchAvailableApps(),
        ]);

        if (!isMounted) return;

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

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

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

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

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

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

  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;
    });
  }

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

  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));
      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 handleRetrySync(syncId: string) {
    applyControlPlaneMutation(retryAdminSync(syncId));
  }

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

  function handleUpdateService(serviceId: string, patch: Partial<Service>) {
    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));
  }

  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 (inviteToken) {
    return (
      <InviteFlowScreen
        state={inviteFlow ?? { status: "loading" }}
        isAuthenticated={Boolean(authSession?.authenticated)}
        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);
  };

  return (
    <div className="launcher-app">
      <TopBar
        me={runtimeMe}
        clients={data.clients}
        profileOptions={profileOptions}
        activeProfileId={activeProfileId}
        activeClientId={resolvedClientId}
        adminOpen={adminOpen}
        onProfileChange={handleProfileChange}
        onClientChange={setActiveClientId}
        onToggleAdmin={() => setAdminOpen((current) => !current)}
        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}
            activeClientId={resolvedClientId}
            onClose={() => setAdminOpen(false)}
            onSetUserServiceAccess={handleSetUserServiceAccess}
            onCreateInvite={handleCreateInvite}
            onUpdateInvite={handleUpdateInvite}
            onDeleteInvite={handleDeleteInvite}
            onRetrySync={handleRetrySync}
            onCreateClient={handleCreateClient}
            onUpdateClient={handleUpdateClient}
            onDeleteClient={handleDeleteClient}
            onCreateUser={handleCreateUser}
            onUpdateUser={handleUpdateUser}
            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 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 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,
  isAuthenticated,
  onAccept,
  onRegister,
  onLogin,
  onSwitchAccount,
  onGoHome,
}: {
  state: InviteFlowState;
  isAuthenticated: boolean;
  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 isAccepting = state.status === "accepting";
  const isRegistering = state.status === "registering";
  const requiresAccountSwitch = state.status === "error" && state.message.includes("другую почту");
  const canAccept = Boolean(
    state.status === "ready" &&
      isAuthenticated &&
      inviteStatus !== "accepted" &&
      inviteStatus !== "expired" &&
      inviteStatus !== "revoked"
  );
  const isTerminalInvite = inviteStatus === "accepted" || inviteStatus === "expired" || inviteStatus === "revoked";
  const canShowRegistrationForm = Boolean(
    payload &&
      !isAuthenticated &&
      !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.client.name}`,
        `Роль: ${membershipRoleLabel(payload.invite.role)}`,
      ]
    : ["Проверяем приглашение и платформенную сессию"];
  const statusMessage = resolveInviteStatusMessage(state, isAuthenticated, inviteStatus);

  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>
          ) : 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={onGoHome}>
              Перейти в витрину
            </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, isAuthenticated: boolean, inviteStatus?: Invite["status"]) {
  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 (!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 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);
}
