/* global React */

// OpeningsStore — single source of truth for the Opener pipeline's *live* state
// (which stage each lead is in, which graduated, and the autonomy mode). The
// seed data in data.jsx never changes; this overlay holds everything the user
// (or the Slack agent) does on top of it.
//
// Why it exists:
//   1. Before this, stage changes lived in OpeningsScreen's local React state —
//      lost on reload, and invisible to the home cockpit (which read the raw
//      seed stage, so the board and the "needs you" count disagreed).
//   2. It's the platform half of the 2-way Slack sync: the slack-bridge poller
//      writes Slack-originated transitions in here (applyFromSlack), and local
//      actions push out to the Slack process (recordLocal → :8124/opening-sync).
//
// Persisted to localStorage so it survives reload. Mirrors NudgeBackend's shape
// (subscribe + a React hook) so screens consume it the same way.

(function () {
  var LS_KEY = "nudge-openings-v1";
  var TRIGGER = "http://localhost:8124"; // the slack-app trigger server

  // Slack/agent op → the stage it lands the lead in. Keep in sync with the
  // agent's execution tools and the in-app lead-room actions.
  var OP_STAGE = {
    approve: "in-sequence",   // first beat sent
    book: "booked",           // meeting proposed/booked
    graduate: "booked",       // booked + graduated flag (handed to Deal Rhythm)
    standdown: "disqualified",
    reply: "booked",          // reply handled → toward booked
  };

  var state = {
    overrides: {},     // id → stage
    graduated: {},     // id → true
    slackTouched: {},  // id → true (last transition came from a Slack command)
    autonomy: "review",
    listeners: new Set(),
  };

  function load() {
    try {
      var raw = JSON.parse(localStorage.getItem(LS_KEY) || "{}");
      if (raw.overrides) state.overrides = raw.overrides;
      if (raw.graduated) state.graduated = raw.graduated;
      if (raw.slackTouched) state.slackTouched = raw.slackTouched;
      if (raw.autonomy) state.autonomy = raw.autonomy;
    } catch (_) {}
  }
  function save() {
    try {
      localStorage.setItem(LS_KEY, JSON.stringify({
        overrides: state.overrides, graduated: state.graduated,
        slackTouched: state.slackTouched, autonomy: state.autonomy,
      }));
    } catch (_) {}
  }
  function cameFromSlack(id) { return !!state.slackTouched[idOf(id)]; }
  function notify() {
    save();
    state.listeners.forEach(function (fn) { try { fn(); } catch (_) {} });
    try { window.dispatchEvent(new CustomEvent("nudge-openings-update")); } catch (_) {}
  }

  function seedStage(idOrOpening) {
    if (idOrOpening && typeof idOrOpening === "object") return idOrOpening.stage;
    var list = window.OPENINGS || [];
    var o = list.find(function (x) { return x.id === idOrOpening; });
    return o ? o.stage : null;
  }
  function idOf(idOrOpening) {
    return idOrOpening && typeof idOrOpening === "object" ? idOrOpening.id : idOrOpening;
  }

  // Effective stage = override (if any) → seed stage, then the autonomy gate:
  // when the human isn't reviewing, drafted leads flow straight into sequence.
  function effectiveStage(idOrOpening) {
    var id = idOf(idOrOpening);
    var s = state.overrides[id] || seedStage(idOrOpening);
    if (state.autonomy !== "review" && s === "needs-review") s = "in-sequence";
    return s;
  }
  function isGraduated(id) { return !!state.graduated[idOf(id)]; }
  function getAutonomy() { return state.autonomy; }
  function setAutonomy(mode) { state.autonomy = mode; notify(); }

  function findLead(id) {
    return (window.OPENINGS || []).find(function (x) { return x.id === id; }) || null;
  }
  function companyOf(id) { var l = findLead(id); return l ? l.company : id; }

  // Push a local (in-app) transition out to the Slack process so the agent's
  // view stays consistent and it can DM a receipt. Fire-and-forget; silent if
  // the slack-app isn't running.
  function pushToSlack(id, op) {
    try {
      fetch(TRIGGER + "/opening-sync?id=" + encodeURIComponent(id) +
            "&op=" + encodeURIComponent(op) + "&source=platform")
        .catch(function () {});
    } catch (_) {}
  }

  // A transition originating IN the platform (lead room button, etc.).
  // Updates state, persists, and mirrors to Slack.
  function recordLocal(id, op) {
    id = idOf(id);
    var stage = OP_STAGE[op];
    if (stage) state.overrides[id] = stage;
    if (op === "graduate") state.graduated[id] = true;
    delete state.slackTouched[id]; // platform now owns the latest move
    notify();
    pushToSlack(id, op);
  }

  // A transition arriving FROM Slack (written to events.json, read by the
  // bridge poller). Same state change, but do NOT echo back to Slack.
  function applyFromSlack(evt) {
    if (!evt || evt.kind !== "opening" || !evt.openingId) return false;
    var id = evt.openingId, op = evt.op;
    var stage = OP_STAGE[op];
    if (!stage) return false;
    state.overrides[id] = stage;
    state.slackTouched[id] = true;
    if (op === "graduate") {
      state.graduated[id] = true;
      var lead = findLead(id);
      if (lead && lead.graduatesTo && typeof window.graduateOpening === "function") {
        window.graduateOpening(lead.graduatesTo);
      }
    }
    notify();
    return true;
  }

  // On load, reconcile with the slack-app's authoritative state: adopt any
  // non-seed stage it reports that we don't already override locally. On a
  // fresh slack-app (state cleared) everything is seed, so nothing clobbers
  // our persisted overrides.
  function hydrate() {
    fetch(TRIGGER + "/openings?t=" + Date.now(), { cache: "no-store" })
      .then(function (r) { return r.ok ? r.json() : null; })
      .then(function (d) {
        if (!d || !Array.isArray(d.openings)) return;
        var changed = false;
        d.openings.forEach(function (o) {
          if (!o.id || state.overrides[o.id]) return;
          if (o.stage && o.stage !== seedStage(o.id)) { state.overrides[o.id] = o.stage; changed = true; }
          if (o.graduated && !state.graduated[o.id]) { state.graduated[o.id] = true; changed = true; }
        });
        if (changed) notify();
      })
      .catch(function () {});
  }

  function subscribe(fn) { state.listeners.add(fn); return function () { state.listeners.delete(fn); }; }

  // React hook — subscribe for the component's lifetime; returns a tick that
  // changes on every store mutation so the consumer re-renders.
  function useOpenings() {
    var pair = React.useState(0);
    var force = pair[1];
    React.useEffect(function () {
      return subscribe(function () { force(function (n) { return n + 1; }); });
    }, []);
    return window.NudgeOpenings;
  }

  load();
  // Hydrate shortly after load so OPENINGS + the trigger server have a moment.
  setTimeout(hydrate, 800);

  window.NudgeOpenings = {
    effectiveStage: effectiveStage,
    isGraduated: isGraduated,
    getAutonomy: getAutonomy,
    setAutonomy: setAutonomy,
    recordLocal: recordLocal,
    applyFromSlack: applyFromSlack,
    cameFromSlack: cameFromSlack,
    hydrate: hydrate,
    subscribe: subscribe,
    useOpenings: useOpenings,
    companyOf: companyOf,
    OP_STAGE: OP_STAGE,
  };
})();
