refactor(autorefresh): migrate to the new DI architecture

The presentation layer has been updated to use DI for the Autorefresh
feature.

Since it's the first feature using DI, this commit also refactors the
main and start entry points to set up the script runner using DI.
Compatibility with the old feature architecture (what we used to call
"new architecture") has been provided as well, so other features which
haven't migrated yet to use DI continue to work properly.

Bug: twpowertools:226
Change-Id: Icf97bebe761693571f3aa915a4935bc002e7c0ca
diff --git a/src/entryPoints/communityConsole/contentScripts/main.ts b/src/entryPoints/communityConsole/contentScripts/main.ts
new file mode 100644
index 0000000..ff70c11
--- /dev/null
+++ b/src/entryPoints/communityConsole/contentScripts/main.ts
@@ -0,0 +1,66 @@
+import DependenciesProviderSingleton, {
+  AutoRefreshDependency,
+} from '../../../common/architecture/dependenciesProvider/DependenciesProvider';
+import { Context } from '../../../common/architecture/entrypoint/Context';
+import {
+  ScriptEnvironment,
+  ScriptPage,
+  ScriptRunPhase,
+} from '../../../common/architecture/scripts/Script';
+import AutoRefreshThreadListHideHandler from '../../../features/autoRefresh/presentation/nodeWatcherHandlers/threadListHide.handler';
+import AutoRefreshThreadListSetUpHandler from '../../../features/autoRefresh/presentation/nodeWatcherHandlers/threadListSetUp.handler';
+import AutoRefreshStylesScript from '../../../features/autoRefresh/presentation/scripts/styles.script';
+import Features from '../../../features/Features';
+import { NodeWatcherAdapter } from '../../../infrastructure/presentation/nodeWatcher/NodeWatcher.adapter';
+import NodeWatcherScriptAdapter from '../../../infrastructure/presentation/scripts/NodeWatcherScript.adapter';
+import ScriptRunner from '../../../infrastructure/presentation/scripts/ScriptRunner';
+import ScriptSorterAdapter from '../../../infrastructure/presentation/scripts/ScriptSorter.adapter';
+import { SortedScriptsProviderAdapter } from '../../../infrastructure/presentation/scripts/SortedScriptsProvider.adapter';
+import { NodeWatcherHandler } from '../../../presentation/nodeWatcher/NodeWatcherHandler';
+import StandaloneScripts from '../../../scripts/Scripts';
+
+// Run legacy Javascript entry point
+import '../../../contentScripts/communityConsole/main';
+
+const scriptRunner = createScriptRunner();
+scriptRunner.run();
+
+function createScriptRunner() {
+  const dependenciesProvider = DependenciesProviderSingleton.getInstance();
+  const autoRefresh = dependenciesProvider.getDependency(AutoRefreshDependency);
+
+  const context: Context = {
+    page: ScriptPage.CommunityConsole,
+    environment: ScriptEnvironment.ContentScript,
+    runPhase: ScriptRunPhase.Main,
+  };
+
+  return new ScriptRunner(
+    new SortedScriptsProviderAdapter(
+      [
+        // Node watcher script with handlers
+        new NodeWatcherScriptAdapter(
+          new NodeWatcherAdapter(),
+          new Map<string, NodeWatcherHandler>([
+            [
+              'autoRefreshThreadListSetUp',
+              new AutoRefreshThreadListSetUpHandler(autoRefresh),
+            ],
+            [
+              'autoRefreshThreadListHide',
+              new AutoRefreshThreadListHideHandler(autoRefresh),
+            ],
+          ]),
+        ),
+
+        // Individual feature scripts
+        new AutoRefreshStylesScript(),
+
+        // Non-DI scripts (legacy, should be migrated to use a DI approach)
+        ...new Features().getScripts(context),
+        ...new StandaloneScripts().getScripts(context),
+      ],
+      new ScriptSorterAdapter(),
+    ).getScripts(),
+  );
+}
diff --git a/src/entryPoints/communityConsole/contentScripts/start.ts b/src/entryPoints/communityConsole/contentScripts/start.ts
new file mode 100644
index 0000000..5e11a87
--- /dev/null
+++ b/src/entryPoints/communityConsole/contentScripts/start.ts
@@ -0,0 +1,46 @@
+import DependenciesProviderSingleton, {
+  AutoRefreshDependency,
+} from '../../../common/architecture/dependenciesProvider/DependenciesProvider';
+import { Context } from '../../../common/architecture/entrypoint/Context';
+import {
+  ScriptEnvironment,
+  ScriptPage,
+  ScriptRunPhase,
+} from '../../../common/architecture/scripts/Script';
+import AutoRefreshSetUpScript from '../../../features/autoRefresh/presentation/scripts/setUp.script';
+import Features from '../../../features/Features';
+import ScriptRunner from '../../../infrastructure/presentation/scripts/ScriptRunner';
+import ScriptSorterAdapter from '../../../infrastructure/presentation/scripts/ScriptSorter.adapter';
+import { SortedScriptsProviderAdapter } from '../../../infrastructure/presentation/scripts/SortedScriptsProvider.adapter';
+import StandaloneScripts from '../../../scripts/Scripts';
+
+// Run legacy Javascript entry point
+import '../../../contentScripts/communityConsole/start';
+
+const scriptRunner = createScriptRunner();
+scriptRunner.run();
+
+function createScriptRunner() {
+  const dependenciesProvider = DependenciesProviderSingleton.getInstance();
+  const autoRefresh = dependenciesProvider.getDependency(AutoRefreshDependency);
+
+  const context: Context = {
+    page: ScriptPage.CommunityConsole,
+    environment: ScriptEnvironment.ContentScript,
+    runPhase: ScriptRunPhase.Start,
+  };
+
+  return new ScriptRunner(
+    new SortedScriptsProviderAdapter(
+      [
+        // Individual feature scripts
+        new AutoRefreshSetUpScript(autoRefresh),
+
+        // Non-DI scripts (legacy, should be migrated to use a DI approach)
+        ...new Features().getScripts(context),
+        ...new StandaloneScripts().getScripts(context),
+      ],
+      new ScriptSorterAdapter(),
+    ).getScripts(),
+  );
+}
diff --git a/src/features/Features.ts b/src/features/Features.ts
index afcba59..bc09eee 100644
--- a/src/features/Features.ts
+++ b/src/features/Features.ts
@@ -1,5 +1,4 @@
 import Feature from '../common/architecture/features/Feature';
