// @ts-expect-error we don't have types for the preloader
import { p as preload } from '@qwik.dev/core/preloader';
import type { DeserializeContainer } from '../types';
import { TypeIds } from './constants';

/**
 * Preprocess the state data to:
 *
 * - Replace RootRef with the actual object
 * - Create a map for forward refs
 * - Create an array of indexes for initial QRLs
 *
 * Before:
 *
 * ```
 * 0 Object [
 *   String "foo"
 *   Object [
 *     String "shared"
 *     Number 1
 *   ]
 * ]
 * 1 Object [
 *   String "bar"
 *   RootRef 2
 * ]
 * 2 RootRef "0 1"
 * (59 chars)
 * ```
 *
 * After:
 *
 * ```
 * 0 Object [
 *   String "foo"
 *   RootRef 2
 * ]
 * 1 Object [
 *   String "bar"
 *   RootRef 2
 * ]
 * 2 Object [
 *   String "shared"
 *   Number 1
 * ]
 * (55 chars)
 * ```
 *
 * @param data - The state data to preprocess
 * @returns The preprocessed state data
 * @internal
 */

export function preprocessState(
  data: unknown[],
  container: DeserializeContainer,
  segmentId?: string,
  startIndex = 0
) {
  const iterator = preprocessStateIterator(data, container, startIndex);
  while (!iterator.next().done) {
    // Run synchronously for non-browser and non-container deserialization paths.
  }
}

export function* preprocessStateIterator(
  data: unknown[],
  container: DeserializeContainer,
  startIndex = 0
): Generator<void, void, void> {
  const isRootDeepRef = (type: TypeIds, value: unknown) => {
    return type === TypeIds.RootRef && typeof value === 'string' && value.indexOf(' ') !== -1;
  };

  const isForwardRefsMap = (type: TypeIds) => {
    return type === TypeIds.ForwardRefs;
  };

  const processRootRef = (index: number) => {
    const rootRefPath = (data[index + 1] as string).split(' ');
    const firstRefIndex = parseInt(rootRefPath[0], 10);
    let object: unknown[] | number | string = data;
    let objectType: TypeIds = TypeIds.RootRef;
    let typeIndex = 0;
    let valueIndex = 0;
    let parent: unknown[] | null = null;

    for (let i = 0; i < rootRefPath.length; i++) {
      parent = object as unknown[];

      typeIndex = (i === 0 ? firstRefIndex : parseInt(rootRefPath[i], 10)) * 2;
      valueIndex = typeIndex + 1;

      const objectArray = object as unknown[];
      objectType = objectArray[typeIndex] as TypeIds;
      object = objectArray[valueIndex] as unknown[];

      if (objectType === TypeIds.RootRef) {
        const rootRef =
          typeof object === 'string' ? parseInt(object, 10) : (object as unknown as number);
        const rootRefTypeIndex = rootRef * 2;
        objectType = data[rootRefTypeIndex] as TypeIds;
        object = data[rootRefTypeIndex + 1] as unknown[];
      }
    }

    if (parent) {
      parent[typeIndex] = TypeIds.RootRef;
      parent[valueIndex] = index / 2;
    }
    data[index] = objectType;
    data[index + 1] = object;
  };

  for (let i = startIndex; i < data.length; i += 2) {
    if (isRootDeepRef(data[i] as TypeIds, data[i + 1])) {
      processRootRef(i);
    } else if (isForwardRefsMap(data[i] as TypeIds)) {
      container.$forwardRefs$ = data[i + 1] as Array<number | string>;
    }
    yield;
  }
}
