const PREVIEW_RUNTIME_QUERY_KEY = 'appPreviewMode';
const PREVIEW_RUNTIME_PARENT_ORIGIN_KEY = 'previewParentOrigin';
const PREVIEW_RUNTIME_TOKEN_QUERY_KEY = 'registeredAccessToken';
const PREVIEW_RUNTIME_DEVICE_MODE_QUERY_KEY = 'previewDeviceMode';
const PREVIEW_RUNTIME_CONSOLE_BRIDGE_EVENT = 'sm-home.preview-runtime.console';
const PREVIEW_TARGET_TYPE_QUERY_KEY = 'previewTargetType';
const PREVIEW_TARGET_COMPONENT_ID_QUERY_KEY = 'previewComponentId';
const PREVIEW_TARGET_SAMPLE_ID_QUERY_KEY = 'previewSampleId';
const PREVIEW_RUNTIME_CACHE_RESET_MARKER_KEY = 'work-app.preview-runtime.cache-reset.v1';
const PREVIEW_RUNTIME_CACHE_RESET_PARAM_KEY = '__previewRuntimeCacheReset';
const PREVIEW_RUNTIME_CLEANUP_TIMEOUT_MS = 2500;
const PREVIEW_RUNTIME_PRESERVED_QUERY_KEYS = [
  PREVIEW_RUNTIME_QUERY_KEY,
  PREVIEW_RUNTIME_PARENT_ORIGIN_KEY,
  PREVIEW_RUNTIME_TOKEN_QUERY_KEY,
  PREVIEW_RUNTIME_DEVICE_MODE_QUERY_KEY,
] as const;

export type PreviewTargetDescriptor =
  | {
      type: 'widget';
      componentId: string;
      sampleId?: string;
    }
  | null;

export type PreviewRuntimeConsoleLevel = 'log' | 'info' | 'warn' | 'error' | 'debug';

export type PreviewRuntimeConsoleBridgeMessage = {
  source: typeof PREVIEW_RUNTIME_CONSOLE_BRIDGE_EVENT;
  level: PreviewRuntimeConsoleLevel;
  args: string[];
  timestamp: string;
  href: string;
};

export function isPreviewRuntime() {
  if (typeof window === 'undefined') {
    return false;
  }

  return new URLSearchParams(window.location.search).get(PREVIEW_RUNTIME_QUERY_KEY) === '1';
}

export function isPreviewAppOrigin() {
  if (typeof window === 'undefined') {
    return false;
  }

  return window.location.origin === resolvePreviewAppOrigin();
}

function readPreviewRuntimeCacheResetMarker() {
  if (typeof window === 'undefined') {
    return '';
  }

  try {
    return window.sessionStorage.getItem(PREVIEW_RUNTIME_CACHE_RESET_MARKER_KEY)?.trim() ?? '';
  } catch {
    return '';
  }
}

function writePreviewRuntimeCacheResetMarker(value: string) {
  if (typeof window === 'undefined') {
    return;
  }

  try {
    if (value) {
      window.sessionStorage.setItem(PREVIEW_RUNTIME_CACHE_RESET_MARKER_KEY, value);
    } else {
      window.sessionStorage.removeItem(PREVIEW_RUNTIME_CACHE_RESET_MARKER_KEY);
    }
  } catch {
    // Ignore storage access failures in restricted runtimes.
  }
}

function buildPreviewRuntimeLocationKey() {
  if (typeof window === 'undefined') {
    return '';
  }

  return `${window.location.origin}${window.location.pathname}`;
}

function buildPreviewRuntimeCacheResetUrl() {
  const nextUrl = new URL(window.location.href);
  nextUrl.searchParams.set(PREVIEW_RUNTIME_CACHE_RESET_PARAM_KEY, `${Date.now()}`);
  return nextUrl.toString();
}

async function clearPreviewRuntimeServiceWorkersAndCaches() {
  if (typeof window === 'undefined') {
    return false;
  }

  let changed = false;

  if (typeof navigator !== 'undefined' && 'serviceWorker' in navigator) {
    try {
      const registrations = await navigator.serviceWorker.getRegistrations();
      if (registrations.length > 0) {
        changed = true;
      }

      await Promise.all(registrations.map((registration) => registration.unregister().catch(() => false)));
    } catch {
      // Ignore cleanup failure and continue.
    }
  }

  if ('caches' in window) {
    try {
      const cacheKeys = await caches.keys();
      if (cacheKeys.length > 0) {
        changed = true;
      }

      await Promise.all(cacheKeys.map((cacheKey) => caches.delete(cacheKey).catch(() => false)));
    } catch {
      // Ignore cache cleanup failure and continue.
    }
  }

  return changed;
}

async function withTimeout<T>(task: Promise<T>, timeoutMs: number, fallbackValue: T) {
  return await Promise.race([
    task,
    new Promise<T>((resolve) => {
      window.setTimeout(() => resolve(fallbackValue), timeoutMs);
    }),
  ]);
}

