import * as plugins from './plugins.js';
import * as interfaces from '../ts_interfaces/index.js';
import { RequestScheduler } from './classes.requestscheduler.js';
import {
  completeResourceLoad,
  createIdleResourceState,
  failResourceLoad,
  startResourceLoad,
  type IResourceState,
} from './classes.resourcestate.js';
import {
  isAuthenticatedSessionState,
  isSessionGenerationCurrent,
  normalizePersistedSession,
  verifiedIdentityFromResponse,
  type ISessionState,
} from './classes.sessionstate.js';
import {
  isProtectedRequestCurrent,
  LatestIntentTracker,
  mergeActionState,
  MutationDomainScheduler,
} from './classes.protectedrequest.js';

// ============================================================================
// Smartstate instance
// ============================================================================
export const appState = new plugins.domtools.plugins.smartstate.Smartstate();
export const requestScheduler = new RequestScheduler();
const latestIntentTracker = new LatestIntentTracker();
const mutationDomainScheduler = new MutationDomainScheduler();

export type TProtectedExecutionPolicy =
  | { mode: 'independent' }
  | { mode: 'latest' }
  | { mode: 'mutation'; domain: string };

const independentPolicy: TProtectedExecutionPolicy = { mode: 'independent' };
const protectedActionPolicies = new WeakMap<object, TProtectedExecutionPolicy>();

const registerProtectedActionPolicy = (
  actionArg: object,
  policyArg: TProtectedExecutionPolicy,
): void => {
  protectedActionPolicies.set(actionArg, policyArg);
};

const runWithExecutionPolicy = <TResult>(
  policyArg: TProtectedExecutionPolicy,
  taskArg: () => Promise<TResult>,
): Promise<TResult> => policyArg.mode === 'mutation'
  ? mutationDomainScheduler.run(policyArg.domain, taskArg)
  : taskArg();

// ============================================================================
// State Part Interfaces
// ============================================================================

export interface ILoginState extends ISessionState {}

export interface ISystemState {
  status: interfaces.data.ISystemStatus | null;
  resource: IResourceState;
}

export interface IServicesState {
  services: interfaces.data.IService[];
  currentService: interfaces.data.IService | null;
  currentHostedAppLifecycle: interfaces.data.IHostedAppLifecycleState | null;
  currentServiceLogs: interfaces.data.ILogEntry[];
  currentServiceStats: interfaces.data.IContainerStats | null;
  platformServices: interfaces.data.IPlatformService[];
  currentPlatformService: interfaces.data.IPlatformService | null;
  currentPlatformServiceStats: interfaces.data.IContainerStats | null;
  currentPlatformServiceLogs: interfaces.data.ILogEntry[];
}

export interface INetworkState {
  targets: interfaces.data.INetworkTarget[];
  stats: interfaces.data.INetworkStats | null;
  statsResource: IResourceState;
  trafficStats: interfaces.data.ITrafficStats | null;
  dnsRecords: interfaces.data.IDnsRecord[];
  domains: interfaces.data.IDomainDetail[];
  gatewayDomains: interfaces.data.IGatewayDomain[];
  gatewayDnsRecords: interfaces.data.IGatewayDnsRecord[];
  certificates: interfaces.data.ICertificate[];
}

export interface IRegistriesState {
  tokens: interfaces.data.IRegistryToken[];
  registryStatus: { running: boolean; port: number } | null;
}

export interface IBackupsState {
  backups: interfaces.data.IBackup[];
  schedules: interfaces.data.IBackupSchedule[];
}

export interface ISettingsState {
  settings: interfaces.data.ISettings | null;
  backupPasswordConfigured: boolean;
  managedDcRouterStatus: interfaces.data.IManagedDcRouterStatus | null;
  storageTargets: interfaces.data.IStorageTarget[];
  storageClasses: interfaces.data.IStorageClass[];
}

export interface IAppStoreState {
  apps: interfaces.requests.IAppStoreApp[];
  sourceInfo: interfaces.requests.IAppStoreSourceInfo | null;
  upgradeableServices: interfaces.requests.IUpgradeableAppStoreService[];
  upgradeOperations: interfaces.requests.IAppStoreUpgradeOperation[];
}

export interface IUiState {
  activeView: string;
  activeSubview: string | null;
  autoRefresh: boolean;
  refreshInterval: number;
  pendingAppTemplate?: any;
}

// ============================================================================
// State Parts
// ============================================================================

export const loginStatePart = await appState.getStatePart<ILoginState>(
  'login',
  {
    identity: null,
    phase: 'anonymous',
    generation: 0,
    verificationError: null,
  },
  'persistent',
);

const persistedLoginState = loginStatePart.getState() as
  | (ILoginState & { isLoggedIn?: boolean })
  | undefined;
if (!persistedLoginState) {
  throw new Error('Login state was not initialized');
}
await loginStatePart.setState(normalizePersistedSession(
  persistedLoginState,
  requestScheduler.invalidate(),
));

export const systemStatePart = await appState.getStatePart<ISystemState>(
  'system',
  {
    status: null,
    resource: createIdleResourceState(),
  },
  'soft',
);

export const servicesStatePart = await appState.getStatePart<IServicesState>(
  'services',
  {
    services: [],
    currentService: null,
    currentHostedAppLifecycle: null,
    currentServiceLogs: [],
    currentServiceStats: null,
    platformServices: [],
    currentPlatformService: null,
    currentPlatformServiceStats: null,
    currentPlatformServiceLogs: [],
  },
  'soft',
);

export const networkStatePart = await appState.getStatePart<INetworkState>(
  'network',
  {
    targets: [],
    stats: null,
    statsResource: createIdleResourceState(),
    trafficStats: null,
    dnsRecords: [],
    domains: [],
    gatewayDomains: [],
    gatewayDnsRecords: [],
    certificates: [],
  },
  'soft',
);

export const registriesStatePart = await appState.getStatePart<IRegistriesState>(
  'registries',
  {
    tokens: [],
    registryStatus: null,
  },
  'soft',
);

export const backupsStatePart = await appState.getStatePart<IBackupsState>(
  'backups',
  {
    backups: [],
    schedules: [],
  },
  'soft',
);

export const settingsStatePart = await appState.getStatePart<ISettingsState>(
  'settings',
  {
    settings: null,
    backupPasswordConfigured: false,
    managedDcRouterStatus: null,
    storageTargets: [],
    storageClasses: [],
  },
  'soft',
);

export const appStoreStatePart = await appState.getStatePart<IAppStoreState>(
  'appStore',
  {
    apps: [],
    sourceInfo: null,
    upgradeableServices: [],
    upgradeOperations: [],
  },
  'soft',
);

export const uiStatePart = await appState.getStatePart<IUiState>(
  'ui',
  {
    activeView: 'dashboard',
    activeSubview: null,
    autoRefresh: true,
    refreshInterval: 30000,
  },
);

// ============================================================================
// Helpers
// ============================================================================

interface IActionContext {
  identity: interfaces.data.IIdentity;
  generation: number;
}

const protectedActionIds = new WeakMap<object, number>();
let nextProtectedActionId = 1;
const protectedActionTimeoutMs = 120_000;

const getProtectedActionId = (actionArg: object): number => {
  let id = protectedActionIds.get(actionArg);
  if (id === undefined) {
    id = nextProtectedActionId++;
    protectedActionIds.set(actionArg, id);
  }
  return id;
};

const hashPayload = (payloadArg: unknown): string => {
  const value = JSON.stringify(payloadArg) ?? String(payloadArg);
  let hash = 2166136261;
  for (let index = 0; index < value.length; index++) {
    hash ^= value.charCodeAt(index);
    hash = Math.imul(hash, 16777619);
  }
  return (hash >>> 0).toString(36);
};

export const getRequiredState = <TState>(statePartArg: { getState: () => TState | undefined }): TState => {
  const state = statePartArg.getState();
  if (state === undefined) {
    throw new Error('State part has not been initialized');
  }
  return state;
};

const logsStringToEntries = (logsArg: string): interfaces.data.ILogEntry[] => {
  return logsArg
    .split('\n')
    .filter((line) => line.trim().length > 0)
    .map((line, index) => {
      const timestampMatch = line.match(/^(\d{4}-\d{2}-\d{2}T[\d:.]+Z?)\s+(.*)$/);
      const message = timestampMatch ? timestampMatch[2] : line;
      const messageLower = message.toLowerCase();
      const level = messageLower.includes('error') || messageLower.includes('fatal')
        ? 'error'
        : messageLower.includes('warn')
          ? 'warn'
          : 'info';
      return {
        id: index,
        serviceId: 0,
        timestamp: timestampMatch ? new Date(timestampMatch[1]).getTime() : Date.now(),
        message,
        level,
        source: 'stdout',
      };
    });
};

const getActionContext = (): IActionContext => {
  const loginState = getRequiredState(loginStatePart);
  if (
    loginState.phase !== 'authenticated'
    || !loginState.identity
    || loginState.identity.expiresAt <= Date.now()
  ) {
    throw new Error('Authenticated session required');
  }
  return { identity: loginState.identity, generation: loginState.generation };
};

export const isAuthenticatedSession = (): boolean =>
  isAuthenticatedSessionState(getRequiredState(loginStatePart));

const errorMessage = (errorArg: unknown): string =>
  errorArg instanceof Error ? errorArg.message : String(errorArg);

const withTimeout = async <T>(
  promiseArg: Promise<T>,
  timeoutMsArg: number,
  messageArg: string,
): Promise<T> => {
  let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
  try {
    return await Promise.race([
      promiseArg,
      new Promise<never>((_resolve, reject) => {
        timeoutHandle = setTimeout(() => reject(new Error(messageArg)), timeoutMsArg);
      }),
    ]);
  } finally {
    if (timeoutHandle) clearTimeout(timeoutHandle);
  }
};

