import { stringifyCollection } from '../stringify/stringifyCollection.mjs';
import { addPairToJSMap } from './addPairToJSMap.mjs';
import { Collection } from './Collection.mjs';
import { isScalar, isPair, MAP } from './Node.mjs';
import { Pair } from './Pair.mjs';
import { isScalarValue } from './Scalar.mjs';

function findPair(items, key) {
  const k = isScalar(key) ? key.value : key;
  for (const it of items) {
    if (isPair(it)) {
      if (it.key === key || it.key === k) return it;
      if (isScalar(it.key) && it.key.value === k) return it;
    }
  }
  return undefined;
}
class YAMLMap extends Collection {
  constructor(schema) {
    super(MAP, schema);
    this.items = [];
  }
  static get tagName() {
    return 'tag:yaml.org,2002:map';
  }
  /**
   * Adds a value to the collection.
   *
   * @param overwrite - If not set `true`, using a key that is already in the
   *   collection will throw. Otherwise, overwrites the previous value.
   */
  add(pair, overwrite) {
    let _pair;
    if (isPair(pair)) _pair = pair;else if (!pair || typeof pair !== 'object' || !('key' in pair)) {
      // In TypeScript, this never happens.
      _pair = new Pair(pair, pair?.value);
    } else _pair = new Pair(pair.key, pair.value);
    const prev = findPair(this.items, _pair.key);
    const sortEntries = this.schema?.sortMapEntries;
    if (prev) {
      if (!overwrite) throw new Error(`Key ${_pair.key} already set`);
      // For scalars, keep the old node & its comments and anchors
      if (isScalar(prev.value) && isScalarValue(_pair.value)) prev.value.value = _pair.value;else prev.value = _pair.value;
    } else if (sortEntries) {
      const i = this.items.findIndex(item => sortEntries(_pair, item) < 0);
      if (i === -1) this.items.push(_pair);else this.items.splice(i, 0, _pair);
    } else {
      this.items.push(_pair);
    }
  }
  delete(key) {
    const it = findPair(this.items, key);
    if (!it) return false;
    const del = this.items.splice(this.items.indexOf(it), 1);
    return del.length > 0;
  }
  get(key, keepScalar) {
    const it = findPair(this.items, key);
    const node = it?.value;
    return (!keepScalar && isScalar(node) ? node.value : node) ?? undefined;
  }
  has(key) {
    return !!findPair(this.items, key);
  }
  set(key, value) {
    this.add(new Pair(key, value), true);
  }
  /**
   * @param ctx - Conversion context, originally set in Document#toJS()
   * @param {Class} Type - If set, forces the returned collection type
   * @returns Instance of Type, Map, or Object
   */
  toJSON(_, ctx, Type) {
    const map = Type ? new Type() : ctx?.mapAsMap ? new Map() : {};
    if (ctx?.onCreate) ctx.onCreate(map);
    for (const item of this.items) addPairToJSMap(ctx, map, item);
    return map;
  }
  toString(ctx, onComment, onChompKeep) {
    if (!ctx) return JSON.stringify(this);
    for (const item of this.items) {
      if (!isPair(item)) throw new Error(`Map items must all be pairs; found ${JSON.stringify(item)} instead`);
    }
    if (!ctx.allNullValues && this.hasAllNullValues(false)) ctx = Object.assign({}, ctx, {
      allNullValues: true
    });
    return stringifyCollection(this, ctx, {
      blockItemPrefix: '',
      flowChars: {
        start: '{',
        end: '}'
      },
      itemIndent: ctx.indent || '',
      onChompKeep,
      onComment
    });
  }
}

export { YAMLMap, findPair };
