/**
 * @file stateManager.js
 *
 * Permission is hereby granted to any person obtaining a copy of this software
 * and associated documentation files (the "Software"), to use it for personal
 * or non-commercial purposes, with the following restrictions:
 *
 * 1. **No Copying or Redistribution**: The Software or any of its parts may not
 *    be copied, merged, distributed, sublicensed, or sold without explicit
 *    prior written permission from the author.
 *
 * 2. **Commercial Use**: Any use of the Software for commercial purposes requires
 *    a valid license, obtainable only with the explicit consent of the author.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM,
 * OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * Ownership of this code remains solely with the original author. Unauthorized
 * use of this Software is strictly prohibited.
 *
 * @summary Class for managing state transitions and state descriptions.
 * @description Class for managing state transitions and state descriptions.
 * @module stateManager
 * @exports stateManager
 * @version 0.1.0
 * @since 0.1.0
 *
 * Author:
 * - Rene De Ren
 * Email:
 * - rene@thegoldenbasket.nl
 */

class stateManager {
  constructor(config, logger) {
    this.currentState = config.state.current;
    // Wall-clock entry timestamp into currentState. Used by
    // getRemainingTransitionS() so callers (e.g. MGC movement planner)
    // can compute exact remaining time for timed states without
    // approximating from the full configured duration.
    this.stateEnteredAt = Date.now();
    this.availableStates = config.state.available;
    this.descriptions = config.state.descriptions;
    this.logger = logger;
    this.transitionTimeleft = 0;
    this.transitionTimes = config.time;

    // Define valid transitions (can be extended dynamically if needed)
    this.validTransitions = config.state.allowedTransitions;

    //runtime tracking
    this.runTimeHours = 0; // cumulative runtime in hours
    this.runTimeStart = null; // timestamp when active state began

    //maintenance tracking
    this.maintenanceTimeStart = null; //timestamp when active state began
    this.maintenanceTimeHours = 0; //cumulative

    // Define active states (runtime counts only in these states)
    this.activeStates = config.state.activeStates;
  }

  getCurrentState() {
    return this.currentState;
  }

  // Seconds remaining in the current timed state (warmingup, coolingdown,
  // starting, stopping, …). Returns 0 for untimed states or once the
  // configured duration has elapsed. The MGC movement planner uses this to
  // compute exact rendezvous time for protected (non-interruptible) states.
  getRemainingTransitionS() {
    const d = this.transitionTimes?.[this.currentState] || 0;
    if (d <= 0) return 0;
    const elapsed = (Date.now() - this.stateEnteredAt) / 1000;
    return Math.max(0, d - elapsed);
  }

  transitionTo(newState,signal) {
    return new Promise((resolve, reject) => {
      if (signal && signal.aborted) {
        this.logger.debug("Transition aborted.");
        return reject("Transition aborted.");
      }

      if (!this.isValidTransition(newState)) {
        return reject(
          `Invalid transition from ${this.currentState} to ${newState}. Transition not executed.`
        ); //go back early and reject promise
      }

      //Time tracking based on active states
      this.handleRuntimeTracking(newState);
      this.handleMaintenancetimeTracking(newState);

      const transitionDuration = this.transitionTimes[this.currentState] || 0; // Default to 0 if no transition time
      this.logger.debug(
        `Transition from ${this.currentState} to ${newState} will take ${transitionDuration}s.`
      );

      if (transitionDuration > 0) {
        const timeoutId = setTimeout(() => {
          this.currentState = newState;
          this.stateEnteredAt = Date.now();
          resolve(`Transition from ${this.currentState} to ${newState} completed in ${transitionDuration}s.`);
        }, transitionDuration * 1000);
        if (signal) {
          signal.addEventListener('abort', () => {
            clearTimeout(timeoutId);
            reject(new Error('Transition aborted'));
          });
        }
      } else {
        this.currentState = newState;
        this.stateEnteredAt = Date.now();
        resolve(`Immediate transition to ${this.currentState} completed.`);
      }
    });
  }

  handleRuntimeTracking(newState) {
    //Handle runtime tracking based on active states
    const wasActive = this.activeStates.has(this.currentState);
    const willBeActive = this.activeStates.has(newState);
    if (wasActive && !willBeActive && this.runTimeStart) {
      // stop runtime timer and accumulate elapsed time
      const elapsed = (Date.now() - this.runTimeStart) / 3600000; // hours
      this.runTimeHours += elapsed;
      this.runTimeStart = null;
      this.logger.debug(
        `Runtime timer stopped; elapsed=${elapsed.toFixed(
          3
        )}h, total=${this.runTimeHours.toFixed(3)}h.`
      );
    } else if (!wasActive && willBeActive && !this.runTimeStart) {
      // starting new runtime
      this.runTimeStart = Date.now();
      this.logger.debug("Runtime timer started.");
    }
  }

  handleMaintenancetimeTracking(newState) {
    //is this maintenance time ?
    const wasActive = (this.currentState == "maintenance"? true:false);
    const willBeActive = ( newState == "maintenance" ? true:false );

    if (wasActive && this.maintenanceTimeStart) {
      // stop runtime timer and accumulate elapsed time
      const elapsed = (Date.now() - this.maintenanceTimeStart) / 3600000; // hours
      this.maintenanceTimeHours += elapsed;
      this.maintenanceTimeStart = null;
      this.logger.debug(
        `Maintenance timer stopped; elapsed=${elapsed.toFixed(
          3
        )}h, total=${this.maintenanceTimeHours.toFixed(3)}h.`
      );
    } else if (willBeActive && !this.runTimeStart) {
      // starting new runtime
      this.maintenanceTimeStart = Date.now();
      this.logger.debug("Runtime timer started.");
    }
  }

  isValidTransition(newState) {
    this.logger.debug(
      `Check 1 Transition valid ? From ${
        this.currentState
      } To ${newState} => ${this.validTransitions[this.currentState]?.has(
        newState
      )} `
    );
    this.logger.debug(
      `Check 2 Transition valid ? ${
        this.currentState
      } is not equal to ${newState} => ${this.currentState !== newState}`
    );
    // check if transition is valid and not the same as before
    const valid =
      this.validTransitions[this.currentState]?.has(newState) &&
      this.currentState !== newState;

    //if not valid
    if (!valid) {
      return false;
    } else {
      return true;
    }
  }

  getStateDescription(state = this.currentState) {
    return this.descriptions[state] || "No description available.";
  }

  getRunTimeHours() {
    // If currently active add the ongoing duration.
    let currentElapsed = 0;
    if (this.runTimeStart) {
      currentElapsed = (Date.now() - this.runTimeStart) / 3600000;
    }
    return this.runTimeHours + currentElapsed;
  }

  getMaintenanceTimeHours() {
    // If currently active add the ongoing duration.
    let currentElapsed = 0;
    if (this.maintenanceTimeStart) {
      currentElapsed = (Date.now() - this.maintenanceTimeStart) / 3600000;
    }
    return this.maintenanceTimeHours + currentElapsed;
  }
}

module.exports = stateManager;