const rpcErrorCode = (errorArg: unknown): interfaces.data.TRpcErrorCode | null => {
  if (!errorArg || typeof errorArg !== 'object') return null;
  const errorData = (errorArg as { errorData?: unknown }).errorData;
  if (!errorData || typeof errorData !== 'object') return null;
  const code = (errorData as { code?: unknown }).code;
  return ['AUTH_REQUIRED', 'AUTH_INVALID', 'AUTH_EXPIRED', 'FORBIDDEN'].includes(String(code))
    ? code as interfaces.data.TRpcErrorCode
    : null;
};

const clearProtectedState = async (): Promise<void> => {
  await Promise.all([
    systemStatePart.setState({ status: null, resource: createIdleResourceState() }),
    servicesStatePart.setState({
      services: [],
      currentService: null,
      currentHostedAppLifecycle: null,
      currentServiceLogs: [],
      currentServiceStats: null,
      platformServices: [],
      currentPlatformService: null,
      currentPlatformServiceStats: null,
      currentPlatformServiceLogs: [],
    }),
    networkStatePart.setState({
      targets: [],
      stats: null,
      statsResource: createIdleResourceState(),
      trafficStats: null,
      dnsRecords: [],
      domains: [],
      gatewayDomains: [],
      gatewayDnsRecords: [],
      certificates: [],
    }),
    registriesStatePart.setState({ tokens: [], registryStatus: null }),
    backupsStatePart.setState({ backups: [], schedules: [] }),
    settingsStatePart.setState({
      settings: null,
      backupPasswordConfigured: false,
      managedDcRouterStatus: null,
      storageTargets: [],
      storageClasses: [],
    }),
    appStoreStatePart.setState({
      apps: [],
      sourceInfo: null,
      upgradeableServices: [],
      upgradeOperations: [],
    }),
  ]);
};

// ============================================================================
// Login Actions
// ============================================================================

let sessionTransition: Promise<void> = Promise.resolve();
let sessionExpiryTimer: ReturnType<typeof setTimeout> | null = null;
const maxSessionExpiryDelayMs = 2_147_000_000;

const clearSessionExpiryTimer = (): void => {
  if (!sessionExpiryTimer) return;
  clearTimeout(sessionExpiryTimer);
  sessionExpiryTimer = null;
};

const scheduleSessionExpiry = (
  identityArg: interfaces.data.IIdentity,
  generationArg: number,
): void => {
  clearSessionExpiryTimer();
  const scheduleNextCheck = (): void => {
    const remainingMs = identityArg.expiresAt - Date.now();
    const delayMs = Math.max(0, Math.min(remainingMs, maxSessionExpiryDelayMs));
    sessionExpiryTimer = setTimeout(() => {
      sessionExpiryTimer = null;
      const current = getRequiredState(loginStatePart);
      if (current.generation !== generationArg || current.identity?.jwt !== identityArg.jwt) return;
      if (identityArg.expiresAt > Date.now()) {
        scheduleNextCheck();
        return;
      }
      void logoutForSessionGeneration(generationArg);
    }, delayMs);
  };
  scheduleNextCheck();
};

const runSessionTransition = (transitionArg: () => Promise<void>): Promise<void> => {
  const nextTransition = sessionTransition.then(transitionArg, transitionArg);
  sessionTransition = nextTransition.catch(() => {});
  return nextTransition;
};

const acceptIdentityTransition = async (
  identityArg: interfaces.data.IIdentity,
): Promise<void> => {
  if (!identityArg.jwt || identityArg.expiresAt <= Date.now()) {
    throw new Error('Cannot establish an expired identity');
  }
  clearSessionExpiryTimer();
  const generation = requestScheduler.invalidate();
  await loginStatePart.setState({
    identity: identityArg,
    phase: 'verifying',
    generation,
    verificationError: null,
  });
  scheduleSessionExpiry(identityArg, generation);
  await disconnectSocket();
  await loginStatePart.setState({
    identity: identityArg,
    phase: 'authenticated',
    generation,
    verificationError: null,
  });
};

export const acceptIdentity = async (identityArg: interfaces.data.IIdentity): Promise<void> =>
  runSessionTransition(() => acceptIdentityTransition(identityArg));

const logoutTransition = async (
  optionsArg: { notifyServer?: boolean } = {},
  expectedGenerationArg?: number,
): Promise<void> => {
  const previousLoginState = getRequiredState(loginStatePart);
  if (
    expectedGenerationArg !== undefined
    && previousLoginState.generation !== expectedGenerationArg
  ) return;
  if (previousLoginState.phase === 'logging-out') return;
  clearSessionExpiryTimer();
  const identity = previousLoginState.identity;
  const generation = requestScheduler.invalidate();
  await loginStatePart.setState({
    identity: null,
    phase: 'logging-out',
    generation,
    verificationError: null,
  });
  if (refreshIntervalHandle) {
    clearInterval(refreshIntervalHandle);
    refreshIntervalHandle = null;
  }
  await disconnectSocket();
  await clearProtectedState();

  if (optionsArg.notifyServer !== false && identity) {
    try {
      const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_AdminLogout
      >('/typedrequest', 'adminLogout');
      await withTimeout(
        typedRequest.fire({ identity }),
        5_000,
        'Onebox logout notification timed out',
      );
    } catch (error) {
      console.warn('Unable to notify server about logout:', error);
    }
  }
  await loginStatePart.setState({
    identity: null,
    phase: 'anonymous',
    generation,
    verificationError: null,
  });
};

export const logout = async (
  optionsArg: { notifyServer?: boolean } = {},
): Promise<void> => runSessionTransition(() => logoutTransition(optionsArg));

async function logoutForSessionGeneration(generationArg: number): Promise<void> {
  await runSessionTransition(() => logoutTransition({ notifyServer: false }, generationArg));
}

const pendingInitialSession = getRequiredState(loginStatePart);
if (pendingInitialSession.identity) {
  scheduleSessionExpiry(pendingInitialSession.identity, pendingInitialSession.generation);
}

let expireSessionPromise: Promise<void> | null = null;
const expireSessionForAuthError = async (
  errorArg: unknown,
  expectedGenerationArg: number,
): Promise<void> => {
  const code = rpcErrorCode(errorArg);
  if (!['AUTH_REQUIRED', 'AUTH_INVALID', 'AUTH_EXPIRED'].includes(String(code))) return;
  if (!requestScheduler.isCurrent(expectedGenerationArg)) return;
  if (!isSessionGenerationCurrent(getRequiredState(loginStatePart), expectedGenerationArg)) return;
  if (!expireSessionPromise) {
    expireSessionPromise = logoutForSessionGeneration(expectedGenerationArg)
      .finally(() => {
        expireSessionPromise = null;
      });
  }
  await expireSessionPromise;
};

const handleProtectedActionError = async (
  errorArg: unknown,
  messageArg: string,
): Promise<void> => {
  console.error(messageArg, errorArg);
  const code = rpcErrorCode(errorArg);
  if (['AUTH_REQUIRED', 'AUTH_INVALID', 'AUTH_EXPIRED'].includes(String(code))) throw errorArg;
};

export const dispatchProtectedAction = async <
  TState extends object,
  TPayload,
  TStatePart extends {
    getState: () => TState | undefined;
    setState: (stateArg: TState) => Promise<TState>;
  },
  TActionContext,
>(
  actionArg: {
    statePartRef: TStatePart;
    actionDef: (statePartArg: TStatePart, payloadArg: TPayload, contextArg: TActionContext) => Promise<TState>;
  },
  payloadArg: TPayload,
): Promise<TState> => {
  const actionId = getProtectedActionId(actionArg);
  const requestKey = `action.${actionId}.${hashPayload(payloadArg)}`;
  const generation = requestScheduler.currentGeneration;
  const policy = protectedActionPolicies.get(actionArg) ?? independentPolicy;
  const intent = policy.mode === 'latest'
    ? latestIntentTracker.begin(
      `action.${actionId}`,
      requestKey,
      generation,
      requestScheduler.hasInFlight(requestKey),
    )
    : null;
  return await requestScheduler.run(requestKey, async (requestContext) =>
    runWithExecutionPolicy(policy, async () => {
      const actionContext = getActionContext();
      if (actionContext.generation !== requestContext.generation) {
        return getRequiredState(actionArg.statePartRef);
      }
      const baseState = getRequiredState(actionArg.statePartRef);
      const actionStatePart = new Proxy(actionArg.statePartRef, {
        get: (targetArg, propertyArg, receiverArg) => {
          if (propertyArg === 'getState') return () => baseState;
          const value = Reflect.get(targetArg, propertyArg, receiverArg) as unknown;
          return typeof value === 'function' ? value.bind(targetArg) : value;
        },
      }) as TStatePart;
      const isCurrent = (): boolean => isProtectedRequestCurrent(
        getRequiredState(loginStatePart),
        requestContext.generation,
        requestScheduler.currentGeneration,
        intent ? latestIntentTracker.isCurrent(intent) : true,
      );

      try {
        const nextState = await withTimeout(
          actionArg.actionDef(
            actionStatePart,
            payloadArg,
            {
              dispatch: async () => {
                throw new Error('Nested Smartstate dispatch is not supported for protected actions');
              },
            } as unknown as TActionContext,
          ),
          protectedActionTimeoutMs,
          `Protected request timed out after ${protectedActionTimeoutMs}ms`,
        );
        if (!isCurrent()) return getRequiredState(actionArg.statePartRef);
        const latestState = getRequiredState(actionArg.statePartRef);
        const mergedState = mergeActionState(baseState, nextState, latestState);
        return await actionArg.statePartRef.setState(mergedState);
      } catch (error) {
        if (!isCurrent()) return getRequiredState(actionArg.statePartRef);
        await expireSessionForAuthError(error, actionContext.generation);
        throw error;
      }
    }),
  );
};

