// Copyright 2017-2025 @pezkuwi/types-codec authors & contributors
// SPDX-License-Identifier: Apache-2.0

import type { Codec, CodecClass, Registry } from '../types/index.js';

import { u8aToHex } from '@pezkuwi/util';

const MAX_DEPTH = 1024;

/** @internal */
function isComplexType (Type: CodecClass): boolean {
  const typeName = Type.name?.toLowerCase() || '';

  return ['enum', 'hashmap', 'linkage', 'null', 'option', 'range', 'rangeinclusive', 'result', 'struct', 'tuple', 'vec', 'vecfixed'].includes(typeName);
}

/** @internal */
function formatFailure (registry: Registry, fn: 'decodeU8a' | 'decodeU8aStruct' | 'decodeU8aVec', _result: unknown[], { message }: Error, u8a: Uint8Array, i: number, count: number, Type: CodecClass, key?: string): string {
  let type = '';

  try {
    type = `: ${new Type(registry).toRawType()}`;
  } catch {
    // ignore
  }

  // This is extra debugging info (we most-probably want this in in some way, shape or form,
  // but at this point not quite sure how to include and format it (it can be quite massive)
  // console.error(JSON.stringify(result, null, 2));

  return `${fn}: failed at ${u8aToHex(u8a.subarray(0, 16))}…${key ? ` on ${key}` : ''} (index ${i + 1}/${count})${type}:: ${message}`;
}

/**
 * @internal
 *
 * Given an u8a, and an array of Type constructors, decode the u8a against the
 * types, and return an array of decoded values.
 *
 * @param u8a - The u8a to decode.
 * @param result - The result array (will be returned with values pushed)
 * @param types - The array of CodecClass to decode the U8a against.
 */
export function decodeU8a <T extends Codec = Codec> (registry: Registry, result: T[], u8a: Uint8Array, [Types, keys]: [CodecClass<T>[], string[]]): [T[], number] {
  const count = result.length;
  let offset = 0;
  let i = 0;

  try {
    while (i < count) {
      const value = new Types[i](registry, u8a.subarray(offset));

      offset += value.initialU8aLength || value.encodedLength;
      result[i] = value;
      i++;
    }
  } catch (error) {
    throw new Error(formatFailure(registry, 'decodeU8a', result, error as Error, u8a.subarray(offset), i, count, Types[i], keys[i]));
  }

  return [result, offset];
}

/**
 * @internal
 *
 * Split from decodeU8a since this is specialized to zip returns ... while we duplicate, this
 * is all on the hot-path, so it is not great, however there is (some) method behind the madness
 */
export function decodeU8aStruct (registry: Registry, result: [string, Codec][], u8a: Uint8Array, [Types, keys]: [CodecClass[], string[]]): [[string, Codec][], number] {
  const count = result.length;
  let offset = 0;
  let i = 0;

  if (count > MAX_DEPTH && isComplexType(Types[i])) {
    throw new Error(`decodeU8aStruct: Maximum depth exceeded, received ${count} elements, limit ${MAX_DEPTH}`);
  }

  try {
    while (i < count) {
      const value = new Types[i](registry, u8a.subarray(offset));

      offset += value.initialU8aLength || value.encodedLength;
      result[i] = [keys[i], value];
      i++;
    }
  } catch (error) {
    throw new Error(formatFailure(registry, 'decodeU8aStruct', result, error as Error, u8a.subarray(offset), i, count, Types[i], keys[i]));
  }

  return [result, offset];
}

/**
 * @internal
 *
 * Split from decodeU8a since this is specialized to 1 instance ... while we duplicate, this
 * is all on the hot-path, so it is not great, however there is (some) method behind the madness
 */
export function decodeU8aVec <T extends Codec = Codec> (registry: Registry, result: unknown[], u8a: Uint8Array, startAt: number, Type: CodecClass<T>): [number, number] {
  const count = result.length;

  if (count > MAX_DEPTH && isComplexType(Type)) {
    throw new Error(`decodeU8aVec: Maximum depth exceeded, received ${count} elements, limit ${MAX_DEPTH}`);
  }

  let offset = startAt;
  let i = 0;

  try {
    while (i < count) {
      const value = new Type(registry, u8a.subarray(offset));

      offset += value.initialU8aLength || value.encodedLength;
      result[i] = value;
      i++;
    }
  } catch (error) {
    throw new Error(formatFailure(registry, 'decodeU8aVec', result, error as Error, u8a.subarray(offset), i, count, Type));
  }

  return [offset, offset - startAt];
}
