import { Encoder, canonicalEncode, canonicalString } from "./encoder";
import { Tag } from "./constants";
import { FlexMap, FlexSet, _iterMap } from "./flex";
import { Value } from "./values";
import { Bytes } from './bytes';
import { GenericEmbedded } from "./embedded";
import type { Preservable } from "./encoder";
import type { Writer, PreserveWritable } from "./writer";
import { annotations, Annotated } from "./annotated";

export type DictionaryType = 'Dictionary' | 'Set';
export const DictionaryType = Symbol.for('DictionaryType');

export class KeyedDictionary<K extends Value<T>, V, T = GenericEmbedded> extends FlexMap<K, V>
    implements Preservable<T>, PreserveWritable<T>
{
    get [DictionaryType](): DictionaryType {
        return 'Dictionary';
    }

    static isKeyedDictionary<K extends Value<T>, V, T = GenericEmbedded>(x: any): x is KeyedDictionary<K, V, T> {
        return x?.[DictionaryType] === 'Dictionary';
    }

    constructor(items?: readonly [K, V][]);
    constructor(items?: Iterable<readonly [K, V]>);
    constructor(items?: Iterable<readonly [K, V]>) {
        super(canonicalString, items);
    }

    mapEntries<W, S extends Value<R>, R = GenericEmbedded>(f: (entry: [K, V]) => [S, W]): KeyedDictionary<S, W, R> {
        const result = new KeyedDictionary<S, W, R>();
        for (let oldEntry of this.entries()) {
            const newEntry = f(oldEntry);
            result.set(newEntry[0], newEntry[1])
        }
        return result;
    }

    clone(): KeyedDictionary<K, V, T> {
        return new KeyedDictionary(this);
    }

    get [Symbol.toStringTag]() { return 'Dictionary'; }

    __preserve_on__(encoder: Encoder<T>) {
        if (encoder.canonical) {
            const entries = Array.from(this);
            const pieces = entries.map<[Bytes, number]>(([k, _v], i) => [canonicalEncode(k), i]);
            pieces.sort((a, b) => Bytes.compare(a[0], b[0]));
            encoder.state.emitbyte(Tag.Dictionary);
            pieces.forEach(([_encodedKey, i]) => {
                const [k, v] = entries[i];
                encoder.push(k);
                encoder.push(v as unknown as Value<T>); // Suuuuuuuper unsound
            });
            encoder.state.emitbyte(Tag.End);
        } else {
            encoder.state.emitbyte(Tag.Dictionary);
            this.forEach((v, k) => {
                encoder.push(k);
                encoder.push(v as unknown as Value<T>); // Suuuuuuuper unsound
            });
            encoder.state.emitbyte(Tag.End);
        }
    }

    __preserve_text_on__(w: Writer<T>) {
        w.state.writeSeq('{', '}', this.entries(), ([k, v]) => {
            w.push(k);
            if (Annotated.isAnnotated<T>(v) && (annotations(v).length > 1) && w.state.isIndenting) {
                w.state.pieces.push(':');
                w.state.indentCount++;
                w.state.writeIndent();
                w.push(v);
                w.state.indentCount--;
            } else {
                w.state.pieces.push(': ');
                w.push(v as unknown as Value<T>); // Suuuuuuuper unsound
            }
        });
    }
}

export class Dictionary<T = GenericEmbedded, V = Value<T>> extends KeyedDictionary<Value<T>, V, T> {
    static isDictionary<T = GenericEmbedded, V = Value<T>>(x: any): x is Dictionary<T, V> {
        return x?.[DictionaryType] === 'Dictionary';
    }

    static __from_preserve__<T>(v: Value<T>): undefined | Dictionary<T> {
        return Dictionary.isDictionary<T>(v) ? v : void 0;
    }
}

export class KeyedSet<K extends Value<T>, T = GenericEmbedded> extends FlexSet<K>
    implements Preservable<T>, PreserveWritable<T>
{
    get [DictionaryType](): DictionaryType {
        return 'Set';
    }

    static isKeyedSet<K extends Value<T>, T = GenericEmbedded>(x: any): x is KeyedSet<K, T> {
        return x?.[DictionaryType] === 'Set';
    }

    constructor(items?: Iterable<K>) {
        super(canonicalString, items);
    }

    map<S extends Value<R>, R = GenericEmbedded>(f: (value: K) => S): KeyedSet<S, R> {
        return new KeyedSet(_iterMap(this[Symbol.iterator](), f));
    }

    filter(f: (value: K) => boolean): KeyedSet<K, T> {
        const result = new KeyedSet<K, T>();
        for (let k of this) if (f(k)) result.add(k);
        return result;
    }

    clone(): KeyedSet<K, T> {
        return new KeyedSet(this);
    }

    get [Symbol.toStringTag]() { return 'Set'; }

    __preserve_on__(encoder: Encoder<T>) {
        if (encoder.canonical) {
            const pieces = Array.from(this).map<[Bytes, K]>(k => [canonicalEncode(k), k]);
            pieces.sort((a, b) => Bytes.compare(a[0], b[0]));
            encoder.encodevalues(Tag.Set, pieces.map(e => e[1]));
        } else {
            encoder.encodevalues(Tag.Set, this);
        }
    }

    __preserve_text_on__(w: Writer<T>) {
        w.state.writeSeq('#{', '}', this, vv => w.push(vv));
    }
}

export class Set<T = GenericEmbedded> extends KeyedSet<Value<T>, T> {
    static isSet<T = GenericEmbedded>(x: any): x is Set<T> {
        return x?.[DictionaryType] === 'Set';
    }

    static __from_preserve__<T>(v: Value<T>): undefined | Set<T> {
        return Set.isSet<T>(v) ? v : void 0;
    }
}
