import { Embedded, GenericEmbedded } from './embedded';
import type { Value } from './values';

import { Annotated } from './annotated';
import { Bytes } from './bytes';
import { KeyedDictionary, KeyedSet } from './dictionary';
import { Writer, WriterOptions, EmbeddedWriter, WriterState } from './writer';
import { fromJS } from './fromjs';
import { Reader, ReaderOptions } from './reader';

export function parse<T = GenericEmbedded>(buffer: string, options?: ReaderOptions<T>): Value<T> {
    return new Reader<T>(buffer, options).next();
}

export function parseAll<T = GenericEmbedded>(buffer: string, options?: ReaderOptions<T>): Value<T>[] {
    return new Reader<T>(buffer, options).readToEnd();
}

export const stringifyEmbeddedWrite: EmbeddedWriter<any> = {
    write(s: WriterState, v: any): void {
        if (v instanceof GenericEmbedded) {
            new Writer(s, this).push(v.generic);
        } else {
            try {
                const j = fromJS(v);
                if (!(j instanceof Embedded)) {
                    new Writer(s, this).push(j);
                    return;
                }
            } catch {}
            try {
                s.pieces.push(JSON.stringify(v));
                return;
            } catch {}
            s.pieces.push('⌜' + v + '⌝');
        }
    }
};

export function stringify<T = GenericEmbedded>(x: any, options?: WriterOptions<T>): string {
    options = { ... (options ?? {}) };
    options.embeddedWrite = options.embeddedWrite ?? stringifyEmbeddedWrite;
    return Writer.stringify(fromJS<T>(x), options);
}

export function preserves<T>(pieces: TemplateStringsArray, ...values: Value<T>[]): string {
    const result = [pieces[0]];
    values.forEach((v, i) => {
        result.push(stringify(v));
        result.push(pieces[i + 1]);
    });
    return result.join('');
}

[Annotated, Bytes, KeyedDictionary, KeyedSet].forEach((C) => {
    C.prototype.toString = function () { return stringify(this); };
});
