"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var generator_elimination_exports = {};
__export(generator_elimination_exports, {
  Elimination: () => Elimination
});
module.exports = __toCommonJS(generator_elimination_exports);
var import_lib = require("../../lib");
class ElimNode {
  constructor(options) {
    this.children = null;
    this.user = options.user || null;
    this.state = options.state || "";
    this.result = options.result || "";
    this.score = options.score || null;
    this.losersBracketNode = options.losersBracketNode || null;
    this.losersBracketIndex = options.losersBracketIndex || 0;
    this.parent = options.parent || null;
    this.fromNode = options.fromNode || null;
  }
  setChildren(children) {
    if (this.children) {
      for (const child of this.children)
        child.parent = null;
    }
    if (children) {
      for (const child of children)
        child.parent = this;
    }
    this.children = children;
  }
  traverse(multiCallback) {
    const queue = [this];
    let node;
    while (node = queue.shift()) {
      multiCallback(node);
      if (node.children)
        queue.push(...node.children);
    }
  }
  find(multiCallback) {
    const queue = [this];
    let node;
    while (node = queue.shift()) {
      const value = multiCallback(node);
      if (value) {
        return value;
      }
      if (node.children)
        queue.push(...node.children);
    }
    return void 0;
  }
  // eslint-disable-next-line no-restricted-globals
  [Symbol.iterator]() {
    const results = [this];
    for (const result of results) {
      if (result.children)
        results.push(...result.children);
    }
    return results[Symbol.iterator]();
  }
  toJSON() {
    const node = {};
    if (!this.children) {
      node.team = this.user || (this.losersBracketIndex <= 1 ? `(loser's bracket)` : `(loser's bracket ${this.losersBracketIndex})`);
    } else {
      node.children = this.children.map((child) => child.toJSON());
      node.state = this.state || "unavailable";
      if (node.state === "finished") {
        node.team = this.user;
        node.result = this.result;
        node.score = this.score;
      }
    }
    return node;
  }
}
const nameMap = [
  "",
  "Single",
  "Double",
  "Triple",
  "Quadruple",
  "Quintuple",
  "Sextuple"
  // Feel free to add more
];
class Elimination {
  constructor(maxSubtrees) {
    this.name = "Elimination";
    this.isDrawingSupported = false;
    this.isBracketFrozen = false;
    this.players = [];
    maxSubtrees = maxSubtrees || 1;
    if (typeof maxSubtrees === "string" && maxSubtrees.toLowerCase() === "infinity") {
      maxSubtrees = Infinity;
    } else if (typeof maxSubtrees !== "number") {
      maxSubtrees = parseInt(maxSubtrees);
    }
    if (!maxSubtrees || maxSubtrees < 1)
      maxSubtrees = 1;
    this.maxSubtrees = maxSubtrees;
    this.treeRoot = null;
    if (nameMap[maxSubtrees]) {
      this.name = `${nameMap[maxSubtrees]} ${this.name}`;
    } else if (maxSubtrees === Infinity) {
      this.name = `N-${this.name}`;
    } else {
      this.name = `${maxSubtrees}-tuple ${this.name}`;
    }
  }
  getPendingBracketData(players) {
    return {
      type: "tree",
      rootNode: null
    };
  }
  getBracketData() {
    return {
      type: "tree",
      rootNode: this.treeRoot.toJSON()
    };
  }
  freezeBracket(players) {
    if (!players.length)
      throw new Error(`No players in tournament`);
    this.players = players;
    this.isBracketFrozen = true;
    let tree = null;
    for (const user of import_lib.Utils.shuffle(players)) {
      if (!tree) {
        tree = {
          root: new ElimNode({ user }),
          currentLayerLeafNodes: [],
          nextLayerLeafNodes: []
        };
        tree.currentLayerLeafNodes.push(tree.root);
        continue;
      }
      const targetNode = tree.currentLayerLeafNodes.shift();
      if (!targetNode)
        throw new Error(`TypeScript bug: no ! in checkJs`);
      const newLeftChild = new ElimNode({ user: targetNode.user });
      tree.nextLayerLeafNodes.push(newLeftChild);
      const newRightChild = new ElimNode({ user });
      tree.nextLayerLeafNodes.push(newRightChild);
      targetNode.setChildren([newLeftChild, newRightChild]);
      targetNode.user = null;
      if (tree.currentLayerLeafNodes.length === 0) {
        tree.currentLayerLeafNodes = tree.nextLayerLeafNodes;
        tree.nextLayerLeafNodes = [];
      }
    }
    this.maxSubtrees = Math.min(this.maxSubtrees, players.length - 1);
    for (let losersBracketIndex = 1; losersBracketIndex < this.maxSubtrees; losersBracketIndex++) {
      const matchesByDepth = {};
      const queue = [{ node: tree.root, depth: 0 }];
      let frame;
      while (frame = queue.shift()) {
        if (!frame.node.children || frame.node.losersBracketNode)
          continue;
        if (!matchesByDepth[frame.depth])
          matchesByDepth[frame.depth] = [];
        matchesByDepth[frame.depth].push(frame.node);
        queue.push({ node: frame.node.children[0], depth: frame.depth + 1 });
        queue.push({ node: frame.node.children[1], depth: frame.depth + 1 });
      }
      const newTree = {
        root: new ElimNode({ losersBracketIndex, fromNode: matchesByDepth[0][0] }),
        currentLayerLeafNodes: [],
        nextLayerLeafNodes: []
      };
      newTree.currentLayerLeafNodes.push(newTree.root);
      for (const depth in matchesByDepth) {
        if (depth === "0")
          continue;
        const matchesThisDepth = matchesByDepth[depth];
        let n = 0;
        for (; n < matchesThisDepth.length - 1; n += 2) {
          const oldLeaf = newTree.currentLayerLeafNodes.shift();
          if (!oldLeaf)
            throw new Error(`TypeScript bug: no ! in checkJs`);
          const oldLeafFromNode = oldLeaf.fromNode;
          oldLeaf.fromNode = null;
          const newBranch = new ElimNode({ losersBracketIndex });
          oldLeaf.setChildren([new ElimNode({ losersBracketIndex, fromNode: oldLeafFromNode }), newBranch]);
          const newLeftChild = new ElimNode({ losersBracketIndex, fromNode: matchesThisDepth[n] });
          newTree.nextLayerLeafNodes.push(newLeftChild);
          const newRightChild = new ElimNode({ losersBracketIndex, fromNode: matchesThisDepth[n + 1] });
          newTree.nextLayerLeafNodes.push(newRightChild);
          newBranch.setChildren([newLeftChild, newRightChild]);
        }
        if (n < matchesThisDepth.length) {
          const oldLeaf = newTree.currentLayerLeafNodes.shift();
          const oldLeafFromNode = oldLeaf.fromNode;
          oldLeaf.fromNode = null;
          const newLeaf = new ElimNode({ fromNode: matchesThisDepth[n] });
          newTree.nextLayerLeafNodes.push(newLeaf);
          oldLeaf.setChildren([new ElimNode({ fromNode: oldLeafFromNode }), newLeaf]);
        }
        newTree.currentLayerLeafNodes = newTree.nextLayerLeafNodes;
        newTree.nextLayerLeafNodes = [];
      }
      newTree.root.traverse((node) => {
        if (node.fromNode) {
          node.fromNode.losersBracketNode = node;
          node.fromNode = null;
        }
      });
      const newRoot = new ElimNode({});
      newRoot.setChildren([tree.root, newTree.root]);
      tree.root = newRoot;
    }
    tree.root.traverse((node) => {
      if (node.children && node.children[0].user && node.children[1].user) {
        node.state = "available";
      }
    });
    this.treeRoot = tree.root;
  }
  disqualifyUser(user) {
    if (!this.isBracketFrozen)
      return "BracketNotFrozen";
    const found = this.treeRoot.find((node) => {
      if (node.state === "available") {
        if (!node.children)
          throw new Error(`no children`);
        if (node.children[0].user === user) {
          return {
            match: [user, node.children[1].user],
            result: "loss",
            score: [0, 1]
          };
        } else if (node.children[1].user === user) {
          return {
            match: [node.children[0].user, user],
            result: "win",
            score: [1, 0]
          };
        }
      }
      return void 0;
    });
    if (found) {
      const error = this.setMatchResult(found.match, found.result, found.score);
      if (error) {
        throw new Error(`Unexpected ${error} from setMatchResult([${found.match.join(", ")}], ${found.result})`);
      }
    }
    user.unlinkUser();
  }
  getAvailableMatches() {
    if (!this.isBracketFrozen)
      return "BracketNotFrozen";
    const matches = [];
    this.treeRoot.traverse((node) => {
      if (node.state !== "available")
        return;
      const p1 = node.children[0].user;
      const p2 = node.children[1].user;
      if (!p1.isBusy && !p2.isBusy) {
        matches.push([p1, p2]);
      }
    });
    return matches;
  }
  setMatchResult([p1, p2], result, score) {
    if (!this.isBracketFrozen)
      return "BracketNotFrozen";
    if (!["win", "loss"].includes(result))
      return "InvalidMatchResult";
    if (!this.players.includes(p1) || !this.players.includes(p2))
      return "UserNotAdded";
    const targetNode = this.treeRoot.find((node) => {
      if (node.state === "available" && (node.children[0].user === p1 && node.children[1].user === p2)) {
        return node;
      }
      return void 0;
    });
    if (!targetNode)
      return "InvalidMatch";
    if (!targetNode.children)
      throw new Error(`invalid available state`);
    targetNode.state = "finished";
    targetNode.result = result;
    targetNode.score = score.slice();
    const winner = targetNode.children[result === "win" ? 0 : 1].user;
    const loser = targetNode.children[result === "loss" ? 0 : 1].user;
    targetNode.user = winner;
    if (!winner || !loser)
      throw new Error(`invalid available state`);
    if (loser.losses === this.maxSubtrees) {
      loser.isEliminated = true;
      loser.sendRoom(`|tournament|update|{"isJoined":false}`);
      loser.unlinkUser();
    }
    if (targetNode.parent) {
      const parent = targetNode.parent;
      if (loser.losses <= winner.losses && !loser.isDisqualified) {
        const newNode = new ElimNode({ state: "available", losersBracketNode: targetNode.losersBracketNode });
        newNode.setChildren([targetNode, new ElimNode({ user: loser })]);
        parent.setChildren([newNode, parent.children[1]]);
        return;
      }
      const userA = parent.children[0].user;
      const userB = parent.children[1].user;
      if (userA && userB) {
        parent.state = "available";
        let error = "";
        if (userA.isDisqualified) {
          error = this.setMatchResult([userA, userB], "loss", [0, 1]);
        } else if (userB.isDisqualified) {
          error = this.setMatchResult([userA, userB], "win", [1, 0]);
        }
        if (error) {
          throw new Error(`Unexpected ${error} from setMatchResult([${userA},${userB}], ...)`);
        }
      }
    } else if (loser.losses < this.maxSubtrees && !loser.isDisqualified) {
      const newRoot = new ElimNode({ state: "available" });
      newRoot.setChildren([targetNode, new ElimNode({ user: loser })]);
      this.treeRoot = newRoot;
    }
    if (targetNode.losersBracketNode) {
      targetNode.losersBracketNode.user = loser;
      const userA = targetNode.losersBracketNode.parent.children[0].user;
      const userB = targetNode.losersBracketNode.parent.children[1].user;
      if (userA && userB) {
        targetNode.losersBracketNode.parent.state = "available";
        let error = "";
        if (userA.isDisqualified) {
          error = this.setMatchResult([userA, userB], "loss", [0, 1]);
        } else if (userB.isDisqualified) {
          error = this.setMatchResult([userA, userB], "win", [1, 0]);
        }
        if (error) {
          throw new Error(`Unexpected ${error} from setMatchResult([${userA}, ${userB}], ...)`);
        }
      }
    }
  }
  isTournamentEnded() {
    return this.treeRoot.state === "finished";
  }
  getResults() {
    if (!this.isTournamentEnded())
      return "TournamentNotEnded";
    const results = [];
    let currentNode = this.treeRoot;
    for (let n = 0; n < this.maxSubtrees; ++n) {
      results.push([currentNode.user]);
      if (!currentNode.children)
        break;
      currentNode = currentNode.children[currentNode.result === "loss" ? 0 : 1];
      if (!currentNode)
        break;
    }
    if (this.players.length - 1 === this.maxSubtrees && currentNode) {
      results.push([currentNode.user]);
    }
    return results;
  }
}
//# sourceMappingURL=generator-elimination.js.map