export const runProtectedRequest = async <TResult>(
  scopeArg: string,
  payloadArg: unknown,
  requestArg: (identityArg: interfaces.data.IIdentity) => Promise<TResult>,
  policyArg: TProtectedExecutionPolicy = independentPolicy,
): Promise<TResult | undefined> => {
  const requestKey = `request.${scopeArg}.${hashPayload(payloadArg)}`;
  const generation = requestScheduler.currentGeneration;
  const intent = policyArg.mode === 'latest'
    ? latestIntentTracker.begin(
      `request.${scopeArg}`,
      requestKey,
      generation,
      requestScheduler.hasInFlight(requestKey),
    )
    : null;
  return await requestScheduler.run(requestKey, async (requestContext) =>
    runWithExecutionPolicy(policyArg, async () => {
      const actionContext = getActionContext();
      if (actionContext.generation !== requestContext.generation) return undefined;
      const isCurrent = (): boolean => isProtectedRequestCurrent(
        getRequiredState(loginStatePart),
        requestContext.generation,
        requestScheduler.currentGeneration,
        intent ? latestIntentTracker.isCurrent(intent) : true,
      );
      try {
        const result = await withTimeout(
          requestArg(actionContext.identity),
          protectedActionTimeoutMs,
          `Protected request timed out after ${protectedActionTimeoutMs}ms`,
        );
        return isCurrent() ? result : undefined;
      } catch (error) {
        if (!isCurrent()) return undefined;
        await expireSessionForAuthError(error, actionContext.generation);
        throw error;
      }
    }),
  );
};

export const verifyPersistedSession = async (): Promise<void> => {
  const loginState = getRequiredState(loginStatePart);
  if (loginState.phase !== 'verifying' || !loginState.identity) return;
  const identity = loginState.identity;
  if (loginState.verificationError) {
    await loginStatePart.setState({ ...loginState, verificationError: null });
  }
  try {
    await requestScheduler.run('session.verify', async (requestContext) => {
      const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_VerifyIdentity
      >('/typedrequest', 'verifyIdentity');
      const response = await typedRequest.fire({ identity });
      if (!requestContext.isCurrent()) return;
      const verifiedIdentity = verifiedIdentityFromResponse(response);
      if (!verifiedIdentity) {
        await logout({ notifyServer: false });
        return;
      }
      await acceptIdentity(verifiedIdentity);
    });
  } catch (error) {
    if (getRequiredState(loginStatePart).phase === 'verifying') {
      console.warn('Stored session verification failed:', error);
      const code = rpcErrorCode(error);
      if (['AUTH_REQUIRED', 'AUTH_INVALID', 'AUTH_EXPIRED'].includes(String(code))) {
        await logout({ notifyServer: false });
        return;
      }
      const current = getRequiredState(loginStatePart);
      await loginStatePart.setState({
        ...current,
        verificationError: 'Onebox is temporarily unavailable. Your session has not been discarded.',
      });
    }
  }
};

// ============================================================================
// System Status Actions
// ============================================================================

export const fetchSystemStatus = (): Promise<void> =>
  requestScheduler.run('system.status', async (requestContext) => {
    const context = getActionContext();
    if (context.generation !== requestContext.generation) return;
    const previous = getRequiredState(systemStatePart);
    await systemStatePart.setState({
      ...previous,
      resource: startResourceLoad(previous.resource, Boolean(previous.status)),
    });
    try {
      const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_GetSystemStatus
      >('/typedrequest', 'getSystemStatus');
      const response = await typedRequest.fire({ identity: context.identity });
      if (!requestContext.isCurrent()) return;
      await systemStatePart.setState({
        status: response.status,
        resource: completeResourceLoad(),
      });
    } catch (error) {
      if (!requestContext.isCurrent()) return;
      await expireSessionForAuthError(error, context.generation);
      if (!requestContext.isCurrent()) return;
      const current = getRequiredState(systemStatePart);
      await systemStatePart.setState({
        ...current,
        resource: failResourceLoad(current.resource, Boolean(current.status), errorMessage(error)),
      });
      throw error;
    }
  });

// ============================================================================
// Services Actions
// ============================================================================

export const fetchServicesAction = servicesStatePart.createAction(async (statePartArg) => {
  const context = getActionContext();
  try {
    const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_GetServices
    >('/typedrequest', 'getServices');
    const response = await typedRequest.fire({ identity: context.identity! });
    return { ...getRequiredState(statePartArg), services: response.services };
  } catch (err) {
    await handleProtectedActionError(err, 'Failed to fetch services:');
    return getRequiredState(statePartArg);
  }
});

export const fetchServiceAction = servicesStatePart.createAction<{ name: string }>(
  async (statePartArg, dataArg) => {
    const context = getActionContext();
    try {
      const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_GetService
      >('/typedrequest', 'getService');
      const hostedAppRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_GetHostedAppLifecycleForService
      >('/typedrequest', 'getHostedAppLifecycleForService');
      const [response, hostedAppResponse] = await Promise.all([
        typedRequest.fire({
          identity: context.identity!,
          serviceName: dataArg.name,
        }),
        hostedAppRequest.fire({
          identity: context.identity!,
          serviceName: dataArg.name,
        }),
      ]);
      return {
        ...getRequiredState(statePartArg),
        currentService: response.service,
        currentHostedAppLifecycle: hostedAppResponse.state || null,
      };
    } catch (err) {
      await handleProtectedActionError(err, 'Failed to fetch service:');
      return getRequiredState(statePartArg);
    }
  },
);

export const createServiceAction = servicesStatePart.createAction<{
  config: interfaces.data.IServiceCreate;
}>(async (statePartArg, dataArg) => {
  const context = getActionContext();
  try {
    const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_CreateService
    >('/typedrequest', 'createService');
    await typedRequest.fire({
      identity: context.identity!,
      serviceConfig: dataArg.config,
    });
    // Re-fetch services list
    const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_GetServices
    >('/typedrequest', 'getServices');
    const listResp = await listReq.fire({ identity: context.identity! });
    return { ...getRequiredState(statePartArg), services: listResp.services };
  } catch (err) {
    await handleProtectedActionError(err, 'Failed to create service:');
    return getRequiredState(statePartArg);
  }
});

export const deleteServiceAction = servicesStatePart.createAction<{ name: string }>(
  async (statePartArg, dataArg) => {
    const context = getActionContext();
    try {
      const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_DeleteService
      >('/typedrequest', 'deleteService');
      await typedRequest.fire({
        identity: context.identity!,
        serviceName: dataArg.name,
      });
      const state = getRequiredState(statePartArg);
      return {
        ...state,
        services: state.services.filter((s) => s.name !== dataArg.name),
        currentService: null,
        currentHostedAppLifecycle: null,
      };
    } catch (err) {
      await handleProtectedActionError(err, 'Failed to delete service:');
      return getRequiredState(statePartArg);
    }
  },
);

export const dismissHostedAppBootstrapAction = servicesStatePart.createAction<{ name: string }>(
  async (statePartArg, dataArg) => {
    const context = getActionContext();
    try {
      const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_DismissHostedAppBootstrapAction
      >('/typedrequest', 'dismissHostedAppBootstrapAction');
      const response = await typedRequest.fire({
        identity: context.identity!,
        serviceName: dataArg.name,
      });
      return {
        ...getRequiredState(statePartArg),
        currentHostedAppLifecycle: response.state || null,
      };
    } catch (err) {
      await handleProtectedActionError(err, 'Failed to dismiss hosted app bootstrap action:');
      return getRequiredState(statePartArg);
    }
  },
);

export const startServiceAction = servicesStatePart.createAction<{ name: string }>(
  async (statePartArg, dataArg) => {
    const context = getActionContext();
    try {
      const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_StartService
      >('/typedrequest', 'startService');
      await typedRequest.fire({
        identity: context.identity!,
        serviceName: dataArg.name,
      });
      // Re-fetch services
      const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_GetServices
    >('/typedrequest', 'getServices');
    const listResp = await listReq.fire({ identity: context.identity! });
    return { ...getRequiredState(statePartArg), services: listResp.services };
  } catch (err) {
    await handleProtectedActionError(err, 'Failed to start service:');
    return getRequiredState(statePartArg);
  }
  },
);

export const stopServiceAction = servicesStatePart.createAction<{ name: string }>(
  async (statePartArg, dataArg) => {
    const context = getActionContext();
    try {
      const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_StopService
      >('/typedrequest', 'stopService');
      await typedRequest.fire({
        identity: context.identity!,
        serviceName: dataArg.name,
      });
      const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_GetServices
    >('/typedrequest', 'getServices');
    const listResp = await listReq.fire({ identity: context.identity! });
      return { ...getRequiredState(statePartArg), services: listResp.services };
    } catch (err) {
      await handleProtectedActionError(err, 'Failed to stop service:');
      return getRequiredState(statePartArg);
    }
  },
);

export const restartServiceAction = servicesStatePart.createAction<{ name: string }>(
  async (statePartArg, dataArg) => {
    const context = getActionContext();
    try {
      const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_RestartService
      >('/typedrequest', 'restartService');
      await typedRequest.fire({
        identity: context.identity!,
        serviceName: dataArg.name,
      });
      const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_GetServices
    >('/typedrequest', 'getServices');
    const listResp = await listReq.fire({ identity: context.identity! });
      return { ...getRequiredState(statePartArg), services: listResp.services };
    } catch (err) {
      await handleProtectedActionError(err, 'Failed to restart service:');
      return getRequiredState(statePartArg);
    }
  },
);

export const fetchServiceLogsAction = servicesStatePart.createAction<{
  name: string;
  lines?: number;
}>(async (statePartArg, dataArg) => {
  const context = getActionContext();
  try {
    const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_GetServiceLogs
    >('/typedrequest', 'getServiceLogs');
    const response = await typedRequest.fire({
      identity: context.identity!,
      serviceName: dataArg.name,
      tail: dataArg.lines || 200,
    });
    return { ...getRequiredState(statePartArg), currentServiceLogs: logsStringToEntries(response.logs) };
  } catch (err) {
    await handleProtectedActionError(err, 'Failed to fetch service logs:');
    return getRequiredState(statePartArg);
  }
});

export const fetchServiceStatsAction = servicesStatePart.createAction<{ name: string }>(
  async (statePartArg, dataArg) => {
    const context = getActionContext();
    try {
      const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_GetServiceStats
      >('/typedrequest', 'getServiceStats');
      const response = await typedRequest.fire({
        identity: context.identity!,
        serviceName: dataArg.name,
      });
      return { ...getRequiredState(statePartArg), currentServiceStats: response.stats };
    } catch (err) {
      await handleProtectedActionError(err, 'Failed to fetch service stats:');
      return getRequiredState(statePartArg);
    }
  },
);