-import AutoRefreshFeature from './autoRefresh/autoRefresh.feature';
 import InfiniteScrollFeature from './infiniteScroll/infiniteScroll.feature';
 import ScriptFilterListProvider from '../common/architecture/scripts/ScriptFilterListProvider';
 import ExtraInfoFeature from './extraInfo/extraInfo.feature';
@@ -12,7 +11,6 @@
 
 export default class Features extends ScriptFilterListProvider {
   private features: ConcreteFeatureClass[] = [
-    AutoRefreshFeature,
     CCDarkThemeFeature,
     ExtraInfoFeature,
     InfiniteScrollFeature,
diff --git a/src/features/autoRefresh/autoRefresh.feature.ts b/src/features/autoRefresh/autoRefresh.feature.ts
deleted file mode 100644
index 06ca388..0000000
--- a/src/features/autoRefresh/autoRefresh.feature.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import Feature from '../../common/architecture/features/Feature';
-import { ConcreteScript } from '../../common/architecture/scripts/Script';
-import { OptionCodename } from '../../common/options/optionsPrototype';
-import AutoRefreshNodeWatcherScript from './scripts/nodeWatcher.script';
-import AutoRefreshSetUpScript from './scripts/setUp.script';
-import AutoRefreshStylesScript from './scripts/styles.script';
-
-export default class AutoRefreshFeature extends Feature {
-  public readonly scripts: ConcreteScript[] = [
-    AutoRefreshNodeWatcherScript,
-    AutoRefreshSetUpScript,
-    AutoRefreshStylesScript,
-  ];
-
-  readonly codename = 'autoRefresh';
-  readonly relatedOptions: OptionCodename[] = ['autorefreshlist'];
-}
diff --git a/src/features/autoRefresh/core/autoRefresh.js b/src/features/autoRefresh/core/autoRefresh.js
index 03e21d1..a18c504 100644
--- a/src/features/autoRefresh/core/autoRefresh.js
+++ b/src/features/autoRefresh/core/autoRefresh.js
@@ -26,8 +26,6 @@
     this.snackbar = null;
     this.statusIndicator = null;
     this.interval = null;
-
-    this.setUpHandlers();
   }
 
   isOrderedByTimestampDescending() {
diff --git a/src/features/autoRefresh/nodeWatcherHandlers/threadListHide.handler.ts b/src/features/autoRefresh/nodeWatcherHandlers/threadListHide.handler.ts
deleted file mode 100644
index 9712c00..0000000
--- a/src/features/autoRefresh/nodeWatcherHandlers/threadListHide.handler.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import CssSelectorNodeWatcherScriptHandler from '../../../common/architecture/scripts/nodeWatcher/handlers/CssSelectorNodeWatcherScriptHandler';
-import { NodeMutationType } from '../../../presentation/nodeWatcher/NodeWatcherHandler';
-import { AutoRefreshNodeWatcherDependencies } from '../scripts/nodeWatcher.script';
-
-/**
- * Removes the snackbar when exiting thread list view.
- */
-export default class AutoRefreshThreadListHideHandler extends CssSelectorNodeWatcherScriptHandler<AutoRefreshNodeWatcherDependencies> {
-  cssSelector = 'ec-thread-list';
-
-  readonly mutationTypesProcessed: NodeMutationType[] = [
-    NodeMutationType.RemovedNode,
-  ];
-
-  onMutatedNode() {
-    this.options.autoRefresh.hideUpdatePrompt();
-  }
-}
diff --git a/src/features/autoRefresh/nodeWatcherHandlers/threadListSetUp.handler.ts b/src/features/autoRefresh/nodeWatcherHandlers/threadListSetUp.handler.ts
deleted file mode 100644
index 4806ed3..0000000
--- a/src/features/autoRefresh/nodeWatcherHandlers/threadListSetUp.handler.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import CssSelectorNodeWatcherScriptHandler from '../../../common/architecture/scripts/nodeWatcher/handlers/CssSelectorNodeWatcherScriptHandler';
-import { AutoRefreshNodeWatcherDependencies } from '../scripts/nodeWatcher.script';
-
-/**
- * Sets up the autorefresh list feature.
- */
-export default class AutoRefreshThreadListSetUpHandler extends CssSelectorNodeWatcherScriptHandler<AutoRefreshNodeWatcherDependencies> {
-  cssSelector = 'ec-thread-list';
-
-  onMutatedNode() {
-    this.options.autoRefresh.setUp();
-  }
-}
diff --git a/src/features/autoRefresh/presentation/nodeWatcherHandlers/threadListHide.handler.ts b/src/features/autoRefresh/presentation/nodeWatcherHandlers/threadListHide.handler.ts
new file mode 100644
index 0000000..1b53ba5
--- /dev/null
+++ b/src/features/autoRefresh/presentation/nodeWatcherHandlers/threadListHide.handler.ts
@@ -0,0 +1,22 @@
+import CssSelectorNodeWatcherHandler from '../../../../infrastructure/presentation/nodeWatcher/handlers/CssSelectorHandler.adapter';
+import { NodeMutationType } from '../../../../presentation/nodeWatcher/NodeWatcherHandler';
+import AutoRefresh from '../../core/autoRefresh';
+
+/**
+ * Removes the snackbar when exiting thread list view.
+ */
+export default class AutoRefreshThreadListHideHandler extends CssSelectorNodeWatcherHandler {
+  cssSelector = 'ec-thread-list';
+
+  readonly mutationTypesProcessed: NodeMutationType[] = [
+    NodeMutationType.RemovedNode,
+  ];
+
+  constructor(private autoRefresh: AutoRefresh) {
+    super();
+  }
+
+  onMutatedNode() {
+    this.autoRefresh.hideUpdatePrompt();
+  }
+}
diff --git a/src/features/autoRefresh/presentation/nodeWatcherHandlers/threadListSetUp.handler.ts b/src/features/autoRefresh/presentation/nodeWatcherHandlers/threadListSetUp.handler.ts
new file mode 100644
index 0000000..d89618d
--- /dev/null
+++ b/src/features/autoRefresh/presentation/nodeWatcherHandlers/threadListSetUp.handler.ts
@@ -0,0 +1,17 @@
+import CssSelectorNodeWatcherHandler from '../../../../infrastructure/presentation/nodeWatcher/handlers/CssSelectorHandler.adapter';
+import AutoRefresh from '../../core/autoRefresh';
+
+/**
+ * Sets up the autorefresh list feature.
+ */
+export default class AutoRefreshThreadListSetUpHandler extends CssSelectorNodeWatcherHandler {
+  cssSelector = 'ec-thread-list';
+
+  constructor(private autoRefresh: AutoRefresh) {
+    super();
+  }
+
+  onMutatedNode() {
+    this.autoRefresh.setUp();
+  }
+}
diff --git a/src/features/autoRefresh/presentation/scripts/setUp.script.ts b/src/features/autoRefresh/presentation/scripts/setUp.script.ts
new file mode 100644
index 0000000..6083e83
--- /dev/null
+++ b/src/features/autoRefresh/presentation/scripts/setUp.script.ts
@@ -0,0 +1,17 @@
+import Script from '../../../../common/architecture/scripts/Script';
+import AutoRefresh from '../../core/autoRefresh';
+
+export default class AutoRefreshSetUpScript extends Script {
+  priority = 100;
+  page: never;
+  environment: never;
+  runPhase: never;
+
+  constructor(private autoRefresh: AutoRefresh) {
+    super();
+  }
+
+  execute() {
+    this.autoRefresh.setUpHandlers();
+  }
+}
diff --git a/src/features/autoRefresh/presentation/scripts/styles.script.ts b/src/features/autoRefresh/presentation/scripts/styles.script.ts
new file mode 100644
index 0000000..ad1d28a
--- /dev/null
+++ b/src/features/autoRefresh/presentation/scripts/styles.script.ts
@@ -0,0 +1,12 @@
+import Script from '../../../../common/architecture/scripts/Script';
+import { injectStylesheet } from '../../../../common/contentScriptsUtils';
+
+export default class AutoRefreshStylesScript extends Script {
+  page: never;
+  environment: never;
+  runPhase: never;
+
+  execute() {
+    injectStylesheet(chrome.runtime.getURL('css/autorefresh_list.css'));
+  }
+}
diff --git a/src/features/autoRefresh/scripts/nodeWatcher.script.ts b/src/features/autoRefresh/scripts/nodeWatcher.script.ts
deleted file mode 100644
index be9c2d3..0000000
--- a/src/features/autoRefresh/scripts/nodeWatcher.script.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import DependenciesProviderSingleton, {
-  AutoRefreshDependency,
-} from '../../../common/architecture/dependenciesProvider/DependenciesProvider';
-import {
-  ScriptEnvironment,
-  ScriptPage,
-  ScriptRunPhase,
-} from '../../../common/architecture/scripts/Script';
-import LegacyNodeWatcherScript from '../../../common/architecture/scripts/nodeWatcher/LegacyNodeWatcherScript';
-import AutoRefresh from '../core/autoRefresh';
-import AutoRefreshThreadListHideHandler from '../nodeWatcherHandlers/threadListHide.handler';
-import AutoRefreshThreadListSetUpHandler from '../nodeWatcherHandlers/threadListSetUp.handler';
-
-export interface AutoRefreshNodeWatcherDependencies {
-  autoRefresh: AutoRefresh;
-}
-
-export default class AutoRefreshNodeWatcherScript extends LegacyNodeWatcherScript<AutoRefreshNodeWatcherDependencies> {
-  public page = ScriptPage.CommunityConsole;
-  public environment = ScriptEnvironment.ContentScript;
-  public runPhase = ScriptRunPhase.Main;
-  public handlers = new Map([
-    ['autoRefreshThreadListSetUp', AutoRefreshThreadListSetUpHandler],
-    ['autoRefreshThreadListHide', AutoRefreshThreadListHideHandler],
-  ]);
-
-  protected optionsFactory(): AutoRefreshNodeWatcherDependencies {
-    const dependenciesProvider = DependenciesProviderSingleton.getInstance();
-    return {
-      autoRefresh: dependenciesProvider.getDependency(AutoRefreshDependency),
-    };
-  }
-}
diff --git a/src/features/autoRefresh/scripts/setUp.script.ts b/src/features/autoRefresh/scripts/setUp.script.ts
deleted file mode 100644
index cb5cce6..0000000
--- a/src/features/autoRefresh/scripts/setUp.script.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import {
-  AutoRefreshDependency,
-  Dependency,
-} from '../../../common/architecture/dependenciesProvider/DependenciesProvider';
-import {
-  ScriptEnvironment,
-  ScriptPage,
-  ScriptRunPhase,
-} from '../../../common/architecture/scripts/Script';
-import SetUpDependenciesScript from '../../../common/architecture/scripts/setUpDependencies/SetUpDependenciesScript';
-
-export default class AutoRefreshSetUpScript extends SetUpDependenciesScript {
-  public priority = 100;
-  public page = ScriptPage.CommunityConsole;
-  public environment = ScriptEnvironment.ContentScript;
-  public runPhase = ScriptRunPhase.Start;
-  public dependencies: Dependency[] = [AutoRefreshDependency];
-}
diff --git a/src/features/autoRefresh/scripts/styles.script.ts b/src/features/autoRefresh/scripts/styles.script.ts
deleted file mode 100644
index 6e78169..0000000
--- a/src/features/autoRefresh/scripts/styles.script.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import Script, {
-  ScriptEnvironment,
-  ScriptPage,
-  ScriptRunPhase,
-} from '../../../common/architecture/scripts/Script';
-import { injectStylesheet } from '../../../common/contentScriptsUtils';
-
-export default class AutoRefreshStylesScript extends Script {
-  page = ScriptPage.CommunityConsole;
-  environment = ScriptEnvironment.ContentScript;
-  runPhase = ScriptRunPhase.Main;
-
-  execute() {
-    injectStylesheet(chrome.runtime.getURL('css/autorefresh_list.css'));
-  }
-}
diff --git a/src/infrastructure/presentation/nodeWatcher/handlers/CssSelectorHandler.adapter.ts b/src/infrastructure/presentation/nodeWatcher/handlers/CssSelectorHandler.adapter.ts
new file mode 100644
index 0000000..bf57020
--- /dev/null
+++ b/src/infrastructure/presentation/nodeWatcher/handlers/CssSelectorHandler.adapter.ts
@@ -0,0 +1,32 @@
+import {
+  NodeMutation,
+  NodeMutationType,
+  NodeWatcherHandler,
+} from '../../../../presentation/nodeWatcher/NodeWatcherHandler';
+
+export default abstract class CssSelectorNodeWatcherHandler
+  implements NodeWatcherHandler
+{
+  readonly mutationTypesProcessed: NodeMutationType[] = [
+    NodeMutationType.InitialDiscovery,
+    NodeMutationType.NewNode,
+  ];
+
+  abstract readonly cssSelector: string;
+
+  nodeFilter(nodeMutation: NodeMutation): boolean {
+    if (
+      !this.mutationTypesProcessed.includes(nodeMutation.type) ||
+      !(nodeMutation.node instanceof Element)
+    ) {
+      return false;
+    }
+    return nodeMutation.node.matches(this.cssSelector);
+  }
+
+  get initialDiscoverySelector() {
+    return this.cssSelector;
+  }
+
+  abstract onMutatedNode(nodeMutation: NodeMutation): void;
+}
diff --git a/src/platforms/communityConsole/entryPoints/main.ts b/src/platforms/communityConsole/entryPoints/main.ts
deleted file mode 100644
index 1f59e20..0000000
--- a/src/platforms/communityConsole/entryPoints/main.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import EntrypointScriptRunner from '../../../common/architecture/entrypoint/EntrypointScriptRunner';
-import {
-  ScriptEnvironment,
-  ScriptPage,
-  ScriptRunPhase,
-} from '../../../common/architecture/scripts/Script';
-
-// Run legacy Javascript entry point
-import '../../../contentScripts/communityConsole/main';
-
-const runner = new EntrypointScriptRunner({
-  page: ScriptPage.CommunityConsole,
-  environment: ScriptEnvironment.ContentScript,
-  runPhase: ScriptRunPhase.Main,
-});
-runner.run();
diff --git a/src/platforms/communityConsole/entryPoints/start.ts b/src/platforms/communityConsole/entryPoints/start.ts
deleted file mode 100644
index c1138a1..0000000
--- a/src/platforms/communityConsole/entryPoints/start.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import EntrypointScriptRunner from '../../../common/architecture/entrypoint/EntrypointScriptRunner';
-import {
-  ScriptEnvironment,
-  ScriptPage,
-  ScriptRunPhase,
-} from '../../../common/architecture/scripts/Script';
-
-// Run legacy Javascript entry point
-import '../../../contentScripts/communityConsole/start';
-
-const runner = new EntrypointScriptRunner({
-  page: ScriptPage.CommunityConsole,
-  environment: ScriptEnvironment.ContentScript,
-  runPhase: ScriptRunPhase.Start,
-});
-runner.run();