import { removeItemFromArrayInPlace } from "chootils/dist/arrays";
import { repondMeta as meta } from "../meta";
import { whenDoingEffectsRunAtStart, whenStartingEffects, whenStoppingEffects } from "../helpers/runWhens";
import { EffectDef, EffectPhase } from "../types";
import { forEach } from "chootils/dist/loops";

export function _addEffect(newEffectDef: EffectDef) {
  const phase: EffectPhase = newEffectDef.atStepEnd ? "endOfStep" : "duringStep";
  const step = newEffectDef.step ?? "default";
  const effectId = newEffectDef.id ?? toSafeEffectId();

  // TODO setupEffect the frist time each time if the cache stuff isn't there
  storeCachedValuesForEffect(newEffectDef);

  if (newEffectDef.runAtStart) {
    whenDoingEffectsRunAtStart(() => runEffectWithoutChange(newEffectDef));
  }

  whenStartingEffects(() => {
    meta.liveEffectsMap[effectId] = newEffectDef;
    const idsByStep = meta.effectIdsByPhaseByStepByPropId[phase];
    if (!idsByStep[step]) idsByStep[step] = {};
    forEach(newEffectDef.changes, (change) => {
      idsByStep[step][change] = idsByStep[step][change] || [];
      if (!idsByStep[step][change].includes(effectId)) idsByStep[step][change].push(effectId);
    });

    // idsByStep[step] = idsByStep[step] || [];
    // if (!idsByStep[step].includes(effectId)) idsByStep[step].push(effectId);
  });

  return effectId;
}

export function runEffectWithoutChangeForItems(effect: EffectDef) {
  let itemIdsToRun = getItemIdsForEffect(effect);
  forEach(itemIdsToRun, (itemId) => effect.run(itemId, meta.diffInfo as any, 16.66666, true /* ranWithoutChange */));
}

export function runEffectWithoutChange(effect: EffectDef) {
  if (effect.isPerItem) {
    runEffectWithoutChangeForItems(effect);
    return;
  }

  effect.run("", meta.diffInfo as any, 16.66666, true /* ranWithoutChange */);
}

// Return or cache item types for an effect
export function getItemTypesFromEffect(effect: EffectDef): string[] {
  let itemIdsToRun: string[] = [];

  // if (effect._itemTypes) return effect._itemTypes;
  if (effect._itemTypes?.length) return effect._itemTypes;

  const changes = effect.changes;

  if (changes.length === 1) {
    const itemType = meta.itemTypeByPropPathId[changes[0]];
    if (!itemType) {
      console.warn(`(one change) Effect ${effect.id} has no item types =-=-=-=-=-=-=-=-=-=-'`);
      console.log("effect", effect.changes[0]);
      console.log(JSON.stringify(meta.itemTypeByPropPathId, null, 2));
      return [];
    }
    // return undefined;
    return [itemType];
  }

  const itemTypeMap = {};
  forEach(changes, (change) => {
    const itemType = meta.itemTypeByPropPathId[change];
    if (itemType && !itemTypeMap[itemType]) {
      itemTypeMap[itemType] = true;
    }
  });
  const itemTypes = Object.keys(itemTypeMap);
  if (!itemTypes.length) {
    console.warn(`Effect ${effect.id} has no item types =-=-=-=-=-=-=-=-=-=-'`);
    console.warn(effect.changes);
    console.log("effect", effect);
  }

  return itemTypes;
}

export function getItemIdsForEffect(effect: EffectDef): string[] {
  if (effect.itemIds) return effect.itemIds;

  let itemIdsToRun: string[] = [];
  const itemTypes = getItemTypesFromEffect(effect);

  if (!effect._itemTypes) effect._itemTypes = itemTypes;

  const changes = effect.changes;

  if (itemTypes.length === 1) {
    const itemType = meta.itemTypeByPropPathId[changes[0]];
    return meta.itemIdsByItemType[itemType] ?? [];
  }

  itemTypes.forEach((itemType) => {
    const itemIds = meta.itemIdsByItemType[itemType];
    if (itemIds) itemIdsToRun.push(...itemIds);
  });
  return itemIdsToRun;
}

export function storeCachedValuesForEffect(effect: EffectDef) {
  if (effect.isPerItem === undefined) effect.isPerItem = true; // default to per item

  if (effect._allowedIdsMap && effect._itemTypes && effect._propsByItemType) return; // NOTE _allowedIdsMap can be undefined, so the check isn't that useful

  let allowedIdsMap: { [itemId: string]: boolean } | undefined = undefined;

  const itemTypes = getItemTypesFromEffect(effect);
  if (!itemTypes?.length) {
    console.log(`Effect ${effect.id} has no item types =-=-=-=-=-=-=-=-=-=-'`);
  }
  effect._itemTypes = itemTypes;
  const ids = effect.itemIds;

  if (ids) {
    allowedIdsMap = {};
    forEach(ids, (itemId) => (allowedIdsMap![itemId] = true));
  }
  effect._allowedIdsMap = allowedIdsMap;

  const propsByItemType = {};
  const checkAddedByItemType = {};
  const checkRemovedByItemType = {};
  forEach(effect.changes, (change) => {
    const itemType = meta.itemTypeByPropPathId[change];
    if (!propsByItemType[itemType]) propsByItemType[itemType] = [];
    const foundPropName = meta.propKeyByPropPathId[change];
    if (foundPropName) {
      propsByItemType[itemType].push(foundPropName);
    } else {
      const foundSpecialPropName = meta.specialKeyByPropPathId[change];
      if (foundSpecialPropName === "__added") checkAddedByItemType[itemType] = true;
      if (foundSpecialPropName === "__removed") checkRemovedByItemType[itemType] = true;
    }
  });
  effect._propsByItemType = propsByItemType;
  effect._checkAddedByItemType = checkAddedByItemType;
  effect._checkRemovedByItemType = checkRemovedByItemType;
}

export function _stopEffect(effectId: string) {
  whenStoppingEffects(() => {
    const effect = meta.liveEffectsMap[effectId];
    if (!effect) return;
    const phase: EffectPhase = !!effect.atStepEnd ? "endOfStep" : "duringStep";
    const step = effect.step ?? "default";

    forEach(effect.changes, (propId) => {
      removeItemFromArrayInPlace(meta.effectIdsByPhaseByStepByPropId[phase]?.[step]?.[propId] ?? [], effect.id);
    });

    delete meta.liveEffectsMap[effectId];
  });
}

export function toSafeEffectId(prefix?: string): string {
  const counterNumber = meta.autoEffectIdCounter;
  meta.autoEffectIdCounter += 1;
  return (prefix || "autoEffect") + "_" + counterNumber;
}