// ============================================================================
// Platform Services Actions
// ============================================================================

export const fetchPlatformServicesAction = servicesStatePart.createAction(
  async (statePartArg) => {
    const context = getActionContext();
    try {
      const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_GetPlatformServices
      >('/typedrequest', 'getPlatformServices');
      const response = await typedRequest.fire({ identity: context.identity! });
      return { ...getRequiredState(statePartArg), platformServices: response.platformServices };
    } catch (err) {
      await handleProtectedActionError(err, 'Failed to fetch platform services:');
      return getRequiredState(statePartArg);
    }
  },
);

export const startPlatformServiceAction = servicesStatePart.createAction<{
  serviceType: interfaces.data.TPlatformServiceType;
}>(async (statePartArg, dataArg) => {
  const context = getActionContext();
  try {
    const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_StartPlatformService
    >('/typedrequest', 'startPlatformService');
    await typedRequest.fire({
      identity: context.identity!,
      serviceType: dataArg.serviceType,
    });
    // Re-fetch platform services
    const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_GetPlatformServices
    >('/typedrequest', 'getPlatformServices');
    const listResp = await listReq.fire({ identity: context.identity! });
    return { ...getRequiredState(statePartArg), platformServices: listResp.platformServices };
  } catch (err) {
    await handleProtectedActionError(err, 'Failed to start platform service:');
    return getRequiredState(statePartArg);
  }
});

export const stopPlatformServiceAction = servicesStatePart.createAction<{
  serviceType: interfaces.data.TPlatformServiceType;
}>(async (statePartArg, dataArg) => {
  const context = getActionContext();
  try {
    const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_StopPlatformService
    >('/typedrequest', 'stopPlatformService');
    await typedRequest.fire({
      identity: context.identity!,
      serviceType: dataArg.serviceType,
    });
    const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_GetPlatformServices
    >('/typedrequest', 'getPlatformServices');
    const listResp = await listReq.fire({ identity: context.identity! });
    return { ...getRequiredState(statePartArg), platformServices: listResp.platformServices };
  } catch (err) {
    await handleProtectedActionError(err, 'Failed to stop platform service:');
    return getRequiredState(statePartArg);
  }
});

export const fetchPlatformServiceStatsAction = servicesStatePart.createAction<{
  serviceType: interfaces.data.TPlatformServiceType;
}>(async (statePartArg, dataArg) => {
  const context = getActionContext();
  try {
    const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_GetPlatformServiceStats
    >('/typedrequest', 'getPlatformServiceStats');
    const response = await typedRequest.fire({
      identity: context.identity!,
      serviceType: dataArg.serviceType,
    });
    return { ...getRequiredState(statePartArg), currentPlatformServiceStats: response.stats };
  } catch (err) {
    await handleProtectedActionError(err, 'Failed to fetch platform service stats:');
    return { ...getRequiredState(statePartArg), currentPlatformServiceStats: null };
  }
});

export const fetchPlatformServiceLogsAction = servicesStatePart.createAction<{
  serviceType: interfaces.data.TPlatformServiceType;
  tail?: number;
}>(async (statePartArg, dataArg) => {
  const context = getActionContext();
  try {
    const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_GetPlatformServiceLogs
    >('/typedrequest', 'getPlatformServiceLogs');
    const response = await typedRequest.fire({
      identity: context.identity!,
      serviceType: dataArg.serviceType,
      tail: dataArg.tail || 100,
    });
    return { ...getRequiredState(statePartArg), currentPlatformServiceLogs: response.logs };
  } catch (err) {
    await handleProtectedActionError(err, 'Failed to fetch platform service logs:');
    return { ...getRequiredState(statePartArg), currentPlatformServiceLogs: [] };
  }
});

// ============================================================================
// Network Actions
// ============================================================================

export const fetchNetworkTargetsAction = networkStatePart.createAction(async (statePartArg) => {
  const context = getActionContext();
  try {
    const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_GetNetworkTargets
    >('/typedrequest', 'getNetworkTargets');
    const response = await typedRequest.fire({ identity: context.identity! });
    return { ...getRequiredState(statePartArg), targets: response.targets };
  } catch (err) {
    await handleProtectedActionError(err, 'Failed to fetch network targets:');
    return getRequiredState(statePartArg);
  }
});

export const fetchNetworkStats = (): Promise<void> =>
  requestScheduler.run('network.stats', async (requestContext) => {
    const context = getActionContext();
    if (context.generation !== requestContext.generation) return;
    const previous = getRequiredState(networkStatePart);
    await networkStatePart.setState({
      ...previous,
      statsResource: startResourceLoad(previous.statsResource, Boolean(previous.stats)),
    });
    try {
      const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_GetNetworkStats
      >('/typedrequest', 'getNetworkStats');
      const response = await typedRequest.fire({ identity: context.identity });
      if (!requestContext.isCurrent()) return;
      await networkStatePart.setState({
        ...getRequiredState(networkStatePart),
        stats: response.stats,
        statsResource: completeResourceLoad(),
      });
    } catch (error) {
      if (!requestContext.isCurrent()) return;
      await expireSessionForAuthError(error, context.generation);
      if (!requestContext.isCurrent()) return;
      const current = getRequiredState(networkStatePart);
      await networkStatePart.setState({
        ...current,
        statsResource: failResourceLoad(
          current.statsResource,
          Boolean(current.stats),
          errorMessage(error),
        ),
      });
      throw error;
    }
  });

export const fetchTrafficStatsAction = networkStatePart.createAction(async (statePartArg) => {
  const context = getActionContext();
  try {
    const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_GetTrafficStats
    >('/typedrequest', 'getTrafficStats');
    const response = await typedRequest.fire({ identity: context.identity! });
    return { ...getRequiredState(statePartArg), trafficStats: response.stats };
  } catch (err) {
    await handleProtectedActionError(err, 'Failed to fetch traffic stats:');
    return getRequiredState(statePartArg);
  }
});

export const fetchDnsRecordsAction = networkStatePart.createAction(async (statePartArg) => {
  const context = getActionContext();
  try {
    const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_GetDnsRecords
    >('/typedrequest', 'getDnsRecords');
    const response = await typedRequest.fire({ identity: context.identity! });
    return { ...getRequiredState(statePartArg), dnsRecords: response.records };
  } catch (err) {
    await handleProtectedActionError(err, 'Failed to fetch DNS records:');
    return getRequiredState(statePartArg);
  }
});

export const syncDnsAction = networkStatePart.createAction(async (statePartArg) => {
  const context = getActionContext();
  try {
    const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_SyncDns
    >('/typedrequest', 'syncDns');
    await typedRequest.fire({ identity: context.identity! });
    // Re-fetch DNS records
    const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_GetDnsRecords
    >('/typedrequest', 'getDnsRecords');
    const listResp = await listReq.fire({ identity: context.identity! });
    return { ...getRequiredState(statePartArg), dnsRecords: listResp.records };
  } catch (err) {
    await handleProtectedActionError(err, 'Failed to sync DNS:');
    return getRequiredState(statePartArg);
  }
});

export const fetchDomainsAction = networkStatePart.createAction(async (statePartArg) => {
  const context = getActionContext();
  try {
    const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_GetDomains
    >('/typedrequest', 'getDomains');
    const response = await typedRequest.fire({ identity: context.identity! });
    return { ...getRequiredState(statePartArg), domains: response.domains };
  } catch (err) {
    await handleProtectedActionError(err, 'Failed to fetch domains:');
    return getRequiredState(statePartArg);
  }
});

export const fetchGatewayDomainsAction = networkStatePart.createAction(async (statePartArg) => {
  const context = getActionContext();
  try {
    const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_GetGatewayDomains
    >('/typedrequest', 'getGatewayDomains');
    const response = await typedRequest.fire({ identity: context.identity! });
    return { ...getRequiredState(statePartArg), gatewayDomains: response.domains };
  } catch (err) {
    await handleProtectedActionError(err, 'Failed to fetch gateway domains:');
    return getRequiredState(statePartArg);
  }
});

export const fetchGatewayDnsRecordsAction = networkStatePart.createAction(async (statePartArg) => {
  const context = getActionContext();
  try {
    const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_GetGatewayDnsRecords
    >('/typedrequest', 'getGatewayDnsRecords');
    const response = await typedRequest.fire({ identity: context.identity! });
    return { ...getRequiredState(statePartArg), gatewayDnsRecords: response.records };
  } catch (err) {
    await handleProtectedActionError(err, 'Failed to fetch gateway DNS records:');
    return getRequiredState(statePartArg);
  }
});

export const fetchCertificatesAction = networkStatePart.createAction(async (statePartArg) => {
  const context = getActionContext();
  try {
    const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_ListCertificates
    >('/typedrequest', 'listCertificates');
    const response = await typedRequest.fire({ identity: context.identity! });
    return { ...getRequiredState(statePartArg), certificates: response.certificates };
  } catch (err) {
    await handleProtectedActionError(err, 'Failed to fetch certificates:');
    return getRequiredState(statePartArg);
  }
});

export const renewCertificateAction = networkStatePart.createAction<{ domain: string }>(
  async (statePartArg, dataArg) => {
    const context = getActionContext();
    try {
      const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_RenewCertificate
      >('/typedrequest', 'renewCertificate');
      await typedRequest.fire({
        identity: context.identity!,
        domain: dataArg.domain,
      });
      // Re-fetch certificates
      const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_ListCertificates
      >('/typedrequest', 'listCertificates');
      const listResp = await listReq.fire({ identity: context.identity! });
      return { ...getRequiredState(statePartArg), certificates: listResp.certificates };
    } catch (err) {
      await handleProtectedActionError(err, 'Failed to renew certificate:');
      return getRequiredState(statePartArg);
    }
  },
);

// ============================================================================
// Registry Actions
// ============================================================================

