/**
 * BaseDomain — shared specificClass scaffolding.
 *
 * Consolidates the constructor boilerplate that every domain (pumpingStation,
 * measurement, MGC, rotatingMachine, …) repeats today: configManager →
 * configUtils → logger → MeasurementContainer → childRegistrationUtils →
 * ChildRouter. Subclasses declare `static name` (matches the JSON config in
 * generalFunctions/src/configs/<name>.json) and optionally `static unitPolicy`
 * (a UnitPolicy.declare(...) instance), then implement `configure()` to wire
 * concern-modules.
 *
 * See CONTRACTS.md §3.
 */

const EventEmitter = require('events');

const configManager = require('../configs/index.js');
const configUtils = require('../helper/configUtils.js');
const Logger = require('../helper/logger.js');
const childRegistrationUtils = require('../helper/childRegistrationUtils.js');
const { MeasurementContainer } = require('../measurements/index.js');
const ChildRouter = require('./ChildRouter.js');

class BaseDomain {
  constructor(userConfig = {}) {
    const ctor = this.constructor;
    if (ctor === BaseDomain) {
      throw new Error('BaseDomain is abstract; subclass it and declare static name');
    }

    this.emitter = new EventEmitter();

    this.configManager = new configManager();
    this.defaultConfig = this.configManager.getConfig(ctor.name);
    this.configUtils = new configUtils(this.defaultConfig);
    this.config = this.configUtils.initConfig(userConfig);

    const loggingCfg = this.config?.general?.logging || {};
    this.logger = new Logger(
      loggingCfg.enabled,
      loggingCfg.logLevel,
      this.config?.general?.name
    );

    // Read static unitPolicy via the constructor — `this.constructor`
    // resolves to the leaf subclass even when this base ctor is the caller.
    this.unitPolicy = ctor.unitPolicy ?? null;
    if (this.unitPolicy && typeof this.unitPolicy.setLogger === 'function') {
      this.unitPolicy.setLogger(this.logger);
    }

    const containerOptions = this.unitPolicy?.containerOptions
      ? this.unitPolicy.containerOptions()
      : { autoConvert: true };
    this.measurements = new MeasurementContainer(containerOptions, this.logger);
    if (this.config?.general?.id) this.measurements.setChildId(this.config.general.id);
    if (this.config?.general?.name) this.measurements.setChildName(this.config.general.name);

    this.childRegistrationUtils = new childRegistrationUtils(this);
    this.router = new ChildRouter(this);

    // childRegistrationUtils calls back into mainClass.registerChild after
    // storing the child. Routing through `this.router` keeps subclasses free
    // of register-switch boilerplate while preserving the existing handshake.
    this.registerChild = (child, softwareType) => {
      this.router.dispatchRegister(child, softwareType);
      return true;
    };

    if (typeof this.configure === 'function') this.configure();
    if (typeof this._init === 'function') this._init();
  }

  /**
   * Install a read-only getter that flattens `this.child[softwareType]`
   * (across all categories, or filtered by `category`) into a single
   * id-keyed object. Lets subclasses expose readable accessors like
   * `this.machines` while the registry remains the source of truth.
   */
  declareChildGetter(name, softwareType, category) {
    const key = String(softwareType || '').toLowerCase();
    Object.defineProperty(this, name, {
      configurable: true,
      enumerable: true,
      get: () => {
        const slice = this.child?.[key];
        if (!slice) return {};
        const cats = category ? [slice[category] || []] : Object.values(slice);
        const out = {};
        for (const list of cats) {
          if (!Array.isArray(list)) continue;
          for (const c of list) {
            const id = c?.config?.general?.id || c?.config?.general?.name;
            if (id != null) out[id] = c;
          }
        }
        return out;
      },
    });
  }

  /**
   * Frozen view passed to concern-modules so they don't reach into `this`.
   * Subclasses may override to add domain-specific keys.
   */
  context() {
    return Object.freeze({
      config: this.config,
      logger: this.logger,
      measurements: this.measurements,
      emitter: this.emitter,
      child: this.child,
      unitPolicy: this.unitPolicy,
      router: this.router,
    });
  }

  /** Default output shape — subclasses extend with concern-module snapshots. */
  getOutput() {
    return this.measurements.getFlattenedOutput?.() || {};
  }

  /** Subclasses MUST override. Grey placeholder so adapters never crash. */
  getStatusBadge() {
    return { fill: 'grey', shape: 'ring', text: 'no status' };
  }

  /** Convenience for event-driven nodes — see CONTRACTS.md §3. */
  notifyOutputChanged() {
    this.emitter.emit('output-changed');
  }

  close() {
    this.router?.tearDown();
    this.emitter.removeAllListeners();
  }
}

module.exports = BaseDomain;
