import { useCallback, useEffect, useRef, useState, type MutableRefObject } from 'react';
import type {
  DashboardBackup,
  DashboardBackupInfo,
  DashboardInstanceInfo,
  DashboardModelCreditUsage,
  DashboardApifyConnectionStatus,
  DashboardPosthogConnectionStatus,
  DashboardPosthogOpenResult,
  DashboardProjectInfo,
  DashboardUserInfo,
  DashboardMetricName,
  DashboardMetricsRange,
  DashboardMetricsResponse,
  DashboardAdvisorSummary,
  DashboardAdvisorIssuesQuery,
  DashboardAdvisorIssuesResponse,
} from '@insforge/dashboard';
import { partnerService } from './partner.service';

const VALID_METRICS_RANGES: readonly DashboardMetricsRange[] = ['1h', '6h', '24h', '3d'] as const;
const VALID_METRIC_NAMES: readonly DashboardMetricName[] = [
  'cpu_usage',
  'memory_usage',
  'disk_usage',
  'disk_used',
  'disk_total',
  'network_in',
  'network_out',
] as const;
const VALID_ADVISOR_SEVERITIES = ['critical', 'warning', 'info'] as const;
const VALID_ADVISOR_CATEGORIES = ['security', 'performance', 'health'] as const;

type InstanceTypeChangeResult = {
  success: boolean;
  instanceType?: string;
  error?: string;
};

type CloudHostingMessage = {
  type: string;
  [key: string]: unknown;
};

type PendingRequestKey =
  | 'authCode'
  | 'backupInfo'
  | 'createBackup'
  | 'deleteBackup'
  | 'renameBackup'
  | 'restoreBackup'
  | 'instanceInfo'
  | 'instanceTypeChange'
  | 'renameProject'
  | 'deleteProject'
  | 'updateVersion'
  | 'userInfo'
  | 'userApiKey'
  | 'modelCredits'
  | 'projectMetrics'
  | 'advisorLatest'
  | 'advisorIssues'
  | 'advisorScan';

type PendingRequest<T> = {
  resolve: (value: T) => void;
  reject: (error: Error) => void;
  timeoutId: number;
};

type PendingRequestValues = {
  authCode: string;
  backupInfo: DashboardBackupInfo;
  createBackup: void;
  deleteBackup: void;
  renameBackup: void;
  restoreBackup: void;
  instanceInfo: DashboardInstanceInfo;
  instanceTypeChange: InstanceTypeChangeResult;
  renameProject: void;
  deleteProject: void;
  updateVersion: void;
  userInfo: DashboardUserInfo;
  userApiKey: string;
  modelCredits: DashboardModelCreditUsage;
  projectMetrics: DashboardMetricsResponse;
  advisorLatest: DashboardAdvisorSummary;
  advisorIssues: DashboardAdvisorIssuesResponse;
  advisorScan: void;
};

type PendingRequests = {
  [K in PendingRequestKey]?: PendingRequest<PendingRequestValues[K]>;
};

const DEFAULT_TIMEOUT_MS = 15000;
const INSTANCE_CHANGE_TIMEOUT_MS = 5 * 60 * 1000;
const INSFORGE_ROOT_ORIGIN = 'https://insforge.dev';
const INSFORGE_SUBDOMAIN_SUFFIX = '.insforge.dev';

function normalizeUrl(url: string) {
  return url.replace(/\/$/, '');
}

function normalizeOrigin(value: string): string | null {
  try {
    return new URL(value).origin;
  } catch {
    return null;
  }
}

function isInsForgeOrigin(origin: string): boolean {
  try {
    const url = new URL(origin);
    return (
      url.protocol === 'https:' &&
      (url.origin === INSFORGE_ROOT_ORIGIN ||
        (url.port === '' && url.hostname.endsWith(INSFORGE_SUBDOMAIN_SUFFIX)))
    );
  } catch {
    return false;
  }
}

async function isTrustedCloudOrigin(origin: string): Promise<boolean> {
  const normalizedOrigin = normalizeOrigin(origin);
  if (!normalizedOrigin) {
    return false;
  }

  if (isInsForgeOrigin(normalizedOrigin)) {
    return true;
  }

  const partnerOrigins = await partnerService.fetchPartnerOrigins();
  return partnerOrigins.has(normalizedOrigin);
}

async function establishTrustedOrigin(
  candidateOrigin: string | null,
  originRef: MutableRefObject<string | null>,
  trustedRef: MutableRefObject<boolean>
): Promise<string | null> {
  const normalizedOrigin = candidateOrigin ? normalizeOrigin(candidateOrigin) : null;
  if (!normalizedOrigin) {
    return null;
  }

  const expectedOrigin = originRef.current;
  if (expectedOrigin && normalizedOrigin !== expectedOrigin) {
    return null;
  }

  if (trustedRef.current) {
    return originRef.current === normalizedOrigin ? normalizedOrigin : null;
  }

  if (!(await isTrustedCloudOrigin(normalizedOrigin))) {
    return null;
  }

  if (trustedRef.current) {
    return originRef.current === normalizedOrigin ? normalizedOrigin : null;
  }

  if (originRef.current && originRef.current !== normalizedOrigin) {
    return null;
  }

  originRef.current = normalizedOrigin;
  trustedRef.current = true;
  return normalizedOrigin;
}

