1const npmlog = require('npmlog')
2
3module.exports = cls => class Tracker extends cls {
4  #progress = new Map()
5  #setProgress
6
7  constructor (options = {}) {
8    super(options)
9    this.#setProgress = !!options.progress
10  }
11
12  addTracker (section, subsection = null, key = null) {
13    if (section === null || section === undefined) {
14      this.#onError(`Tracker can't be null or undefined`)
15    }
16
17    if (key === null) {
18      key = subsection
19    }
20
21    const hasTracker = this.#progress.has(section)
22    const hasSubtracker = this.#progress.has(`${section}:${key}`)
23
24    if (hasTracker && subsection === null) {
25      // 0. existing tracker, no subsection
26      this.#onError(`Tracker "${section}" already exists`)
27    } else if (!hasTracker && subsection === null) {
28      // 1. no existing tracker, no subsection
29      // Create a new tracker from npmlog
30      // starts progress bar
31      if (this.#setProgress && this.#progress.size === 0) {
32        npmlog.enableProgress()
33      }
34
35      this.#progress.set(section, npmlog.newGroup(section))
36    } else if (!hasTracker && subsection !== null) {
37      // 2. no parent tracker and subsection
38      this.#onError(`Parent tracker "${section}" does not exist`)
39    } else if (!hasTracker || !hasSubtracker) {
40      // 3. existing parent tracker, no subsection tracker
41      // Create a new subtracker in this.#progress from parent tracker
42      this.#progress.set(`${section}:${key}`,
43        this.#progress.get(section).newGroup(`${section}:${subsection}`)
44      )
45    }
46    // 4. existing parent tracker, existing subsection tracker
47    // skip it
48  }
49
50  finishTracker (section, subsection = null, key = null) {
51    if (section === null || section === undefined) {
52      this.#onError(`Tracker can't be null or undefined`)
53    }
54
55    if (key === null) {
56      key = subsection
57    }
58
59    const hasTracker = this.#progress.has(section)
60    const hasSubtracker = this.#progress.has(`${section}:${key}`)
61
62    // 0. parent tracker exists, no subsection
63    // Finish parent tracker and remove from this.#progress
64    if (hasTracker && subsection === null) {
65      // check if parent tracker does
66      // not have any remaining children
67      const keys = this.#progress.keys()
68      for (const key of keys) {
69        if (key.match(new RegExp(section + ':'))) {
70          this.finishTracker(section, key)
71        }
72      }
73
74      // remove parent tracker
75      this.#progress.get(section).finish()
76      this.#progress.delete(section)
77
78      // remove progress bar if all
79      // trackers are finished
80      if (this.#setProgress && this.#progress.size === 0) {
81        npmlog.disableProgress()
82      }
83    } else if (!hasTracker && subsection === null) {
84      // 1. no existing parent tracker, no subsection
85      this.#onError(`Tracker "${section}" does not exist`)
86    } else if (!hasTracker || hasSubtracker) {
87      // 2. subtracker exists
88      // Finish subtracker and remove from this.#progress
89      this.#progress.get(`${section}:${key}`).finish()
90      this.#progress.delete(`${section}:${key}`)
91    }
92    // 3. existing parent tracker, no subsection
93  }
94
95  #onError (msg) {
96    if (this.#setProgress) {
97      npmlog.disableProgress()
98    }
99    throw new Error(msg)
100  }
101}
102