interface Entry<T> {
  signature: string;
  item: T;
}

export interface ShareCache<T> {
  byKey: Map<string, Entry<T>>;
  list: T[];
}

export function newShareCache<T>(): ShareCache<T> {
  return { byKey: new Map(), list: [] };
}

function signature(text: string): string {
  let a = 0x811c9dc5;
  let b = 0x9747b28c;
  for (let i = 0; i < text.length; i += 1) {
    const c = text.charCodeAt(i);
    a = Math.imul(a ^ c, 0x01000193);
    b = Math.imul(b + c, 0x85ebca6b) ^ (b >>> 13);
  }
  return `${text.length}.${(a >>> 0).toString(36)}.${(b >>> 0).toString(36)}`;
}

/**
 * Parses `raw` while reusing the objects from the previous call whose serialised form is unchanged.
 * Entries are matched by `identify` and compared by a hash of their JSON, so only the latest list is retained.
 * Returns the previous array itself when every element was reused and the length matches.
 */
export function parseShared<T>(
  cache: ShareCache<T>,
  raw: unknown,
  parse: (entry: unknown) => T | null,
  identify: (entry: unknown) => string | null = () => null
): T[] {
  if (!Array.isArray(raw)) {
    cache.byKey = new Map();
    cache.list = cache.list.length ? [] : cache.list;
    return cache.list;
  }
  const byKey = new Map<string, Entry<T>>();
  const out: T[] = [];
  for (const entry of raw) {
    const sig = signature(JSON.stringify(entry));
    const base = identify(entry) ?? `~${sig}`;
    let key = base;
    for (let n = 1; byKey.has(key); n += 1) key = `${base}#${n}`;
    const previous = cache.byKey.get(key);
    const item = previous && previous.signature === sig ? previous.item : parse(entry);
    if (item === null) continue;
    byKey.set(key, { signature: sig, item });
    out.push(item);
  }
  cache.byKey = byKey;
  if (out.length === cache.list.length && out.every((item, index) => item === cache.list[index])) return cache.list;
  cache.list = out;
  return out;
}

export function sameList<T>(a: readonly T[], b: readonly T[]): boolean {
  return a === b || (a.length === b.length && a.every((item, index) => item === b[index]));
}
