import { assert } from "../../../../../core/assert.js";
import { array_push_if_unique } from "../../../../../core/collection/array/array_push_if_unique.js";
import { Codec } from "./Codec.js";

/**
 * Codec that tries to decode data with multiple codecs in order.
 *
 * @template T
 * @extends {Codec<T>}
 */
export class CodecWithFallback extends Codec {
    /**
     *
     * @type {Codec<T>[]}
     */
    children = []

    /**
     *
     * @param {Codec<T>} codecs
     */
    constructor(...codecs) {
        super();

        assert.isArray(codecs, 'codecs');

        for (const codec of codecs) {
            this.#add(codec);
        }

        if (this.children.length < 1) {
            throw new Error('At least one codec must be provided');
        }
    }

    /**
     *
     * @param {Codec<T>} codec
     */
    #add(codec) {
        assert.defined(codec, 'codec');
        assert.isObject(codec, 'codec');
        assert.isInstanceOf(codec, Codec, 'codec', 'Codec');

        return array_push_if_unique(this.children, codec);
    }

    async test(data) {
        const codecs = this.children;
        const codec_count = codecs.length;

        for (let i = 0; i < codec_count; i++) {
            const codec = codecs[i];

            const codec_supports_data = await codec.test(data);

            if (codec_supports_data) {
                return true;
            }
        }

        return false;
    }

    async decode(data) {

        const codecs = this.children;
        const codec_count = codecs.length;

        const errors = [];

        let result;

        for (let i = 0; i < codec_count; i++) {
            const codec = codecs[i];

            const codec_supports_data = await codec.test(data);

            if (!codec_supports_data) {

                errors.push({
                    index: i, error: "Codec doesn't support this data"
                });

                continue;
            }

            try {
                result = await codec.decode(data);
            } catch (e) {

                errors.push({
                    index: i, error: e
                });

                continue;
            }

            return result;
        }

        throw new Error(`All codecs failed, errors: ${errors.map(a => JSON.stringify(a)).join('\n')}`);

    }
}