export async function ensurePreviewRuntimeFreshState() {
  if (typeof window === 'undefined' || !isPreviewRuntime()) {
    return;
  }

  const currentLocationKey = buildPreviewRuntimeLocationKey();
  const cleanedLocationKey = readPreviewRuntimeCacheResetMarker();
  const resetSearchParam = new URL(window.location.href).searchParams.get(PREVIEW_RUNTIME_CACHE_RESET_PARAM_KEY)?.trim() ?? '';

  if (cleanedLocationKey === currentLocationKey && !resetSearchParam) {
    return;
  }

  if (resetSearchParam) {
    const nextUrl = new URL(window.location.href);
    nextUrl.searchParams.delete(PREVIEW_RUNTIME_CACHE_RESET_PARAM_KEY);
    window.history.replaceState(window.history.state, '', nextUrl.pathname + nextUrl.search + nextUrl.hash);
    writePreviewRuntimeCacheResetMarker(currentLocationKey);
    return;
  }

  const changed = await withTimeout(clearPreviewRuntimeServiceWorkersAndCaches(), PREVIEW_RUNTIME_CLEANUP_TIMEOUT_MS, false);

  if (!changed) {
    writePreviewRuntimeCacheResetMarker(currentLocationKey);
    return;
  }

  writePreviewRuntimeCacheResetMarker(currentLocationKey);
  window.location.replace(buildPreviewRuntimeCacheResetUrl());
}

export function getPreviewRuntimeParentOrigin() {
  if (typeof window === 'undefined') {
    return '';
  }

  return new URLSearchParams(window.location.search).get(PREVIEW_RUNTIME_PARENT_ORIGIN_KEY)?.trim() ?? '';
}

function stringifyPreviewRuntimeConsoleArg(value: unknown, seen = new WeakSet<object>()): string {
  if (typeof value === 'string') {
    return value;
  }

  if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
    return String(value);
  }

  if (typeof value === 'undefined') {
    return 'undefined';
  }

  if (value === null) {
    return 'null';
  }

  if (typeof value === 'function') {
    return `[function ${value.name || 'anonymous'}]`;
  }

  if (value instanceof Error) {
    return value.stack || `${value.name}: ${value.message}`;
  }

  if (value instanceof URL) {
    return value.toString();
  }

  if (typeof value === 'object') {
    if (seen.has(value)) {
      return '[circular]';
    }

    seen.add(value);

    try {
      return JSON.stringify(
        value,
        (_key, nestedValue) => {
          if (typeof nestedValue === 'bigint') {
            return nestedValue.toString();
          }

          if (nestedValue instanceof Error) {
            return {
              name: nestedValue.name,
              message: nestedValue.message,
              stack: nestedValue.stack,
            };
          }

          if (nestedValue instanceof URL) {
            return nestedValue.toString();
          }

          if (typeof nestedValue === 'function') {
            return `[function ${nestedValue.name || 'anonymous'}]`;
          }

          if (nestedValue && typeof nestedValue === 'object') {
            if (seen.has(nestedValue)) {
              return '[circular]';
            }

            seen.add(nestedValue);
          }

          return nestedValue;
        },
        2,
      );
    } catch {
      return Object.prototype.toString.call(value);
    }
  }

  return String(value);
}

function postPreviewRuntimeConsoleMessage(level: PreviewRuntimeConsoleLevel, args: unknown[]) {
  if (typeof window === 'undefined') {
    return;
  }

  const parentOrigin = getPreviewRuntimeParentOrigin();

  if (!parentOrigin || window.parent === window) {
    return;
  }

  const payload: PreviewRuntimeConsoleBridgeMessage = {
    source: PREVIEW_RUNTIME_CONSOLE_BRIDGE_EVENT,
    level,
    args: args.map((arg) => stringifyPreviewRuntimeConsoleArg(arg)),
    timestamp: new Date().toISOString(),
    href: window.location.href,
  };

  window.parent.postMessage(payload, parentOrigin);
}

export function installPreviewRuntimeConsoleBridge() {
  if (typeof window === 'undefined' || !isPreviewRuntime()) {
    return;
  }

  const consoleMethods: PreviewRuntimeConsoleLevel[] = ['log', 'info', 'warn', 'error', 'debug'];
  const previewConsole = window.console as Console & {
    __smHomePreviewConsoleBridgeInstalled?: boolean;
  };

  if (previewConsole.__smHomePreviewConsoleBridgeInstalled) {
    return;
  }

  previewConsole.__smHomePreviewConsoleBridgeInstalled = true;

  consoleMethods.forEach((level) => {
    const originalMethod = previewConsole[level].bind(previewConsole);

    previewConsole[level] = (...args: unknown[]) => {
      postPreviewRuntimeConsoleMessage(level, args);
      originalMethod(...args);
    };
  });

  window.addEventListener('error', (event) => {
    postPreviewRuntimeConsoleMessage('error', [
      event.message || 'Unknown error',
      event.filename ? `${event.filename}:${event.lineno}:${event.colno}` : '',
      event.error ?? '',
    ]);
  });

  window.addEventListener('unhandledrejection', (event) => {
    postPreviewRuntimeConsoleMessage('error', ['Unhandled promise rejection', event.reason ?? '']);
  });

  postPreviewRuntimeConsoleMessage('info', ['Preview runtime console bridge connected']);
}