function getParentWindow(): Window | null {
  if (typeof window === 'undefined' || window.parent === window) {
    return null;
  }

  return window.parent;
}

function getOpenerWindow(): Window | null {
  if (typeof window === 'undefined' || !window.opener || window.opener.closed) {
    return null;
  }

  return window.opener;
}

function getParentOrigin(): string | null {
  if (typeof window === 'undefined' || !getParentWindow() || !document.referrer) {
    return null;
  }

  try {
    return new URL(document.referrer).origin;
  } catch {
    return null;
  }
}

function getCurrentOrigin(): string {
  if (typeof window !== 'undefined') {
    return normalizeUrl(window.location.origin);
  }

  return '';
}

function getErrorMessage(message: unknown, fallback: string): string {
  return typeof message === 'string' && message.trim() ? message : fallback;
}

function normalizeBackups(backups: unknown): DashboardBackup[] {
  if (!Array.isArray(backups)) {
    return [];
  }

  return backups.flatMap((backup) => {
    const item = backup as Record<string, unknown>;
    const id = typeof item.id === 'string' ? item.id.trim() : '';

    if (!id) {
      return [];
    }

    return {
      id,
      name:
        typeof item.name === 'string' || item.name === null ? (item.name as string | null) : null,
      triggerSource: item.triggerSource === 'scheduled' ? 'scheduled' : 'manual',
      status: typeof item.status === 'string' ? item.status : '',
      sizeBytes:
        typeof item.sizeBytes === 'number' || item.sizeBytes === null
          ? (item.sizeBytes as number | null)
          : null,
      expiresAt:
        typeof item.expiresAt === 'string' || item.expiresAt === null
          ? (item.expiresAt as string | null)
          : null,
      createdAt: typeof item.createdAt === 'string' ? item.createdAt : '',
      createdBy:
        typeof item.createdBy === 'string' || item.createdBy === null
          ? (item.createdBy as string | null)
          : null,
    };
  });
}

function normalizeProjectInfo(
  previous: DashboardProjectInfo | undefined,
  origin: string,
  message: CloudHostingMessage
): DashboardProjectInfo {
  const previousInfo = previous ?? {
    id: origin,
    name: 'Project',
    region: '',
    instanceType: '',
  };

  return {
    id: typeof message.id === 'string' && message.id ? message.id : previousInfo.id,
    name: typeof message.name === 'string' && message.name ? message.name : previousInfo.name,
    region:
      typeof message.region === 'string' && message.region ? message.region : previousInfo.region,
    instanceType:
      typeof message.instanceType === 'string' && message.instanceType
        ? message.instanceType
        : previousInfo.instanceType,
    latestVersion:
      typeof message.latestVersion === 'string' || message.latestVersion === null
        ? (message.latestVersion as string | null)
        : previousInfo.latestVersion,
    currentVersion:
      typeof message.currentVersion === 'string' || message.currentVersion === null
        ? (message.currentVersion as string | null)
        : previousInfo.currentVersion,
    status:
      typeof message.status === 'string' && message.status ? message.status : previousInfo.status,
    isBranch: typeof message.isBranch === 'boolean' ? message.isBranch : previousInfo.isBranch,
  };
}