export const fetchRegistryTokensAction = registriesStatePart.createAction(
  async (statePartArg) => {
    const context = getActionContext();
    try {
      const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_GetRegistryTokens
      >('/typedrequest', 'getRegistryTokens');
      const response = await typedRequest.fire({ identity: context.identity! });
      return { ...getRequiredState(statePartArg), tokens: response.tokens };
    } catch (err) {
      await handleProtectedActionError(err, 'Failed to fetch registry tokens:');
      return getRequiredState(statePartArg);
    }
  },
);

export const createRegistryTokenAction = registriesStatePart.createAction<{
  token: interfaces.data.ICreateTokenRequest;
}>(async (statePartArg, dataArg) => {
  const context = getActionContext();
  try {
    const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_CreateRegistryToken
    >('/typedrequest', 'createRegistryToken');
    await typedRequest.fire({
      identity: context.identity!,
      tokenConfig: dataArg.token,
    });
    // Re-fetch tokens
    const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_GetRegistryTokens
    >('/typedrequest', 'getRegistryTokens');
    const listResp = await listReq.fire({ identity: context.identity! });
    return { ...getRequiredState(statePartArg), tokens: listResp.tokens };
  } catch (err) {
    await handleProtectedActionError(err, 'Failed to create registry token:');
    return getRequiredState(statePartArg);
  }
});

export const deleteRegistryTokenAction = registriesStatePart.createAction<{
  tokenId: number;
}>(async (statePartArg, dataArg) => {
  const context = getActionContext();
  try {
    const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_DeleteRegistryToken
    >('/typedrequest', 'deleteRegistryToken');
    await typedRequest.fire({
      identity: context.identity!,
      tokenId: dataArg.tokenId,
    });
    const state = getRequiredState(statePartArg);
    return {
      ...state,
      tokens: state.tokens.filter((t) => t.id !== dataArg.tokenId),
    };
  } catch (err) {
    await handleProtectedActionError(err, 'Failed to delete registry token:');
    return getRequiredState(statePartArg);
  }
});

// ============================================================================
// Backups Actions
// ============================================================================

export const fetchBackupsAction = backupsStatePart.createAction(async (statePartArg) => {
  const context = getActionContext();
  try {
    const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_GetBackups
    >('/typedrequest', 'getBackups');
    const response = await typedRequest.fire({ identity: context.identity! });
    return { ...getRequiredState(statePartArg), backups: response.backups };
  } catch (err) {
    await handleProtectedActionError(err, 'Failed to fetch backups:');
    return getRequiredState(statePartArg);
  }
});

export const deleteBackupAction = backupsStatePart.createAction<{ backupId: number }>(
  async (statePartArg, dataArg) => {
    const context = getActionContext();
    try {
      const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_DeleteBackup
      >('/typedrequest', 'deleteBackup');
      await typedRequest.fire({
        identity: context.identity!,
        backupId: dataArg.backupId,
      });
      const state = getRequiredState(statePartArg);
      return {
        ...state,
        backups: state.backups.filter((b) => b.id !== dataArg.backupId),
      };
    } catch (err) {
      await handleProtectedActionError(err, 'Failed to delete backup:');
      return getRequiredState(statePartArg);
    }
  },
);

export const fetchSchedulesAction = backupsStatePart.createAction(async (statePartArg) => {
  const context = getActionContext();
  try {
    const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_GetBackupSchedules
    >('/typedrequest', 'getBackupSchedules');
    const response = await typedRequest.fire({ identity: context.identity! });
    return { ...getRequiredState(statePartArg), schedules: response.schedules };
  } catch (err) {
    await handleProtectedActionError(err, 'Failed to fetch schedules:');
    return getRequiredState(statePartArg);
  }
});

export const createScheduleAction = backupsStatePart.createAction<{
  config: interfaces.data.IBackupScheduleCreate;
}>(async (statePartArg, dataArg) => {
  const context = getActionContext();
  try {
    const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_CreateBackupSchedule
    >('/typedrequest', 'createBackupSchedule');
    await typedRequest.fire({
      identity: context.identity!,
      scheduleConfig: dataArg.config,
    });
    // Re-fetch schedules
    const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_GetBackupSchedules
    >('/typedrequest', 'getBackupSchedules');
    const listResp = await listReq.fire({ identity: context.identity! });
    return { ...getRequiredState(statePartArg), schedules: listResp.schedules };
  } catch (err) {
    await handleProtectedActionError(err, 'Failed to create schedule:');
    return getRequiredState(statePartArg);
  }
});

export const deleteScheduleAction = backupsStatePart.createAction<{ scheduleId: number }>(
  async (statePartArg, dataArg) => {
    const context = getActionContext();
    try {
      const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_DeleteBackupSchedule
      >('/typedrequest', 'deleteBackupSchedule');
      await typedRequest.fire({
        identity: context.identity!,
        scheduleId: dataArg.scheduleId,
      });
      const state = getRequiredState(statePartArg);
      return {
        ...state,
        schedules: state.schedules.filter((s) => s.id !== dataArg.scheduleId),
      };
    } catch (err) {
      await handleProtectedActionError(err, 'Failed to delete schedule:');
      return getRequiredState(statePartArg);
    }
  },
);

export const triggerScheduleAction = backupsStatePart.createAction<{ scheduleId: number }>(
  async (statePartArg, dataArg) => {
    const context = getActionContext();
    try {
      const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_TriggerBackupSchedule
      >('/typedrequest', 'triggerBackupSchedule');
      await typedRequest.fire({
        identity: context.identity!,
        scheduleId: dataArg.scheduleId,
      });
      // Re-fetch backups
      const backupsReq = new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_GetBackups
      >('/typedrequest', 'getBackups');
      const backupsResp = await backupsReq.fire({ identity: context.identity! });
      return { ...getRequiredState(statePartArg), backups: backupsResp.backups };
    } catch (err) {
      await handleProtectedActionError(err, 'Failed to trigger schedule:');
      return getRequiredState(statePartArg);
    }
  },
);

// ============================================================================
// Settings Actions
// ============================================================================

const requestStorageConfig = async (identityArg: interfaces.data.IIdentity) => {
  return await new plugins.domtools.plugins.typedrequest.TypedRequest<
    interfaces.requests.IReq_GetStorageConfig
  >('/typedrequest', 'getStorageConfig').fire({ identity: identityArg });
};

export const fetchSettingsAction = settingsStatePart.createAction(async (statePartArg) => {
  const context = getActionContext();
  try {
    const [settingsResp, passwordResp, managedDcRouterResp, storageResp] = await Promise.all([
      new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_GetSettings
      >('/typedrequest', 'getSettings').fire({ identity: context.identity! }),
      new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_GetBackupPasswordStatus
      >('/typedrequest', 'getBackupPasswordStatus').fire({ identity: context.identity! }),
      new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_GetManagedDcRouterStatus
      >('/typedrequest', 'getManagedDcRouterStatus').fire({ identity: context.identity! }),
      requestStorageConfig(context.identity!),
    ]);
    return {
      settings: settingsResp.settings,
      backupPasswordConfigured: passwordResp.status.isConfigured,
      managedDcRouterStatus: managedDcRouterResp.status,
      storageTargets: storageResp.storageTargets,
      storageClasses: storageResp.storageClasses,
    };
  } catch (err) {
    await handleProtectedActionError(err, 'Failed to fetch settings:');
    return getRequiredState(statePartArg);
  }
});

export const fetchStorageConfigAction = settingsStatePart.createAction(async (statePartArg) => {
  const context = getActionContext();
  try {
    const response = await requestStorageConfig(context.identity!);
    return {
      ...getRequiredState(statePartArg),
      storageTargets: response.storageTargets,
      storageClasses: response.storageClasses,
    };
  } catch (err) {
    await handleProtectedActionError(err, 'Failed to fetch storage config:');
    return getRequiredState(statePartArg);
  }
});

export const upsertStorageTargetAction = settingsStatePart.createAction<{
  storageTarget: Omit<interfaces.data.IStorageTarget, 'createdAt' | 'updatedAt'>;
}>(async (statePartArg, dataArg) => {
  const context = getActionContext();
  try {
    await new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_UpsertStorageTarget
    >('/typedrequest', 'upsertStorageTarget').fire({
      identity: context.identity!,
      storageTarget: dataArg.storageTarget,
    });
    const response = await requestStorageConfig(context.identity!);
    return {
      ...getRequiredState(statePartArg),
      storageTargets: response.storageTargets,
      storageClasses: response.storageClasses,
    };
  } catch (err) {
    await handleProtectedActionError(err, 'Failed to upsert storage target:');
    throw err;
  }
});

export const deleteStorageTargetAction = settingsStatePart.createAction<{
  name: string;
}>(async (statePartArg, dataArg) => {
  const context = getActionContext();
  try {
    await new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_DeleteStorageTarget
    >('/typedrequest', 'deleteStorageTarget').fire({
      identity: context.identity!,
      name: dataArg.name,
    });
    const response = await requestStorageConfig(context.identity!);
    return {
      ...getRequiredState(statePartArg),
      storageTargets: response.storageTargets,
      storageClasses: response.storageClasses,
    };
  } catch (err) {
    await handleProtectedActionError(err, 'Failed to delete storage target:');
    throw err;
  }
});

export const upsertStorageClassAction = settingsStatePart.createAction<{
  storageClass: Omit<interfaces.data.IStorageClass, 'createdAt' | 'updatedAt'>;
}>(async (statePartArg, dataArg) => {
  const context = getActionContext();
  try {
    await new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_UpsertStorageClass
    >('/typedrequest', 'upsertStorageClass').fire({
      identity: context.identity!,
      storageClass: dataArg.storageClass,
    });
    const response = await requestStorageConfig(context.identity!);
    return {
      ...getRequiredState(statePartArg),
      storageTargets: response.storageTargets,
      storageClasses: response.storageClasses,
    };
  } catch (err) {
    await handleProtectedActionError(err, 'Failed to upsert storage class:');
    throw err;
  }
});