export function readPreviewRuntimeDeviceModeFromUrl(): 'desktop' | 'mobile' | null {
  if (typeof window === 'undefined') {
    return null;
  }

  const value = new URLSearchParams(window.location.search).get(PREVIEW_RUNTIME_DEVICE_MODE_QUERY_KEY)?.trim() ?? '';

  if (value === 'desktop' || value === 'mobile') {
    return value;
  }

  return null;
}

export function readPreviewRuntimeTokenFromUrl() {
  if (typeof window === 'undefined') {
    return '';
  }

  return new URLSearchParams(window.location.search).get(PREVIEW_RUNTIME_TOKEN_QUERY_KEY)?.trim() ?? '';
}

export function clearPreviewRuntimeTokenFromUrl() {
  if (typeof window === 'undefined') {
    return;
  }

  const url = new URL(window.location.href);

  if (!url.searchParams.has(PREVIEW_RUNTIME_TOKEN_QUERY_KEY)) {
    return;
  }

  url.searchParams.delete(PREVIEW_RUNTIME_TOKEN_QUERY_KEY);
  window.history.replaceState(window.history.state, '', `${url.pathname}${url.search}${url.hash}`);
}

export function resolvePreviewAppOrigin() {
  return 'https://preview.sm-home.cloud';
}

export function appendPreviewRuntimeSearch(path: string, sourceSearch = '') {
  if (!path) {
    return path;
  }

  const [rawPathname, rawHash = ''] = path.split('#', 2);
  const [pathname, rawSearch = ''] = rawPathname.split('?', 2);
  const nextSearchParams = new URLSearchParams(rawSearch);
  const sourceSearchParams = new URLSearchParams(sourceSearch.startsWith('?') ? sourceSearch.slice(1) : sourceSearch);

  PREVIEW_RUNTIME_PRESERVED_QUERY_KEYS.forEach((key) => {
    const value = sourceSearchParams.get(key)?.trim() ?? '';

    if (value) {
      nextSearchParams.set(key, value);
      return;
    }

    nextSearchParams.delete(key);
  });

  const nextSearch = nextSearchParams.toString();
  const nextHash = rawHash ? `#${rawHash}` : '';

  return `${pathname}${nextSearch ? `?${nextSearch}` : ''}${nextHash}`;
}

export function readPreviewTargetDescriptorFromUrl(): PreviewTargetDescriptor {
  if (typeof window === 'undefined') {
    return null;
  }

  const searchParams = new URLSearchParams(window.location.search);
  const targetType = searchParams.get(PREVIEW_TARGET_TYPE_QUERY_KEY)?.trim() ?? '';

  if (targetType !== 'widget') {
    return null;
  }

  const componentId = searchParams.get(PREVIEW_TARGET_COMPONENT_ID_QUERY_KEY)?.trim() ?? '';
  const sampleId = searchParams.get(PREVIEW_TARGET_SAMPLE_ID_QUERY_KEY)?.trim() ?? '';

  if (!componentId) {
    return null;
  }

  return {
    type: 'widget',
    componentId,
    sampleId: sampleId || undefined,
  };
}

export function buildPreviewRuntimeUrl(
  pathname: string,
  search = '',
  token = '',
  targetDescriptor: PreviewTargetDescriptor = null,
  deviceMode: 'desktop' | 'mobile' = 'desktop',
) {
  const targetUrl = new URL(pathname || '/', resolvePreviewAppOrigin());

  if (search) {
    const sourceParams = new URLSearchParams(search.startsWith('?') ? search.slice(1) : search);
    sourceParams.forEach((value, key) => {
      targetUrl.searchParams.set(key, value);
    });
  }

  targetUrl.searchParams.set(PREVIEW_RUNTIME_QUERY_KEY, '1');

  if (typeof window !== 'undefined') {
    targetUrl.searchParams.set(PREVIEW_RUNTIME_PARENT_ORIGIN_KEY, window.location.origin);
  }

  targetUrl.searchParams.set(PREVIEW_RUNTIME_DEVICE_MODE_QUERY_KEY, deviceMode);

  if (token.trim()) {
    targetUrl.searchParams.set(PREVIEW_RUNTIME_TOKEN_QUERY_KEY, token.trim());
  }

  if (targetDescriptor?.type === 'widget') {
    targetUrl.searchParams.set(PREVIEW_TARGET_TYPE_QUERY_KEY, 'widget');
    targetUrl.searchParams.set(PREVIEW_TARGET_COMPONENT_ID_QUERY_KEY, targetDescriptor.componentId);

    if (targetDescriptor.sampleId?.trim()) {
      targetUrl.searchParams.set(PREVIEW_TARGET_SAMPLE_ID_QUERY_KEY, targetDescriptor.sampleId.trim());
    } else {
      targetUrl.searchParams.delete(PREVIEW_TARGET_SAMPLE_ID_QUERY_KEY);
    }
  }

  return targetUrl.toString();
}
