import { getStoreTarget, isStore } from '../../reactive-primitives/impl/store';
import { SubscriptionData } from '../../reactive-primitives/subscription-data';
import { NEEDS_COMPUTATION, STORE_ALL_PROPS } from '../../reactive-primitives/types';
import { untrack } from '../../use/use-core';
import { isTask } from '../../use/use-task';
import { isQwikComponent } from '../component.public';
import { isJSXNode } from '../jsx/jsx-node';
import { isPropsProxy } from '../jsx/props-proxy';
import { Slot } from '../jsx/slot.public';
import { isQrl } from '../qrl/qrl-utils';
import { _UNINITIALIZED } from '../utils/constants';
import { isPromise } from '../utils/promises';
import { isDomRef } from './serialization-context';
// Keep last
import { Fragment } from '../jsx/jsx-runtime';
import { isSerializerObj } from '../../reactive-primitives/utils';

const getKeyVal = <T>(value: T, key: keyof T) => value[key];

export const canSerialize = (value: unknown, seen: WeakSet<any> = new WeakSet()): boolean => {
  const hasTemporal = typeof Temporal !== 'undefined';
  if (
    value == null ||
    typeof value === 'string' ||
    typeof value === 'number' ||
    typeof value === 'boolean' ||
    typeof value === 'bigint'
  ) {
    return true;
  } else if (typeof value === 'object') {
    if (seen.has(value)) {
      return true;
    }
    seen.add(value);
    const proto = Object.getPrototypeOf(value);
    if (isStore(value)) {
      value = getStoreTarget(value);
    }
    if (proto == Object.prototype) {
      for (const key in value as object) {
        // if the value is a props proxy, then sometimes we could create a component-level subscription,
        // so we should call untrack here to avoid tracking the value
        if (!canSerialize(untrack(getKeyVal, value, key as keyof typeof value), seen)) {
          return false;
        }
      }
      return true;
    } else if (proto == Array.prototype) {
      for (let i = 0; i < (value as unknown[]).length; i++) {
        // ignore sparse array holes
        if (!(i in (value as unknown[]))) {
          return false;
        }
        if (!canSerialize((value as unknown[])[i], seen)) {
          return false;
        }
      }
      return true;
    } else if (isTask(value)) {
      return true;
    } else if (isPropsProxy(value)) {
      return true;
    } else if (isPromise(value)) {
      return true;
    } else if (isJSXNode(value)) {
      return true;
    } else if (isSerializerObj(value)) {
      return true;
    } else if (value instanceof Error) {
      return true;
    } else if (value instanceof URL) {
      return true;
    } else if (value instanceof Date) {
      return true;
    } else if (hasTemporal && value instanceof Temporal.Duration) {
      return true;
    } else if (hasTemporal && value instanceof Temporal.Instant) {
      return true;
    } else if (hasTemporal && value instanceof Temporal.PlainDate) {
      return true;
    } else if (hasTemporal && value instanceof Temporal.PlainDateTime) {
      return true;
    } else if (hasTemporal && value instanceof Temporal.PlainMonthDay) {
      return true;
    } else if (hasTemporal && value instanceof Temporal.PlainTime) {
      return true;
    } else if (hasTemporal && value instanceof Temporal.PlainYearMonth) {
      return true;
    } else if (hasTemporal && value instanceof Temporal.ZonedDateTime) {
      return true;
    } else if (value instanceof RegExp) {
      return true;
    } else if (value instanceof URLSearchParams) {
      return true;
    } else if (value instanceof FormData) {
      return true;
    } else if (value instanceof Set) {
      return true;
    } else if (value instanceof Map) {
      return true;
    } else if (value instanceof Uint8Array) {
      return true;
    } else if (value instanceof SubscriptionData) {
      return true;
    } else if (isDomRef?.(value)) {
      return true;
    }
  } else if (typeof value === 'function') {
    if (isQrl(value) || isQwikComponent(value) || value === Slot || value === Fragment) {
      return true;
    }
  } else if (value === _UNINITIALIZED || value === NEEDS_COMPUTATION || value === STORE_ALL_PROPS) {
    return true;
  }
  return false;
};