export const deleteStorageClassAction = settingsStatePart.createAction<{
  name: string;
}>(async (statePartArg, dataArg) => {
  const context = getActionContext();
  try {
    await new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_DeleteStorageClass
    >('/typedrequest', 'deleteStorageClass').fire({
      identity: context.identity!,
      name: dataArg.name,
    });
    const response = await requestStorageConfig(context.identity!);
    return {
      ...getRequiredState(statePartArg),
      storageTargets: response.storageTargets,
      storageClasses: response.storageClasses,
    };
  } catch (err) {
    await handleProtectedActionError(err, 'Failed to delete storage class:');
    throw err;
  }
});

export const updateSettingsAction = settingsStatePart.createAction<{
  settings: Partial<interfaces.data.ISettings>;
}>(async (statePartArg, dataArg) => {
  const context = getActionContext();
  try {
    const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_UpdateSettings
    >('/typedrequest', 'updateSettings');
    const response = await typedRequest.fire({
      identity: context.identity!,
      settings: dataArg.settings,
    });
    return { ...getRequiredState(statePartArg), settings: response.settings };
  } catch (err) {
    await handleProtectedActionError(err, 'Failed to update settings:');
    return getRequiredState(statePartArg);
  }
});

export const fetchManagedDcRouterStatusAction = settingsStatePart.createAction(async (statePartArg) => {
  const context = getActionContext();
  try {
    const response = await new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_GetManagedDcRouterStatus
    >('/typedrequest', 'getManagedDcRouterStatus').fire({ identity: context.identity! });
    return { ...getRequiredState(statePartArg), managedDcRouterStatus: response.status };
  } catch (err) {
    await handleProtectedActionError(err, 'Failed to fetch managed dcrouter status:');
    return getRequiredState(statePartArg);
  }
});

export const startManagedDcRouterAction = settingsStatePart.createAction(async (statePartArg) => {
  const context = getActionContext();
  try {
    const response = await new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_StartManagedDcRouter
    >('/typedrequest', 'startManagedDcRouter').fire({ identity: context.identity! });
    return { ...getRequiredState(statePartArg), managedDcRouterStatus: response.status };
  } catch (err) {
    await handleProtectedActionError(err, 'Failed to start managed dcrouter:');
    return getRequiredState(statePartArg);
  }
});

export const stopManagedDcRouterAction = settingsStatePart.createAction(async (statePartArg) => {
  const context = getActionContext();
  try {
    const response = await new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_StopManagedDcRouter
    >('/typedrequest', 'stopManagedDcRouter').fire({ identity: context.identity! });
    return { ...getRequiredState(statePartArg), managedDcRouterStatus: response.status };
  } catch (err) {
    await handleProtectedActionError(err, 'Failed to stop managed dcrouter:');
    return getRequiredState(statePartArg);
  }
});

export const restartManagedDcRouterAction = settingsStatePart.createAction(async (statePartArg) => {
  const context = getActionContext();
  try {
    const response = await new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_RestartManagedDcRouter
    >('/typedrequest', 'restartManagedDcRouter').fire({ identity: context.identity! });
    return { ...getRequiredState(statePartArg), managedDcRouterStatus: response.status };
  } catch (err) {
    await handleProtectedActionError(err, 'Failed to restart managed dcrouter:');
    return getRequiredState(statePartArg);
  }
});

export const setBackupPasswordAction = settingsStatePart.createAction<{ password: string }>(
  async (statePartArg, dataArg) => {
    const context = getActionContext();
    try {
      const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_SetBackupPassword
      >('/typedrequest', 'setBackupPassword');
      await typedRequest.fire({
        identity: context.identity!,
        password: dataArg.password,
      });
      return { ...getRequiredState(statePartArg), backupPasswordConfigured: true };
    } catch (err) {
      await handleProtectedActionError(err, 'Failed to set backup password:');
      return getRequiredState(statePartArg);
    }
  },
);

const latestReadActions: object[] = [
  fetchServiceAction,
  fetchServiceLogsAction,
  fetchServiceStatsAction,
  fetchPlatformServiceStatsAction,
  fetchPlatformServiceLogsAction,
];
for (const action of latestReadActions) {
  registerProtectedActionPolicy(action, { mode: 'latest' });
}

const mutationActionDomains: Array<[string, object[]]> = [
  ['services', [
    createServiceAction,
    deleteServiceAction,
    dismissHostedAppBootstrapAction,
    startServiceAction,
    stopServiceAction,
    restartServiceAction,
    startPlatformServiceAction,
    stopPlatformServiceAction,
  ]],
  ['network', [syncDnsAction, renewCertificateAction]],
  ['registries', [createRegistryTokenAction, deleteRegistryTokenAction]],
  ['backups', [
    deleteBackupAction,
    createScheduleAction,
    deleteScheduleAction,
    triggerScheduleAction,
  ]],
  ['settings', [
    upsertStorageTargetAction,
    deleteStorageTargetAction,
    upsertStorageClassAction,
    deleteStorageClassAction,
    updateSettingsAction,
    startManagedDcRouterAction,
    stopManagedDcRouterAction,
    restartManagedDcRouterAction,
    setBackupPasswordAction,
  ]],
];
for (const [domain, actions] of mutationActionDomains) {
  for (const action of actions) {
    registerProtectedActionPolicy(action, { mode: 'mutation', domain });
  }
}

// ============================================================================
// UI Actions
// ============================================================================

export const setActiveViewAction = uiStatePart.createAction<{ view: string; subview?: string | null }>(
  async (statePartArg, dataArg) => {
    const normalizedView = dataArg.view.toLowerCase().replace(/\s+/g, '-');
    const normalizedSubview = dataArg.subview
      ? dataArg.subview.toLowerCase().replace(/\s+/g, '-')
      : null;
    return {
      ...getRequiredState(statePartArg),
      activeView: normalizedView,
      activeSubview: normalizedSubview,
    };
  },
);

export const toggleAutoRefreshAction = uiStatePart.createAction(async (statePartArg) => {
  const state = getRequiredState(statePartArg);
  return { ...state, autoRefresh: !state.autoRefresh };
});

// ============================================================================
// Auto-refresh system
// ============================================================================

let refreshIntervalHandle: ReturnType<typeof setInterval> | null = null;

const dispatchCombinedRefreshAction = async () => {
  if (!isAuthenticatedSession()) return;
  await Promise.allSettled([
    fetchSystemStatus(),
    fetchNetworkStats(),
    dispatchProtectedAction(fetchTrafficStatsAction, null),
  ]);
};

const startAutoRefresh = () => {
  const uiState = getRequiredState(uiStatePart);
  const loginState = getRequiredState(loginStatePart);

  if (uiState.autoRefresh && isAuthenticatedSessionState(loginState)) {
    if (refreshIntervalHandle) {
      clearInterval(refreshIntervalHandle);
    }
    refreshIntervalHandle = setInterval(() => {
      dispatchCombinedRefreshAction();
    }, uiState.refreshInterval);
  } else {
    if (refreshIntervalHandle) {
      clearInterval(refreshIntervalHandle);
      refreshIntervalHandle = null;
    }
  }
};

uiStatePart.select((s) => s).subscribe(() => startAutoRefresh());
loginStatePart.select((s) => s).subscribe(() => startAutoRefresh());
startAutoRefresh();

// ============================================================================
// TypedSocket — real-time server push (logs, events)
// ============================================================================

let socketClient: Awaited<ReturnType<typeof plugins.typedsocket.TypedSocket.createClient>> | null = null;
let socketClientPromise: Promise<void> | null = null;
let socketStatusSubscription: { unsubscribe: () => void } | null = null;
let socketDesired = false;
let socketConnectionGeneration = 0;
let activeSocketSessionGeneration: number | null = null;
const socketRouter = new plugins.domtools.plugins.typedrequest.TypedRouter();

interface IActiveTelemetrySubscription {
  target: interfaces.requests.TTelemetrySubscriptionTarget;
  subscriptionId?: string;
  owners: Set<string>;
  subscribePromise?: Promise<void>;
}

const activeTelemetrySubscriptions = new Map<string, IActiveTelemetrySubscription>();
let telemetryRenewInterval: ReturnType<typeof setInterval> | null = null;
const telemetryRenewIntervalMs = 30_000;

const getTelemetryTargetKey = (targetArg: interfaces.requests.TTelemetrySubscriptionTarget): string => {
  switch (targetArg.type) {
    case 'serviceLogs':
      return `service:${targetArg.serviceName}:logs`;
    case 'serviceMetrics':
      return `service:${targetArg.serviceName}:metrics`;
    case 'platformServiceLogs':
      return `platform:${targetArg.serviceType}:logs`;
    case 'platformServiceMetrics':
      return `platform:${targetArg.serviceType}:metrics`;
  }
};

const nextLogEntryId = (entriesArg: interfaces.data.ILogEntry[]): number => {
  return entriesArg.reduce(
    (maximumArg, entryArg) => typeof entryArg.id === 'number'
      ? Math.max(maximumArg, entryArg.id)
      : maximumArg,
    -1,
  ) + 1;
};

const appendLogEntry = (
  entriesArg: interfaces.data.ILogEntry[],
  entryArg: interfaces.data.ILogEntry,
): interfaces.data.ILogEntry[] => {
  const updated = [...entriesArg, { ...entryArg, id: nextLogEntryId(entriesArg) }];
  if (updated.length > 2000) {
    updated.splice(0, updated.length - 2000);
  }
  return updated;
};

const startTelemetryRenewal = () => {
  if (telemetryRenewInterval) return;
  telemetryRenewInterval = setInterval(() => {
    void resubscribeTelemetryTargets();
  }, telemetryRenewIntervalMs);
};

const stopTelemetryRenewalIfIdle = () => {
  if (activeTelemetrySubscriptions.size > 0 || !telemetryRenewInterval) return;
  clearInterval(telemetryRenewInterval);
  telemetryRenewInterval = null;
};

const stopTelemetryRenewal = () => {
  if (!telemetryRenewInterval) return;
  clearInterval(telemetryRenewInterval);
  telemetryRenewInterval = null;
};

