'use strict';
// Automatisch rijden — eenvoudige, robuuste blok-naar-blok motor.
//
// Een "rit" (run) = een loc + een geordende reeks stappen; elke stap verwijst naar een
// rijweg (die wissels zet + blokken reserveert) en een rijsnelheid + wachttijd.
// Per stap: reserveer de rijweg → rijd → wacht tot het doelblok bezet meldt (aankomst)
// → stop → wacht → volgende stap. De blokreservering voorkomt botsingen tussen ritten.
//
// Werkt identiek met de simulator (de motor "laat de trein aankomen" door de sensor te
// zetten) en met echte hardware (wacht op de echte s88/LDT-terugmelding).
//
// API:
//   const auto = new Auto(ctx);  ctx = { dcc, roster(), reservations, activateRoute,
//                                        releaseRoute, isSim }
//   auto.start(runId); auto.stop(addr); auto.tick(); auto.status();

class Auto {
  constructor(ctx) {
    this.ctx = ctx;
    this.active = {};   // addr -> runState
  }

  _run(id) { return (this.ctx.roster().runs || []).find((r) => String(r.id) === String(id)); }
  _dest(step) { const route = (this.ctx.roster().routes || []).find((r) => String(r.id) === String(step.route)); const bl = (route && route.blocks) || []; return bl[bl.length - 1]; }
  _routeOf(step) { return (this.ctx.roster().routes || []).find((r) => String(r.id) === String(step.route)); }

  start(runId) {
    const run = this._run(runId);
    if (!run) return { ok: false, reason: 'rit niet gevonden' };
    const addr = run.addr;
    if (this.active[addr]) return { ok: false, reason: 'loc rijdt al automatisch' };
    this.active[addr] = { runId: String(runId), addr, idx: 0, phase: 'reserve', since: this._now(), prevRoute: null, simArriveAt: 0 };
    this.ctx.log && this.ctx.log('🤖 auto-rit "' + (run.name || runId) + '" gestart voor loc ' + addr);
    return { ok: true };
  }
  stop(addr) {
    const st = this.active[addr]; if (!st) return { ok: false };
    this.ctx.dcc.throttle(addr, 0, 1);
    const run = this._run(st.runId);
    if (run) for (const step of run.steps || []) this.ctx.releaseRoute(step.route);
    delete this.active[addr];
    this.ctx.log && this.ctx.log('🤖 auto-rit gestopt voor loc ' + addr);
    return { ok: true };
  }
  stopAll() { for (const a of Object.keys(this.active)) this.stop(parseInt(a, 10)); }

  _now() { return Date.now(); }
  hasActive() { return Object.keys(this.active).length > 0; }
  // Rijd via de (vloeiende) ramp van de server als die er is, anders direct.
  _drive(addr, speed, dir) { (this.ctx.drive || this.ctx.dcc.throttle.bind(this.ctx.dcc))(parseInt(addr, 10), speed, dir); }
  // Welke ándere auto-loc houdt een van deze blokken vast? (voor deadlock-melding)
  _holderOf(blocks, exceptAddr) {
    const r = this.ctx.reservations || {};
    for (const b of (blocks || [])) { const h = r[b]; if (h && String(h) !== String(exceptAddr) && this.active[h]) return h; }
    return null;
  }

  // Periodiek aangeroepen (bv. elke 500ms) door de server.
  tick() {
    if (!this.ctx.dcc.state.power) return;            // zonder rijstroom niets doen
    const sensors = this.ctx.dcc.state.sensors || {};
    for (const addr of Object.keys(this.active)) {
      const st = this.active[addr];
      const run = this._run(st.runId); if (!run) { this.stop(parseInt(addr, 10)); continue; }
      const steps = run.steps || []; const step = steps[st.idx];
      if (!step) { // klaar
        if (run.loop) { st.idx = 0; st.phase = 'reserve'; st.since = this._now(); continue; }
        this.stop(parseInt(addr, 10)); continue;
      }
      const dest = this._dest(step);

      if (st.phase === 'reserve') {
        const r = this.ctx.activateRoute(step.route, parseInt(addr, 10));
        if (r.ok) {
          if (st.prevRoute && st.prevRoute !== step.route) this.ctx.releaseRoute(st.prevRoute);
          st.phase = 'drive'; st.since = this._now(); st.waitSince = 0; st.warned = false;
          this._drive(addr, step.speed || 50, 1);
          // simulator: plan aankomst (reistijd ~ afstand; hier vast 3.5s)
          st.simArriveAt = this._now() + (step.travelMs || 3500);
        } else {
          // wacht: rem vloeiend af tot stilstand tot het blok vrij is (sein rood)
          this._drive(addr, 0, 1);
          st.waitSince = st.waitSince || this._now();
          st.waitBusy = r.busy || [];
          // deadlock-detectie: wacht een trein lang op blok(ken) die een andere auto-loc vasthoudt?
          if (!st.warned && this._now() - st.waitSince > 15000) {
            st.warned = true;
            const other = this._holderOf(st.waitBusy, parseInt(addr, 10));
            const msg = other
              ? '⚠️ Mogelijke deadlock: loc ' + addr + ' wacht op blok ' + st.waitBusy.join(',') + ' (vastgehouden door loc ' + other + '). Stop handmatig een van beide.'
              : '⏳ Loc ' + addr + ' wacht al lang op blok ' + st.waitBusy.join(',') + '.';
            this.ctx.log && this.ctx.log(msg);
          }
        }
      } else if (st.phase === 'drive') {
        // simulator: laat de trein "aankomen" door het doelblok bezet te zetten
        if (this.ctx.isSim() && dest && this._now() >= st.simArriveAt) {
          // maak vorige blokken vrij in de sim, zet doel bezet
          const route = this._routeOf(step);
          for (const b of ((route && route.blocks) || [])) this.ctx.dcc.state.sensors[b] = (b === dest);
          this.ctx.dcc.emit && this.ctx.dcc.emit('state');
        }
        if (dest && sensors[dest]) { // aangekomen (echt of gesimuleerd)
          st.phase = 'arrived'; st.since = this._now();
        }
      } else if (st.phase === 'arrived') {
        const nextStep = steps[st.idx + 1] || (run.loop ? steps[0] : null);
        // vooruitkijken: doorgaand blok (wacht=0) → niet stoppen als de volgende route al vrij is
        if ((step.wait || 0) === 0 && nextStep && this.ctx.activateRoute(nextStep.route, parseInt(addr, 10)).ok) {
          if (st.prevRoute && st.prevRoute !== step.route) this.ctx.releaseRoute(st.prevRoute);
          st.prevRoute = step.route;
          st.idx = steps[st.idx + 1] ? st.idx + 1 : 0;
          st.phase = 'drive'; st.since = this._now(); st.simArriveAt = this._now() + (nextStep.travelMs || 3500);
          this._drive(addr, nextStep.speed || 50, 1);
        } else {
          this._drive(addr, 0, 1);    // vloeiend stoppen
          st.prevRoute = step.route;
          st.phase = 'dwell'; st.since = this._now();
        }
      } else if (st.phase === 'dwell') {
        if (this._now() - st.since >= (step.wait || 2) * 1000) {
          st.idx++; st.phase = 'reserve'; st.since = this._now();
        }
      }
    }
  }

  status() {
    const out = {};
    for (const addr of Object.keys(this.active)) {
      const st = this.active[addr]; const run = this._run(st.runId);
      out[addr] = { runId: st.runId, name: run ? run.name : '', step: st.idx + 1, steps: run ? (run.steps || []).length : 0, phase: st.phase };
    }
    return out;
  }
}

module.exports = { Auto };
