1import { PluginState } from '../types';
2import { SelectableValue } from '../types/select';
3
4export interface RegistryItem {
5  id: string; // Unique Key -- saved in configs
6  name: string; // Display Name, can change without breaking configs
7  description?: string;
8  aliasIds?: string[]; // when the ID changes, we may want backwards compatibility ('current' => 'last')
9
10  /**
11   * Some extensions should not be user selectable
12   *  like: 'all' and 'any' matchers;
13   */
14  excludeFromPicker?: boolean;
15
16  /**
17   * Optional feature state
18   */
19  state?: PluginState;
20}
21
22export interface RegistryItemWithOptions<TOptions = any> extends RegistryItem {
23  /**
24   * Convert the options to a string
25   */
26  getOptionsDisplayText?: (options: TOptions) => string;
27
28  /**
29   * Default options used if nothing else is specified
30   */
31  defaultOptions?: TOptions;
32}
33
34interface RegistrySelectInfo {
35  options: Array<SelectableValue<string>>;
36  current: Array<SelectableValue<string>>;
37}
38
39export class Registry<T extends RegistryItem> {
40  private ordered: T[] = [];
41  private byId = new Map<string, T>();
42  private initialized = false;
43
44  constructor(private init?: () => T[]) {}
45
46  setInit = (init: () => T[]) => {
47    if (this.initialized) {
48      throw new Error('Registry already initialized');
49    }
50    this.init = init;
51  };
52
53  getIfExists(id: string | undefined): T | undefined {
54    if (!this.initialized) {
55      this.initialize();
56    }
57
58    if (id) {
59      return this.byId.get(id);
60    }
61
62    return undefined;
63  }
64
65  private initialize() {
66    if (this.init) {
67      for (const ext of this.init()) {
68        this.register(ext);
69      }
70    }
71    this.sort();
72    this.initialized = true;
73  }
74
75  get(id: string): T {
76    const v = this.getIfExists(id);
77    if (!v) {
78      throw new Error(`"${id}" not found in: ${this.list().map((v) => v.id)}`);
79    }
80    return v;
81  }
82
83  selectOptions(current?: string[], filter?: (ext: T) => boolean): RegistrySelectInfo {
84    if (!this.initialized) {
85      this.initialize();
86    }
87
88    const select = {
89      options: [],
90      current: [],
91    } as RegistrySelectInfo;
92
93    const currentOptions: Record<string, SelectableValue<string>> = {};
94    if (current) {
95      for (const id of current) {
96        currentOptions[id] = {};
97      }
98    }
99
100    for (const ext of this.ordered) {
101      if (ext.excludeFromPicker) {
102        continue;
103      }
104      if (filter && !filter(ext)) {
105        continue;
106      }
107
108      const option = {
109        value: ext.id,
110        label: ext.name,
111        description: ext.description,
112      };
113
114      if (ext.state === PluginState.alpha) {
115        option.label += ' (alpha)';
116      }
117
118      select.options.push(option);
119      if (currentOptions[ext.id]) {
120        currentOptions[ext.id] = option;
121      }
122    }
123
124    if (current) {
125      // this makes sure we preserve the order of ids
126      select.current = Object.values(currentOptions);
127    }
128
129    return select;
130  }
131
132  /**
133   * Return a list of values by ID, or all values if not specified
134   */
135  list(ids?: any[]): T[] {
136    if (!this.initialized) {
137      this.initialize();
138    }
139
140    if (ids) {
141      const found: T[] = [];
142      for (const id of ids) {
143        const v = this.getIfExists(id);
144        if (v) {
145          found.push(v);
146        }
147      }
148      return found;
149    }
150
151    return this.ordered;
152  }
153
154  isEmpty(): boolean {
155    if (!this.initialized) {
156      this.initialize();
157    }
158
159    return this.ordered.length === 0;
160  }
161
162  register(ext: T) {
163    if (this.byId.has(ext.id)) {
164      throw new Error('Duplicate Key:' + ext.id);
165    }
166
167    this.byId.set(ext.id, ext);
168    this.ordered.push(ext);
169
170    if (ext.aliasIds) {
171      for (const alias of ext.aliasIds) {
172        if (!this.byId.has(alias)) {
173          this.byId.set(alias, ext);
174        }
175      }
176    }
177
178    if (this.initialized) {
179      this.sort();
180    }
181  }
182
183  private sort() {
184    // TODO sort the list
185  }
186}
187