const isTelemetryPaused = (): boolean =>
  typeof document !== 'undefined' && document.visibilityState === 'hidden';

const fireTelemetryUnsubscribeById = async (subscriptionIdArg: string): Promise<void> => {
  const identity = getRequiredState(loginStatePart).identity;
  if (!identity || !socketClient || socketClient.getStatus() !== 'connected') return;
  const typedRequest = socketClient.createTypedRequest<interfaces.requests.IReq_UnsubscribeTelemetry>(
    'unsubscribeTelemetry',
  );
  await typedRequest.fire({ identity, subscriptionId: subscriptionIdArg });
};

const ensureTelemetrySubscribed = async (recordArg: IActiveTelemetrySubscription): Promise<void> => {
  if (recordArg.subscribePromise) return recordArg.subscribePromise;
  const targetKey = getTelemetryTargetKey(recordArg.target);
  let subscribePromise: Promise<void>;
  subscribePromise = (async () => {
    const identity = getRequiredState(loginStatePart).identity;
    if (!identity || !socketClient || socketClient.getStatus() !== 'connected') return;
    if (isTelemetryPaused()) return;
    const typedRequest = socketClient.createTypedRequest<interfaces.requests.IReq_SubscribeTelemetry>(
      'subscribeTelemetry',
    );
    const response = await typedRequest.fire({
      identity,
      target: recordArg.target,
      subscriptionId: recordArg.subscriptionId,
    });
    if (activeTelemetrySubscriptions.get(targetKey) !== recordArg || recordArg.owners.size === 0) {
      await fireTelemetryUnsubscribeById(response.subscriptionId).catch(() => {});
      return;
    }
    recordArg.subscriptionId = response.subscriptionId;
  })().finally(() => {
    if (recordArg.subscribePromise === subscribePromise) {
      recordArg.subscribePromise = undefined;
    }
  });
  recordArg.subscribePromise = subscribePromise;
  return subscribePromise;
};

const fireTelemetryUnsubscribe = async (recordArg: IActiveTelemetrySubscription): Promise<void> => {
  if (!recordArg.subscriptionId) return;
  await fireTelemetryUnsubscribeById(recordArg.subscriptionId);
  recordArg.subscriptionId = undefined;
};

const removeTelemetryRecord = async (
  targetKeyArg: string,
  recordArg: IActiveTelemetrySubscription,
): Promise<void> => {
  if (activeTelemetrySubscriptions.get(targetKeyArg) === recordArg) {
    activeTelemetrySubscriptions.delete(targetKeyArg);
  }
  recordArg.owners.clear();
  const subscriptionId = recordArg.subscriptionId;
  await recordArg.subscribePromise?.catch(() => {});
  if (subscriptionId || recordArg.subscriptionId) {
    recordArg.subscriptionId = subscriptionId || recordArg.subscriptionId;
    await fireTelemetryUnsubscribe(recordArg).catch((err) => console.error('Failed to unsubscribe telemetry:', err));
  }
  stopTelemetryRenewalIfIdle();
};

const pauseTelemetrySubscriptions = async (): Promise<void> => {
  const identity = getRequiredState(loginStatePart).identity;
  if (!identity || activeTelemetrySubscriptions.size === 0 || !socketClient || socketClient.getStatus() !== 'connected') return;
  const typedRequest = socketClient.createTypedRequest<interfaces.requests.IReq_UnsubscribeAllTelemetry>(
    'unsubscribeAllTelemetry',
  );
  await typedRequest.fire({ identity }).catch(() => {});
  for (const record of activeTelemetrySubscriptions.values()) {
    record.subscriptionId = undefined;
  }
};

const resubscribeTelemetryTargets = async (): Promise<void> => {
  if (activeTelemetrySubscriptions.size === 0) return;
  if (isTelemetryPaused()) return;
  await connectSocket();
  await Promise.allSettled(Array.from(activeTelemetrySubscriptions.values()).map(ensureTelemetrySubscribed));
};

export const subscribeTelemetryTarget = async (
  targetArg: interfaces.requests.TTelemetrySubscriptionTarget,
  ownerArg = 'default',
): Promise<void> => {
  const targetKey = getTelemetryTargetKey(targetArg);
  let record = activeTelemetrySubscriptions.get(targetKey);
  if (!record) {
    record = { target: targetArg, owners: new Set() };
    activeTelemetrySubscriptions.set(targetKey, record);
  } else {
    record.target = targetArg;
  }
  record.owners.add(ownerArg);
  if (!isTelemetryPaused()) {
    startTelemetryRenewal();
  }
  await connectSocket();
  await ensureTelemetrySubscribed(record).catch((err) => console.error('Failed to subscribe telemetry:', err));
};

export const unsubscribeTelemetryTarget = async (
  targetArg: interfaces.requests.TTelemetrySubscriptionTarget,
  ownerArg = 'default',
): Promise<void> => {
  const targetKey = getTelemetryTargetKey(targetArg);
  const record = activeTelemetrySubscriptions.get(targetKey);
  if (!record) return;
  record.owners.delete(ownerArg);
  if (record.owners.size > 0) return;
  await removeTelemetryRecord(targetKey, record);
};

export const removeTelemetryTarget = async (
  targetArg: interfaces.requests.TTelemetrySubscriptionTarget,
): Promise<void> => {
  const targetKey = getTelemetryTargetKey(targetArg);
  const record = activeTelemetrySubscriptions.get(targetKey);
  if (!record) return;
  await removeTelemetryRecord(targetKey, record);
};

const upsertService = (
  services: interfaces.data.IService[],
  service: interfaces.data.IService,
): interfaces.data.IService[] => {
  const existingIndex = services.findIndex((item) => item.name === service.name);
  if (existingIndex === -1) {
    return [...services, service];
  }

  const updatedServices = [...services];
  updatedServices[existingIndex] = service;
  return updatedServices;
};

const upsertUpgradeOperation = (
  operations: interfaces.requests.IAppStoreUpgradeOperation[],
  operation: interfaces.requests.IAppStoreUpgradeOperation,
): interfaces.requests.IAppStoreUpgradeOperation[] => {
  const existingIndex = operations.findIndex((item) => item.id === operation.id);
  const updatedOperations = existingIndex === -1
    ? [operation, ...operations]
    : operations.map((item) => item.id === operation.id ? operation : item);
  return updatedOperations
    .sort((a, b) => b.startedAt - a.startedAt)
    .slice(0, 25);
};

export const canAcceptSocketPush = (): boolean => {
  return activeSocketSessionGeneration !== null
    && activeSocketSessionGeneration === requestScheduler.currentGeneration
    && isSessionGenerationCurrent(getRequiredState(loginStatePart), activeSocketSessionGeneration);
};

socketRouter.addTypedHandler(
  new plugins.domtools.plugins.typedrequest.TypedHandler<interfaces.requests.IReq_PushServiceUpdate>(
    'pushServiceUpdate',
    async (dataArg) => {
      if (!canAcceptSocketPush()) return {};
      const state = getRequiredState(servicesStatePart);
      let services = state.services;
      let currentService = state.currentService;
      let currentHostedAppLifecycle = state.currentHostedAppLifecycle;
      let currentServiceLogs = state.currentServiceLogs;
      let currentServiceStats = state.currentServiceStats;

      if (dataArg.action === 'deleted') {
        services = services.filter((service) => service.name !== dataArg.serviceName);
        void Promise.all([
          removeTelemetryTarget({ type: 'serviceLogs', serviceName: dataArg.serviceName }),
          removeTelemetryTarget({ type: 'serviceMetrics', serviceName: dataArg.serviceName }),
        ]).catch((err) => console.error('Failed to clear deleted service telemetry:', err));
        if (currentService?.name === dataArg.serviceName) {
          currentService = null;
          currentHostedAppLifecycle = null;
          currentServiceLogs = [];
          currentServiceStats = null;
        }
      } else if (dataArg.service) {
        services = upsertService(services, dataArg.service);
        if (currentService?.name === dataArg.service.name) {
          currentService = dataArg.service;
        }
      }

      await servicesStatePart.setState({
        ...state,
        services,
        currentService,
        currentHostedAppLifecycle,
        currentServiceLogs,
        currentServiceStats,
      });
      return {};
    },
  ),
);

socketRouter.addTypedHandler(
  new plugins.domtools.plugins.typedrequest.TypedHandler<interfaces.requests.IReq_PushAppStoreUpgradeProgress>(
    'pushAppStoreUpgradeProgress',
    async (dataArg) => {
      if (!canAcceptSocketPush()) return {};
      const state = getRequiredState(appStoreStatePart);
      await appStoreStatePart.setState({
        ...state,
        upgradeOperations: upsertUpgradeOperation(state.upgradeOperations, dataArg.operation),
        upgradeableServices: dataArg.operation.status === 'success'
          ? state.upgradeableServices.filter((service) => service.serviceName !== dataArg.operation.serviceName)
          : state.upgradeableServices,
      });
      if (!canAcceptSocketPush()) return {};
      if (dataArg.operation.service) {
        const servicesState = getRequiredState(servicesStatePart);
        await servicesStatePart.setState({
          ...servicesState,
          services: upsertService(servicesState.services, dataArg.operation.service),
          currentService: servicesState.currentService?.name === dataArg.operation.service.name
            ? dataArg.operation.service
            : servicesState.currentService,
        });
      }
      return {};
    },
  ),
);

socketRouter.addTypedHandler(
  new plugins.domtools.plugins.typedrequest.TypedHandler<interfaces.requests.IReq_PushTelemetryLog>(
    'pushTelemetryLog',
    async (dataArg) => {
      if (!canAcceptSocketPush()) return {};
      const state = getRequiredState(servicesStatePart);
      if (dataArg.target.type === 'serviceLogs') {
        if (!state.currentService || state.currentService.name !== dataArg.target.serviceName) return {};
        await servicesStatePart.setState({
          ...state,
          currentServiceLogs: appendLogEntry(state.currentServiceLogs, dataArg.entry),
        });
      } else if (dataArg.target.type === 'platformServiceLogs') {
        if (!activeTelemetrySubscriptions.has(getTelemetryTargetKey(dataArg.target))) return {};
        await servicesStatePart.setState({
          ...state,
          currentPlatformServiceLogs: appendLogEntry(state.currentPlatformServiceLogs, dataArg.entry),
        });
      }
      return {};
    },
  ),
);