export function useCloudHosting() {
  const currentOrigin = getCurrentOrigin();
  // PROJECT_INFO can only arrive from a parent iframe: REQUEST_PROJECT_INFO is
  // posted to the parent window, and opener messages are restricted to the
  // authorization-code types. When the dashboard runs in its own tab (partner
  // new-tab flow), start from a default so consumers that wait on project info
  // (e.g. the cloud login redirect) don't block forever.
  const [projectInfo, setProjectInfo] = useState<DashboardProjectInfo | undefined>(() =>
    getParentWindow()
      ? undefined
      : { id: currentOrigin, name: 'Project', region: '', instanceType: '' }
  );
  const parentOriginRef = useRef<string | null>(getParentOrigin());
  const openerOriginRef = useRef<string | null>(null);
  const parentOriginTrustedRef = useRef(false);
  const openerOriginTrustedRef = useRef(false);
  const pendingRequestsRef = useRef<PendingRequests>({});
  const posthogStatusSubscribersRef = useRef<
    Set<(event: DashboardPosthogConnectionStatus) => void>
  >(new Set());
  const apifyStatusSubscribersRef = useRef<Set<(event: DashboardApifyConnectionStatus) => void>>(
    new Set()
  );

  const setPendingRequest = useCallback(
    <K extends PendingRequestKey>(
      key: K,
      pendingRequest: PendingRequest<PendingRequestValues[K]>
    ) => {
      switch (key) {
        case 'authCode':
          pendingRequestsRef.current.authCode = pendingRequest as PendingRequest<string>;
          return;
        case 'backupInfo':
          pendingRequestsRef.current.backupInfo =
            pendingRequest as PendingRequest<DashboardBackupInfo>;
          return;
        case 'createBackup':
          pendingRequestsRef.current.createBackup = pendingRequest as PendingRequest<void>;
          return;
        case 'deleteBackup':
          pendingRequestsRef.current.deleteBackup = pendingRequest as PendingRequest<void>;
          return;
        case 'renameBackup':
          pendingRequestsRef.current.renameBackup = pendingRequest as PendingRequest<void>;
          return;
        case 'restoreBackup':
          pendingRequestsRef.current.restoreBackup = pendingRequest as PendingRequest<void>;
          return;
        case 'instanceInfo':
          pendingRequestsRef.current.instanceInfo =
            pendingRequest as PendingRequest<DashboardInstanceInfo>;
          return;
        case 'instanceTypeChange':
          pendingRequestsRef.current.instanceTypeChange =
            pendingRequest as PendingRequest<InstanceTypeChangeResult>;
          return;
        case 'renameProject':
          pendingRequestsRef.current.renameProject = pendingRequest as PendingRequest<void>;
          return;
        case 'deleteProject':
          pendingRequestsRef.current.deleteProject = pendingRequest as PendingRequest<void>;
          return;
        case 'updateVersion':
          pendingRequestsRef.current.updateVersion = pendingRequest as PendingRequest<void>;
          return;
        case 'userInfo':
          pendingRequestsRef.current.userInfo = pendingRequest as PendingRequest<DashboardUserInfo>;
          return;
        case 'userApiKey':
          pendingRequestsRef.current.userApiKey = pendingRequest as PendingRequest<string>;
          return;
        case 'modelCredits':
          pendingRequestsRef.current.modelCredits =
            pendingRequest as PendingRequest<DashboardModelCreditUsage>;
          return;
        case 'projectMetrics':
          pendingRequestsRef.current.projectMetrics =
            pendingRequest as PendingRequest<DashboardMetricsResponse>;
          return;
        case 'advisorLatest':
          pendingRequestsRef.current.advisorLatest =
            pendingRequest as PendingRequest<DashboardAdvisorSummary>;
          return;
        case 'advisorIssues':
          pendingRequestsRef.current.advisorIssues =
            pendingRequest as PendingRequest<DashboardAdvisorIssuesResponse>;
          return;
        case 'advisorScan':
          pendingRequestsRef.current.advisorScan = pendingRequest as PendingRequest<void>;
          return;
        default: {
          const exhaustiveKey: never = key;
          throw new Error(`Unhandled pending request key: ${exhaustiveKey}`);
        }
      }
    },
    []
  );

  const clearPendingRequest = useCallback((key: PendingRequestKey) => {
    const pendingRequest = pendingRequestsRef.current[key];
    if (!pendingRequest) {
      return;
    }

    window.clearTimeout(pendingRequest.timeoutId);
    delete pendingRequestsRef.current[key];
  }, []);

  const rejectPendingRequest = useCallback(
    <K extends PendingRequestKey>(key: K, message: string) => {
      const pendingRequest = pendingRequestsRef.current[key];
      if (!pendingRequest) {
        return;
      }

      clearPendingRequest(key);
      pendingRequest.reject(new Error(message));
    },
    [clearPendingRequest]
  );

  const resolvePendingRequest = useCallback(
    <K extends PendingRequestKey>(key: K, value: PendingRequestValues[K]) => {
      const pendingRequest = pendingRequestsRef.current[key];
      if (!pendingRequest) {
        return;
      }

      clearPendingRequest(key);
      pendingRequest.resolve(value);
    },
    [clearPendingRequest]
  );

  const ensureTrustedParentOrigin = useCallback(
    () =>
      establishTrustedOrigin(
        parentOriginRef.current ?? getParentOrigin(),
        parentOriginRef,
        parentOriginTrustedRef
      ),
    []
  );

  const postMessageToParent = useCallback(
    async (message: CloudHostingMessage): Promise<boolean> => {
      const parentOrigin = await ensureTrustedParentOrigin();
      const parentWindow = getParentWindow();
      if (!parentWindow || !parentOrigin) {
        return false;
      }

      parentWindow.postMessage(message, parentOrigin);
      return true;
    },
    [ensureTrustedParentOrigin]
  );

  const sendMessageToParent = useCallback(
    async (message: CloudHostingMessage, errorMessage: string): Promise<void> => {
      if (!(await postMessageToParent(message))) {
        throw new Error(errorMessage);
      }
    },
    [postMessageToParent]
  );

  const createPendingRequest = useCallback(
    <K extends PendingRequestKey>(
      key: K,
      actionLabel: string,
      options?: { timeoutMs?: number; supersede?: boolean }
    ) =>
      new Promise<PendingRequestValues[K]>((resolve, reject) => {
        const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
        if (pendingRequestsRef.current[key]) {
          if (options?.supersede) {
            rejectPendingRequest(key, `${actionLabel} superseded by newer request`);
          } else {
            reject(new Error(`${actionLabel} is already in progress`));
            return;
          }
        }

        const timeoutId = window.setTimeout(() => {
          rejectPendingRequest(key, `${actionLabel} timed out`);
        }, timeoutMs);

        setPendingRequest(key, {
          resolve: resolve as (value: PendingRequestValues[K]) => void,
          reject: (error: Error) => reject(error),
          timeoutId,
        });
      }),
    [rejectPendingRequest, setPendingRequest]
  );

  useEffect(() => {
    const handleMessage = (event: MessageEvent<CloudHostingMessage>) => {
      void (async () => {
        const isParentMessage = event.source === getParentWindow();
        const isOpenerMessage = event.source === getOpenerWindow();

        if (!isParentMessage && !isOpenerMessage) {
          return;
        }

        if (isParentMessage) {
          const trustedOrigin = await establishTrustedOrigin(
            event.origin,
            parentOriginRef,
            parentOriginTrustedRef
          );
          if (!trustedOrigin) {
            return;
          }
        } else {
          const trustedOrigin = await establishTrustedOrigin(
            event.origin,
            openerOriginRef,
            openerOriginTrustedRef
          );
          if (!trustedOrigin) {
            return;
          }
        }

        const message = event.data;
        if (!message || typeof message !== 'object' || typeof message.type !== 'string') {
          return;
        }

        if (
          isOpenerMessage &&
          message.type !== 'AUTHORIZATION_CODE' &&
          message.type !== 'AUTHORIZATION_CODE_ERROR' &&
          message.type !== 'AUTH_ERROR'
        ) {
          return;
        }

        switch (message.type) {
          case 'AUTHORIZATION_CODE': {
            const code =
              typeof message.code === 'string' && message.code.trim() ? message.code : null;

            if (!code) {
              rejectPendingRequest('authCode', 'Received an invalid authorization code');
              return;
            }

            if (pendingRequestsRef.current.authCode) {
              resolvePendingRequest('authCode', code);
            }

            return;
          }
          case 'AUTHORIZATION_CODE_ERROR':
          case 'AUTH_ERROR': {
            rejectPendingRequest(
              'authCode',
              getErrorMessage(
                message.error ?? message.message,
                'Failed to generate authorization code'
              )
            );
            return;
          }
          case 'PROJECT_INFO': {
            setProjectInfo((previous) => normalizeProjectInfo(previous, currentOrigin, message));
            return;
          }
          case 'BACKUP_INFO': {
            resolvePendingRequest('backupInfo', {
              manualBackups: normalizeBackups(message.manualBackups),
              scheduledBackups: normalizeBackups(message.scheduledBackups),
            });
            return;
          }
          case 'BACKUP_INFO_ERROR': {
            rejectPendingRequest(
              'backupInfo',
              getErrorMessage(message.error, 'Failed to load backup information')
            );
            return;
          }
          case 'BACKUP_CREATE_RESULT': {
            if (message.success === true) {
              resolvePendingRequest('createBackup', undefined);
              return;
            }

            rejectPendingRequest(
              'createBackup',
              getErrorMessage(message.error, 'Failed to create backup')
            );
            return;
          }
          case 'BACKUP_DELETE_RESULT': {
            if (message.success === true) {
              resolvePendingRequest('deleteBackup', undefined);
              return;
            }

            rejectPendingRequest(
              'deleteBackup',
              getErrorMessage(message.error, 'Failed to delete backup')
            );
            return;
          }
          case 'BACKUP_RENAME_RESULT': {
            if (message.success === true) {
              resolvePendingRequest('renameBackup', undefined);
              return;
            }

            rejectPendingRequest(
              'renameBackup',
              getErrorMessage(message.error, 'Failed to rename backup')
            );
            return;
          }
          case 'BACKUP_RESTORE_RESULT': {
            if (message.success === true) {
              resolvePendingRequest('restoreBackup', undefined);
              return;
            }

            rejectPendingRequest(
              'restoreBackup',
              getErrorMessage(message.error, 'Failed to restore backup')
            );
            return;
          }
          case 'USER_INFO': {
            const userId = typeof message.userId === 'string' ? message.userId : '';
            const email = typeof message.email === 'string' ? message.email : '';
            if (!userId || !email) {
              rejectPendingRequest('userInfo', 'Received an invalid user info payload');
              return;
            }
            resolvePendingRequest('userInfo', {
              userId,
              email,
              name: typeof message.name === 'string' ? message.name : undefined,
            });
            return;
          }
          case 'USER_API_KEY': {
            const apiKey =
              typeof message.apiKey === 'string' && message.apiKey.trim() ? message.apiKey : '';
            if (!apiKey) {
              rejectPendingRequest('userApiKey', 'Received an invalid user API key payload');
              return;
            }
            resolvePendingRequest('userApiKey', apiKey);
            return;
          }
          case 'USER_API_KEY_ERROR': {
            rejectPendingRequest(
              'userApiKey',
              getErrorMessage(message.error, 'Failed to create user API key')
            );
            return;
          }
          case 'MODEL_CREDITS': {
            const used =
              typeof message.used === 'number' && Number.isFinite(message.used) ? message.used : 0;
            const limit =
              typeof message.limit === 'number' && Number.isFinite(message.limit)
                ? message.limit
                : 0;

            resolvePendingRequest('modelCredits', {
              used,
              limit,
              isFree: message.isFree === true,
            });
            return;
          }
          case 'MODEL_CREDITS_ERROR': {
            rejectPendingRequest(
              'modelCredits',
              getErrorMessage(message.error, 'Failed to load model credit usage')
            );
            return;
          }
          case 'INSTANCE_INFO': {
            resolvePendingRequest('instanceInfo', {
              currentInstanceType:
                typeof message.currentInstanceType === 'string' ? message.currentInstanceType : '',
              planName: typeof message.planName === 'string' ? message.planName : '',
              computeCredits:
                typeof message.computeCredits === 'number' ? message.computeCredits : 0,
              currentOrgComputeCost:
                typeof message.currentOrgComputeCost === 'number'
                  ? message.currentOrgComputeCost
                  : 0,
              instanceTypes: Array.isArray(message.instanceTypes)
                ? (message.instanceTypes as DashboardInstanceInfo['instanceTypes'])
                : [],
              projects: Array.isArray(message.projects)
                ? (message.projects as DashboardInstanceInfo['projects'])
                : [],
            });
            return;
          }
          case 'INSTANCE_TYPE_CHANGE_RESULT': {
            resolvePendingRequest('instanceTypeChange', {
              success: Boolean(message.success),
              instanceType:
                typeof message.instanceType === 'string' ? message.instanceType : undefined,
              error: typeof message.error === 'string' ? message.error : undefined,
            });
            return;
          }
          case 'PROJECT_NAME_UPDATE_RESULT': {
            if (message.success === true) {
              if (typeof message.name === 'string' && message.name.trim()) {
                setProjectInfo((previous) =>
                  normalizeProjectInfo(previous, currentOrigin, {
                    type: 'PROJECT_INFO',
                    name: message.name,
                  })
                );
              }
              resolvePendingRequest('renameProject', undefined);
              return;
            }

            rejectPendingRequest(
              'renameProject',
              getErrorMessage(message.error, 'Failed to update project name')
            );
            return;
          }
          case 'DELETE_PROJECT_RESULT': {
            if (message.success === true) {
              resolvePendingRequest('deleteProject', undefined);
              return;
            }

            rejectPendingRequest(
              'deleteProject',
              getErrorMessage(message.error, 'Failed to delete project')
            );
            return;
          }
          case 'VERSION_UPDATE_STARTED': {
            resolvePendingRequest('updateVersion', undefined);
            return;
          }
          case 'VERSION_UPDATE_RESULT': {
            if (message.success === true) {
              resolvePendingRequest('updateVersion', undefined);
              return;
            }

            rejectPendingRequest(
              'updateVersion',
              getErrorMessage(message.error, 'Failed to update project version')
            );
            return;
          }
          case 'PROJECT_METRICS': {
            const range: DashboardMetricsRange = VALID_METRICS_RANGES.includes(
              message.range as DashboardMetricsRange
            )
              ? (message.range as DashboardMetricsRange)
              : '1h';
            const metrics = Array.isArray(message.metrics)
              ? message.metrics.flatMap((entry: unknown) => {
                  if (!entry || typeof entry !== 'object') {
                    return [];
                  }
                  const m = entry as Record<string, unknown>;
                  if (!VALID_METRIC_NAMES.includes(m.metric as DashboardMetricName)) {
                    return [];
                  }
                  return [
                    {
                      metric: m.metric as DashboardMetricName,
                      instanceId: typeof m.instanceId === 'string' ? m.instanceId : undefined,
                      data: Array.isArray(m.data)
                        ? m.data.flatMap((sample: unknown) => {
                            if (!sample || typeof sample !== 'object') {
                              return [];
                            }
                            const s = sample as Record<string, unknown>;
                            if (
                              typeof s.timestamp !== 'number' ||
                              !Number.isFinite(s.timestamp) ||
                              typeof s.value !== 'number' ||
                              !Number.isFinite(s.value)
                            ) {
                              return [];
                            }
                            return [{ timestamp: s.timestamp, value: s.value }];
                          })
                        : [],
                    },
                  ];
                })
              : [];
            resolvePendingRequest('projectMetrics', { range, metrics });
            return;
          }
          case 'PROJECT_METRICS_ERROR': {
            rejectPendingRequest(
              'projectMetrics',
              message.code === 'unavailable'
                ? 'METRICS_UNAVAILABLE'
                : getErrorMessage(message.error, 'Failed to load metrics')
            );
            return;
          }
          case 'ADVISOR_LATEST': {
            const summaryRaw = message.summary as Record<string, unknown> | undefined;
            const finiteCount = (key: string): number => {
              const v = summaryRaw?.[key];
              return typeof v === 'number' && Number.isFinite(v) ? v : 0;
            };
            resolvePendingRequest('advisorLatest', {
              scanId: typeof message.scanId === 'string' ? message.scanId : '',
              status:
                message.status === 'running' || message.status === 'failed'
                  ? message.status
                  : 'completed',
              scanType: message.scanType === 'manual' ? 'manual' : 'scheduled',
              scannedAt: typeof message.scannedAt === 'string' ? message.scannedAt : '',
              summary: {
                total: finiteCount('total'),
                critical: finiteCount('critical'),
                warning: finiteCount('warning'),
                info: finiteCount('info'),
              },
            });
            return;
          }
          case 'ADVISOR_LATEST_ERROR': {
            rejectPendingRequest(
              'advisorLatest',
              getErrorMessage(message.error, 'Failed to load advisor summary')
            );
            return;
          }
          case 'ADVISOR_ISSUES': {
            type AdvisorIssue = DashboardAdvisorIssuesResponse['issues'][number];
            const issues = Array.isArray(message.issues)
              ? message.issues.flatMap((entry: unknown): AdvisorIssue[] => {
                  if (!entry || typeof entry !== 'object') {
                    return [];
                  }
                  const i = entry as Record<string, unknown>;
                  if (!VALID_ADVISOR_SEVERITIES.includes(i.severity as AdvisorIssue['severity'])) {
                    return [];
                  }
                  if (!VALID_ADVISOR_CATEGORIES.includes(i.category as AdvisorIssue['category'])) {
                    return [];
                  }
                  return [
                    {
                      id: typeof i.id === 'string' ? i.id : '',
                      ruleId: typeof i.ruleId === 'string' ? i.ruleId : '',
                      severity: i.severity as AdvisorIssue['severity'],
                      category: i.category as AdvisorIssue['category'],
                      title: typeof i.title === 'string' ? i.title : '',
                      description: typeof i.description === 'string' ? i.description : '',
                      affectedObject:
                        typeof i.affectedObject === 'string' ? i.affectedObject : undefined,
                      recommendation:
                        typeof i.recommendation === 'string' ? i.recommendation : undefined,
                    },
                  ];
                })
              : [];
            const totalRaw = message.total;
            const total =
              typeof totalRaw === 'number' && Number.isFinite(totalRaw) && totalRaw >= 0
                ? Math.floor(totalRaw)
                : issues.length;
            resolvePendingRequest('advisorIssues', { issues, total });
            return;
          }
          case 'ADVISOR_ISSUES_ERROR': {
            rejectPendingRequest(
              'advisorIssues',
              getErrorMessage(message.error, 'Failed to load advisor issues')
            );
            return;
          }
          case 'ADVISOR_SCAN_RESULT': {
            if (message.success === true) {
              resolvePendingRequest('advisorScan', undefined);
              return;
            }
            rejectPendingRequest(
              'advisorScan',
              getErrorMessage(message.error, 'Failed to trigger advisor scan')
            );
            return;
          }
          case 'POSTHOG_CONNECTION_STATUS': {
            const status = message.status;
            if (status !== 'connected' && status !== 'error' && status !== 'cancelled') {
              return;
            }
            const rawReason = typeof message.reason === 'string' ? message.reason : undefined;
            const reason = rawReason ? rawReason.slice(0, 200) : undefined;
            const timestamp =
              typeof message.timestamp === 'number' ? message.timestamp : Date.now();
            const event: DashboardPosthogConnectionStatus = { status, reason, timestamp };
            posthogStatusSubscribersRef.current.forEach((cb) => {
              try {
                cb(event);
              } catch {
                // Subscriber crashes shouldn't break delivery to other subscribers.
              }
            });
            return;
          }
          case 'APIFY_CONNECTION_STATUS': {
            const status = message.status;
            if (status !== 'connected' && status !== 'error' && status !== 'cancelled') {
              return;
            }
            const rawReason = typeof message.reason === 'string' ? message.reason : undefined;
            const reason = rawReason ? rawReason.slice(0, 200) : undefined;
            const timestamp =
              typeof message.timestamp === 'number' ? message.timestamp : Date.now();
            const event: DashboardApifyConnectionStatus = { status, reason, timestamp };
            apifyStatusSubscribersRef.current.forEach((cb) => {
              try {
                cb(event);
              } catch {
                // Subscriber crashes shouldn't break delivery to other subscribers.
              }
            });
            return;
          }
          default:
            return;
        }
      })();
    };

    const pendingRequests = pendingRequestsRef.current;

    window.addEventListener('message', handleMessage);
    return () => {
      window.removeEventListener('message', handleMessage);

      (Object.keys(pendingRequests) as PendingRequestKey[]).forEach((key) => {
        rejectPendingRequest(key, 'Cloud hosting was disposed');
      });
    };
  }, [currentOrigin, rejectPendingRequest, resolvePendingRequest]);

  useEffect(() => {
    void postMessageToParent({ type: 'REQUEST_PROJECT_INFO' });
  }, [postMessageToParent]);

  const getAuthorizationCode = useCallback(async (): Promise<string> => {
    // Even if the send fails, keep the pending request open so a proactive parent or opener
    // message can still resolve it when the cloud hosting transport finishes initializing.
    void postMessageToParent({ type: 'REQUEST_AUTHORIZATION_CODE' });

    return createPendingRequest('authCode', 'Authorization code request');
  }, [createPendingRequest, postMessageToParent]);

  const requestBackupInfo = useCallback(async (): Promise<DashboardBackupInfo> => {
    await sendMessageToParent(
      { type: 'REQUEST_BACKUP_INFO' },
      'Unable to request backup information from the parent window'
    );
    return createPendingRequest('backupInfo', 'Backup info request');
  }, [createPendingRequest, sendMessageToParent]);

  const createBackup = useCallback(
    async (name: string): Promise<void> => {
      await sendMessageToParent(
        { type: 'CREATE_BACKUP', name },
        'Unable to request a backup creation from the parent window'
      );
      return createPendingRequest('createBackup', 'Backup creation');
    },
    [createPendingRequest, sendMessageToParent]
  );

  const deleteBackup = useCallback(
    async (backupId: string): Promise<void> => {
      await sendMessageToParent(
        { type: 'DELETE_BACKUP', backupId },
        'Unable to request a backup deletion from the parent window'
      );
      return createPendingRequest('deleteBackup', 'Backup deletion');
    },
    [createPendingRequest, sendMessageToParent]
  );

  const renameBackup = useCallback(
    async (backupId: string, name: string | null): Promise<void> => {
      await sendMessageToParent(
        { type: 'RENAME_BACKUP', backupId, name },
        'Unable to request a backup rename from the parent window'
      );
      return createPendingRequest('renameBackup', 'Backup rename');
    },
    [createPendingRequest, sendMessageToParent]
  );

  const restoreBackup = useCallback(
    async (backupId: string): Promise<void> => {
      await sendMessageToParent(
        { type: 'RESTORE_BACKUP', backupId },
        'Unable to request a backup restore from the parent window'
      );
      return createPendingRequest('restoreBackup', 'Backup restore');
    },
    [createPendingRequest, sendMessageToParent]
  );

  const requestInstanceInfo = useCallback(async (): Promise<DashboardInstanceInfo> => {
    await sendMessageToParent(
      { type: 'REQUEST_INSTANCE_INFO' },
      'Unable to request instance information from the parent window'
    );
    return createPendingRequest('instanceInfo', 'Instance info request');
  }, [createPendingRequest, sendMessageToParent]);

  const requestInstanceTypeChange = useCallback(
    async (instanceType: string): Promise<InstanceTypeChangeResult> => {
      await sendMessageToParent(
        { type: 'REQUEST_INSTANCE_TYPE_CHANGE', instanceType },
        'Unable to request an instance type change from the parent window'
      );
      return createPendingRequest('instanceTypeChange', 'Instance type change', {
        timeoutMs: INSTANCE_CHANGE_TIMEOUT_MS,
      });
    },
    [createPendingRequest, sendMessageToParent]
  );

  const renameProject = useCallback(
    async (name: string): Promise<void> => {
      await sendMessageToParent(
        { type: 'UPDATE_PROJECT_NAME', name },
        'Unable to request a project rename from the parent window'
      );
      return createPendingRequest('renameProject', 'Project rename');
    },
    [createPendingRequest, sendMessageToParent]
  );

  const deleteProject = useCallback(async (): Promise<void> => {
    await sendMessageToParent(
      { type: 'DELETE_PROJECT' },
      'Unable to request project deletion from the parent window'
    );
    return createPendingRequest('deleteProject', 'Project deletion');
  }, [createPendingRequest, sendMessageToParent]);

  const updateVersion = useCallback(async (): Promise<void> => {
    await sendMessageToParent(
      { type: 'UPDATE_PROJECT_VERSION' },
      'Unable to request a project version update from the parent window'
    );
    return createPendingRequest('updateVersion', 'Project version update');
  }, [createPendingRequest, sendMessageToParent]);

  const requestUserInfo = useCallback(async (): Promise<DashboardUserInfo> => {
    await sendMessageToParent(
      { type: 'REQUEST_USER_INFO' },
      'Unable to request user info from the parent window'
    );
    return createPendingRequest('userInfo', 'User info request');
  }, [createPendingRequest, sendMessageToParent]);

  const requestUserApiKey = useCallback(async (): Promise<string> => {
    await sendMessageToParent(
      { type: 'REQUEST_USER_API_KEY' },
      'Unable to request a user API key from the parent window'
    );
    return createPendingRequest('userApiKey', 'User API key request');
  }, [createPendingRequest, sendMessageToParent]);

  const requestModelCredits = useCallback(async (): Promise<DashboardModelCreditUsage> => {
    await sendMessageToParent(
      { type: 'REQUEST_MODEL_CREDITS' },
      'Unable to request model credit usage from the parent window'
    );
    return createPendingRequest('modelCredits', 'Model credits request', {
      supersede: true,
    });
  }, [createPendingRequest, sendMessageToParent]);

  const requestProjectMetrics = useCallback(
    async (range: DashboardMetricsRange): Promise<DashboardMetricsResponse> => {
      await sendMessageToParent(
        { type: 'REQUEST_PROJECT_METRICS', range },
        'Unable to request project metrics from the parent window'
      );
      return createPendingRequest('projectMetrics', 'Project metrics request', {
        supersede: true,
      });
    },
    [createPendingRequest, sendMessageToParent]
  );

  const requestAdvisorLatest = useCallback(async (): Promise<DashboardAdvisorSummary> => {
    await sendMessageToParent(
      { type: 'REQUEST_ADVISOR_LATEST' },
      'Unable to request advisor summary from the parent window'
    );
    return createPendingRequest('advisorLatest', 'Advisor latest request');
  }, [createPendingRequest, sendMessageToParent]);

  const advisorIssuesLockRef = useRef<Promise<unknown>>(Promise.resolve());

  const requestAdvisorIssues = useCallback(
    (query: DashboardAdvisorIssuesQuery): Promise<DashboardAdvisorIssuesResponse> => {
      const next = advisorIssuesLockRef.current
        .catch(() => undefined)
        .then(async () => {
          await sendMessageToParent(
            {
              type: 'REQUEST_ADVISOR_ISSUES',
              severity: query.severity,
              category: query.category,
              limit: query.limit,
              offset: query.offset,
            },
            'Unable to request advisor issues from the parent window'
          );
          return createPendingRequest('advisorIssues', 'Advisor issues request');
        });
      advisorIssuesLockRef.current = next.catch(() => undefined);
      return next;
    },
    [createPendingRequest, sendMessageToParent]
  );

  const triggerAdvisorScan = useCallback(async (): Promise<void> => {
    await sendMessageToParent(
      { type: 'TRIGGER_ADVISOR_SCAN' },
      'Unable to trigger advisor scan from the parent window'
    );
    return createPendingRequest('advisorScan', 'Advisor scan trigger');
  }, [createPendingRequest, sendMessageToParent]);

  const showUpgradeDialog = useCallback(() => {
    void postMessageToParent({ type: 'SHOW_UPGRADE_DIALOG' });
  }, [postMessageToParent]);

  const openWhatsNew = useCallback(() => {
    void postMessageToParent({ type: 'OPEN_WHATS_NEW' });
  }, [postMessageToParent]);

  const reportRouteChange = useCallback(
    (path: string) => {
      void postMessageToParent({ type: 'APP_ROUTE_CHANGE', path });
    },
    [postMessageToParent]
  );

  const connectPosthog = useCallback(
    (projectId: string) => {
      void postMessageToParent({
        type: 'POSTHOG_CONNECT_REQUEST',
        projectId,
        timestamp: Date.now(),
      });
    },
    [postMessageToParent]
  );

  const connectApify = useCallback(
    (projectId: string) => {
      void postMessageToParent({
        type: 'APIFY_CONNECT_REQUEST',
        projectId,
        timestamp: Date.now(),
      });
    },
    [postMessageToParent]
  );

  // Deep-link "Open in PostHog". Cloud calls /integrations/posthog/v1/open and
  // posts the resolved URL back as POSTHOG_OPEN_RESPONSE. Self-contained one-off
  // listener (rather than the PendingRequest singleton machinery) so concurrent
  // clicks across multiple projects don't collide.
  const openPosthog = useCallback(
    (projectId: string): Promise<DashboardPosthogOpenResult> => {
      return new Promise((resolve) => {
        const requestId =
          typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
            ? crypto.randomUUID()
            : `${Date.now()}-${Math.random().toString(36).slice(2)}`;

        const cleanup = () => {
          window.clearTimeout(timeoutId);
          window.removeEventListener('message', handleResponse);
        };

        const handleResponse = (ev: MessageEvent<CloudHostingMessage>) => {
          if (ev.source !== getParentWindow()) {
            return;
          }
          const expectedOrigin = parentOriginRef.current;
          if (!expectedOrigin || ev.origin !== expectedOrigin) {
            return;
          }
          if (ev.data?.type !== 'POSTHOG_OPEN_RESPONSE' || ev.data.requestId !== requestId) {
            return;
          }
          cleanup();
          const url = typeof ev.data.url === 'string' ? ev.data.url : undefined;
          const error = typeof ev.data.error === 'string' ? ev.data.error : undefined;
          if (url) {
            resolve({ url });
          } else {
            resolve({ error: error ?? 'missing_url' });
          }
        };

        const timeoutId = window.setTimeout(() => {
          cleanup();
          resolve({ error: 'timeout' });
        }, DEFAULT_TIMEOUT_MS);

        window.addEventListener('message', handleResponse);
        // Don't wait the full timeout if delivery to the parent fails
        // (untrusted origin / no parent window / sync throw) — resolve right
        // away so the caller can close its placeholder tab.
        postMessageToParent({
          type: 'POSTHOG_OPEN_REQUEST',
          projectId,
          requestId,
        }).then(
          (delivered) => {
            if (!delivered) {
              cleanup();
              resolve({ error: 'no_parent_window' });
            }
          },
          (err) => {
            cleanup();
            resolve({ error: err instanceof Error ? err.message : 'post_failed' });
          }
        );
      });
    },
    [postMessageToParent]
  );

  const subscribePosthogConnectionStatus = useCallback(
    (cb: (event: DashboardPosthogConnectionStatus) => void) => {
      posthogStatusSubscribersRef.current.add(cb);
      return () => {
        posthogStatusSubscribersRef.current.delete(cb);
      };
    },
    []
  );

  const subscribeApifyConnectionStatus = useCallback(
    (cb: (event: DashboardApifyConnectionStatus) => void) => {
      apifyStatusSubscribersRef.current.add(cb);
      return () => {
        apifyStatusSubscribersRef.current.delete(cb);
      };
    },
    []
  );

  return {
    projectInfo,
    getAuthorizationCode,
    reportRouteChange,
    requestBackupInfo,
    createBackup,
    deleteBackup,
    renameBackup,
    restoreBackup,
    requestInstanceInfo,
    requestInstanceTypeChange,
    renameProject,
    deleteProject,
    updateVersion,
    showUpgradeDialog,
    openWhatsNew,
    requestUserInfo,
    requestUserApiKey,
    requestModelCredits,
    requestProjectMetrics,
    requestAdvisorLatest,
    requestAdvisorIssues,
    triggerAdvisorScan,
    connectPosthog,
    openPosthog,
    subscribePosthogConnectionStatus,
    connectApify,
    subscribeApifyConnectionStatus,
  };
}
