import type { EffectPatch, Primitive } from './types';

export function clone<T>(value: T): T {
  if (typeof structuredClone === 'function') {
    return structuredClone(value);
  }

  return JSON.parse(JSON.stringify(value)) as T;
}

export function clamp(value: number, min?: number, max?: number): number {
  if (min !== undefined && value < min) {
    return min;
  }

  if (max !== undefined && value > max) {
    return max;
  }

  return value;
}

export function uniqueStrings(values: string[] = []): string[] {
  return Array.from(new Set(values));
}

export function compareValue(value: Primitive, comparator: {
  eq?: Primitive;
  neq?: Primitive;
  gt?: number;
  gte?: number;
  lt?: number;
  lte?: number;
  oneOf?: Primitive[];
}): boolean {
  if (comparator.eq !== undefined && value !== comparator.eq) {
    return false;
  }

  if (comparator.neq !== undefined && value === comparator.neq) {
    return false;
  }

  if (comparator.oneOf && !comparator.oneOf.includes(value)) {
    return false;
  }

  if (typeof value === 'number') {
    if (comparator.gt !== undefined && !(value > comparator.gt)) {
      return false;
    }

    if (comparator.gte !== undefined && !(value >= comparator.gte)) {
      return false;
    }

    if (comparator.lt !== undefined && !(value < comparator.lt)) {
      return false;
    }

    if (comparator.lte !== undefined && !(value <= comparator.lte)) {
      return false;
    }
  }

  return true;
}

export function pickWeighted<T>(
  items: T[],
  getWeight: (item: T) => number,
  random: () => number,
): T | undefined {
  const total = items.reduce((sum, item) => sum + getWeight(item), 0);

  if (total <= 0) {
    return undefined;
  }

  let roll = random() * total;

  for (const item of items) {
    roll -= getWeight(item);
    if (roll < 0) {
      return item;
    }
  }

  return items.at(-1);
}

export function mergeEffects(...patches: Array<EffectPatch | undefined>): EffectPatch {
  const merged: EffectPatch = {};

  for (const patch of patches) {
    if (!patch) {
      continue;
    }

    if (patch.stats) {
      merged.stats ??= {};
      for (const [key, value] of Object.entries(patch.stats)) {
        merged.stats[key] = (merged.stats[key] ?? 0) + value;
      }
    }

    if (patch.resources) {
      merged.resources ??= {};
      for (const [key, value] of Object.entries(patch.resources)) {
        merged.resources[key] = (merged.resources[key] ?? 0) + value;
      }
    }

    if (patch.inventory) {
      merged.inventory ??= {};
      for (const [key, value] of Object.entries(patch.inventory)) {
        merged.inventory[key] = (merged.inventory[key] ?? 0) + value;
      }
    }

    if (patch.flags) {
      merged.flags = {
        ...(merged.flags ?? {}),
        ...patch.flags,
      };
    }

    if (patch.addTags) {
      merged.addTags = uniqueStrings([...(merged.addTags ?? []), ...patch.addTags]);
    }

    if (patch.removeTags) {
      merged.removeTags = uniqueStrings([...(merged.removeTags ?? []), ...patch.removeTags]);
    }
  }

  return merged;
}

export function createSeededRandom(seed: number): () => number {
  let state = seed >>> 0;

  return () => {
    state = (state * 1664525 + 1013904223) >>> 0;
    return state / 0x100000000;
  };
}