socketRouter.addTypedHandler(
  new plugins.domtools.plugins.typedrequest.TypedHandler<interfaces.requests.IReq_PushTelemetryMetric>(
    'pushTelemetryMetric',
    async (dataArg) => {
      if (!canAcceptSocketPush()) return {};
      const state = getRequiredState(servicesStatePart);
      if (dataArg.target.type === 'serviceMetrics') {
        if (!state.currentService || state.currentService.name !== dataArg.target.serviceName) return {};
        await servicesStatePart.setState({
          ...state,
          currentServiceStats: dataArg.stats,
        });
      } else if (dataArg.target.type === 'platformServiceMetrics') {
        if (!activeTelemetrySubscriptions.has(getTelemetryTargetKey(dataArg.target))) return {};
        await servicesStatePart.setState({
          ...state,
          currentPlatformServiceStats: dataArg.stats,
        });
      }
      return {};
    },
  ),
);

// Handle server-pushed platform service log entries
socketRouter.addTypedHandler(
  new plugins.domtools.plugins.typedrequest.TypedHandler<interfaces.requests.IReq_PushPlatformServiceLog>(
    'pushPlatformServiceLog',
    async (dataArg) => {
      if (!canAcceptSocketPush()) return {};
      const state = getRequiredState(servicesStatePart);
      const entry: interfaces.data.ILogEntry = {
        serviceId: 0,
        timestamp: new Date(dataArg.entry.timestamp).getTime(),
        message: dataArg.entry.message,
        level: dataArg.entry.level as 'info' | 'warn' | 'error' | 'debug',
        source: 'stdout',
      };
      await servicesStatePart.setState({
        ...state,
        currentPlatformServiceLogs: appendLogEntry(state.currentPlatformServiceLogs, entry),
      });
      return {};
    },
  ),
);

// Handle server-pushed user service log entries
socketRouter.addTypedHandler(
  new plugins.domtools.plugins.typedrequest.TypedHandler<interfaces.requests.IReq_PushServiceLog>(
    'pushServiceLog',
    async (dataArg) => {
      if (!canAcceptSocketPush()) return {};
      const state = getRequiredState(servicesStatePart);
      // Only append if we're currently viewing this service
      if (!state.currentService || state.currentService.name !== dataArg.serviceName) {
        return {};
      }
      const entry: interfaces.data.ILogEntry = {
        serviceId: 0,
        timestamp: new Date(dataArg.entry.timestamp).getTime(),
        message: dataArg.entry.message,
        level: dataArg.entry.level as 'info' | 'warn' | 'error' | 'debug',
        source: 'stdout',
      };
      await servicesStatePart.setState({
        ...state,
        currentServiceLogs: appendLogEntry(state.currentServiceLogs, entry),
      });
      return {};
    },
  ),
);

async function connectSocket() {
  if (socketClient) return;
  if (socketClientPromise) return socketClientPromise;
  const session = getRequiredState(loginStatePart);
  if (!isAuthenticatedSessionState(session)) return;
  const sessionGeneration = session.generation;
  socketDesired = true;
  const generation = ++socketConnectionGeneration;
  let connectPromise!: Promise<void>;
  connectPromise = (async () => {
    let nextClient: Awaited<ReturnType<typeof plugins.typedsocket.TypedSocket.createClient>> | null = null;
    let nextStatusSubscription: { unsubscribe: () => void } | null = null;
    try {
      nextClient = await plugins.typedsocket.TypedSocket.createClient(
        socketRouter,
        plugins.typedsocket.TypedSocket.useWindowLocationOriginUrl(),
        { autoReconnect: true },
      );
      if (
        !socketDesired
        || generation !== socketConnectionGeneration
        || sessionGeneration !== requestScheduler.currentGeneration
        || !isSessionGenerationCurrent(getRequiredState(loginStatePart), sessionGeneration)
      ) {
        await nextClient.stop();
        return;
      }
      nextStatusSubscription = nextClient.statusSubject.subscribe((status: string) => {
        if (status === 'connected') {
          void handleSocketConnected();
        }
      });
      socketStatusSubscription?.unsubscribe();
      socketClient = nextClient;
      activeSocketSessionGeneration = sessionGeneration;
      socketStatusSubscription = nextStatusSubscription;
      await handleSocketConnected();
      nextClient = null;
      nextStatusSubscription = null;
      console.log('TypedSocket dashboard connection established');
    } catch (err) {
      console.error('TypedSocket connection failed:', err);
      nextStatusSubscription?.unsubscribe();
      if (nextClient) {
        await nextClient.stop().catch(() => {});
      }
      if (generation === socketConnectionGeneration) {
        activeSocketSessionGeneration = null;
        if (socketClient === nextClient) {
          socketClient = null;
        }
        if (socketStatusSubscription === nextStatusSubscription) {
          socketStatusSubscription = null;
        }
      }
    } finally {
      if (socketClientPromise === connectPromise) {
        socketClientPromise = null;
      }
    }
  })();
  socketClientPromise = connectPromise;
  return socketClientPromise;
}

async function handleSocketConnected(): Promise<void> {
  if (!socketClient || socketClient.getStatus() !== 'connected') return;
  await socketClient.setTag('role', 'ops_dashboard');
  await resubscribeTelemetryTargets();
}

async function disconnectSocket() {
  socketDesired = false;
  activeSocketSessionGeneration = null;
  socketConnectionGeneration++;
  const currentSocket = socketClient;
  const currentConnectPromise = socketClientPromise;
  const currentStatusSubscription = socketStatusSubscription;
  socketClientPromise = null;
  await withTimeout(
    pauseTelemetrySubscriptions(),
    5_000,
    'Telemetry shutdown timed out',
  ).catch(() => {});
  socketClient = null;
  socketStatusSubscription = null;
  currentStatusSubscription?.unsubscribe();
  if (currentSocket) {
    await withTimeout(
      currentSocket.stop(),
      5_000,
      'Socket shutdown timed out',
    ).catch(() => {});
  }
  if (currentConnectPromise) {
    await withTimeout(
      currentConnectPromise,
      5_000,
      'Pending socket connection shutdown timed out',
    ).catch(() => {});
  }
  activeTelemetrySubscriptions.clear();
  stopTelemetryRenewal();
}

if (typeof document !== 'undefined') {
  document.addEventListener('visibilitychange', () => {
    if (document.visibilityState === 'hidden') {
      void pauseTelemetrySubscriptions();
      stopTelemetryRenewal();
    } else {
      if (activeTelemetrySubscriptions.size > 0) {
        startTelemetryRenewal();
      }
      void resubscribeTelemetryTargets();
    }
  });
}

if (typeof window !== 'undefined') {
  window.addEventListener('pagehide', () => {
    void pauseTelemetrySubscriptions();
    stopTelemetryRenewal();
  });
}

// ============================================================================
// App Store Actions
// ============================================================================

export const fetchAppStoreTemplatesAction = appStoreStatePart.createAction(
  async (statePartArg) => {
    const context = getActionContext();
    try {
      const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_GetAppStoreTemplates
      >('/typedrequest', 'getAppStoreTemplates');
      const response = await typedRequest.fire({ identity: context.identity! });
      return { ...getRequiredState(statePartArg), apps: response.apps, sourceInfo: response.sourceInfo };
    } catch (err) {
      await handleProtectedActionError(err, 'Failed to fetch app templates:');
      return getRequiredState(statePartArg);
    }
  },
);

export const fetchUpgradeableAppStoreServicesAction = appStoreStatePart.createAction(
  async (statePartArg) => {
    const context = getActionContext();
    try {
      const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_GetUpgradeableAppStoreServices
      >('/typedrequest', 'getUpgradeableAppStoreServices');
      const response = await typedRequest.fire({ identity: context.identity! });
      return { ...getRequiredState(statePartArg), upgradeableServices: response.services };
    } catch (err) {
      await handleProtectedActionError(err, 'Failed to fetch upgradeable services:');
      return getRequiredState(statePartArg);
    }
  },
);

export const fetchAppStoreUpgradeOperationsAction = appStoreStatePart.createAction(
  async (statePartArg) => {
    const context = getActionContext();
    try {
      const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_GetAppStoreUpgradeOperations
      >('/typedrequest', 'getAppStoreUpgradeOperations');
      const response = await typedRequest.fire({ identity: context.identity! });
      return { ...getRequiredState(statePartArg), upgradeOperations: response.operations };
    } catch (err) {
      await handleProtectedActionError(err, 'Failed to fetch upgrade operations:');
      return getRequiredState(statePartArg);
    }
  },
);

export const upgradeAppStoreServiceAction = appStoreStatePart.createAction<{
  serviceName: string;
  targetVersion: string;
}>(async (statePartArg, dataArg) => {
  const context = getActionContext();
  try {
    const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
      interfaces.requests.IReq_StartAppStoreServiceUpgrade
    >('/typedrequest', 'startAppStoreServiceUpgrade');
    const response = await typedRequest.fire({
      identity: context.identity!,
      serviceName: dataArg.serviceName,
      targetVersion: dataArg.targetVersion,
    });
    const state = getRequiredState(statePartArg);
    return {
      ...state,
      upgradeOperations: upsertUpgradeOperation(state.upgradeOperations, response.operation),
    };
  } catch (err) {
    await handleProtectedActionError(err, 'Failed to upgrade service:');
    return getRequiredState(statePartArg);
  }
});

registerProtectedActionPolicy(
  upgradeAppStoreServiceAction,
  { mode: 'mutation', domain: 'appstore' },
);

// Connect socket when logged in, disconnect when logged out
loginStatePart.select((s) => s).subscribe((loginState) => {
  if (isAuthenticatedSessionState(loginState)) {
    void connectSocket();
  }
});
