/* MPS-21 offer progressive enhancement.
 *
 * Capability-driven presentation: every offer form (the "Make an offer"
 * form on product pages, and the counter / accept / decline / withdraw
 * forms on the offer detail page) works as a plain POST + redirect with
 * no JS. When JS is available, this intercepts the submit, posts via
 * fetch() with X-Requested-With: XMLHttpRequest, and the server returns
 * JSON instead of a redirect. On AJAX success we navigate to the URL
 * the JSON points at (the offer detail page) — same destination the
 * no-JS redirect would reach — but without a full reload of the
 * originating page first. On AJAX error we surface the message inline.
 *
 * Targets any <form> with class "product-offer-form" (the make-offer
 * form) or "offer-action-form" (the offer-detail action forms).
 */
(function () {
  "use strict";

  function flash(form, text, kind) {
    var el = form.querySelector(".offer-js-flash");
    if (!el) {
      el = document.createElement("p");
      el.className = "offer-js-flash";
      form.appendChild(el);
    }
    el.textContent = text;
    el.className = "offer-js-flash offer-js-flash-" + (kind || "info");
  }

  function handleSubmit(e) {
    // An inline onsubmit="return confirm(...)" on terminal-action forms
    // runs before this listener. If the user cancelled, the event is
    // already defaultPrevented — bail so we don't fire the AJAX request.
    if (e.defaultPrevented) return;
    var form = e.currentTarget;
    e.preventDefault();
    var submitBtn = form.querySelector('[type="submit"]');
    if (submitBtn) submitBtn.disabled = true;

    fetch(form.action, {
      method: "POST",
      body: new FormData(form),
      credentials: "same-origin",
      headers: { "X-Requested-With": "XMLHttpRequest" },
    })
      .then(function (r) {
        return r.json().then(function (j) {
          return { status: r.status, body: j };
        });
      })
      .then(function (res) {
        if (res.status >= 200 && res.status < 300 && res.body && res.body.ok) {
          // Navigate to the offer detail page — the canonical place to
          // view/act on the offer next. (offer_open returns offer_id;
          // the action endpoints return the updated offer object.)
          var offerId =
            res.body.offer_id ||
            (res.body.offer && res.body.offer.id);
          if (offerId) {
            window.location.href = "/o/" + offerId;
            return;
          }
          // No id to redirect to — just reload so server-rendered state
          // (flash, updated offer page) shows.
          window.location.reload();
          return;
        }
        var msg = (res.body && res.body.error) || "Something went wrong.";
        flash(form, msg, "error");
        if (submitBtn) submitBtn.disabled = false;
      })
      .catch(function () {
        // Network error — fall back to a normal submit so the user still
        // gets the server's redirect/flash flow.
        if (submitBtn) submitBtn.disabled = false;
        form.submit();
      });
  }

  function wire() {
    var forms = document.querySelectorAll(
      "form.product-offer-form, form.offer-action-form"
    );
    Array.prototype.forEach.call(forms, function (form) {
      form.addEventListener("submit", handleSubmit);
    });
  }

  // Live state on the offer detail page via a bounded SSE feed. When the
  // offer's state changes (counter / accept / decline / expire / pay) the
  // whole page layout changes (which forms show, the notice, can_act), so
  // the correct response is a full reload. EventSource auto-reconnects
  // when the bounded stream closes.
  function watchOfferState() {
    var page = document.querySelector(".offer-page[data-offer-id]");
    if (!page || !window.EventSource) return;
    var offerId = page.getAttribute("data-offer-id");
    var lastState = parseInt(page.getAttribute("data-offer-state"), 10);
    var es = new EventSource("/o/" + offerId + "/events");
    es.onmessage = function (e) {
      var s;
      try {
        s = JSON.parse(e.data);
      } catch (err) {
        return;
      }
      if (typeof s.state !== "number") return;
      if (isNaN(lastState)) {
        lastState = s.state;
        return;
      }
      if (s.state !== lastState) {
        window.location.reload();
      }
    };
    es.addEventListener("gone", function () {
      es.close();
    });
  }

  /* Pay-by deadline countdown.
   *
   * Any element carrying [data-pay-deadline] (an absolute ms timestamp)
   * with a child .offer-pay-countdown-value will tick once a second
   * showing how long the buyer has left to pay. When the window
   * elapses the offer auto-expires server-side; we reflect that
   * locally as "expired — refresh to update."
   */
  /* fmtRemaining — prose human delta, matches ago.human() server-side.
   * The top two non-zero units make the cut so the line stays short
   * even on small phones (`in 1 day, 5 hours`, not `in 1d 5h 30m 2s`).
   */
  function fmtRemaining(ms) {
    if (ms <= 0) return "expired";
    var totalSeconds = Math.floor(ms / 1000);
    var units = [
      { name: "day", value: Math.floor(totalSeconds / 86400) },
      { name: "hour", value: Math.floor((totalSeconds % 86400) / 3600) },
      { name: "minute", value: Math.floor((totalSeconds % 3600) / 60) },
      { name: "second", value: totalSeconds % 60 },
    ];
    var parts = [];
    for (var i = 0; i < units.length && parts.length < 2; i++) {
      var u = units[i];
      if (u.value === 0 && parts.length === 0 && i < units.length - 1) continue;
      parts.push(u.value + " " + u.name + (u.value === 1 ? "" : "s"));
    }
    return "in " + parts.join(", ");
  }

  /* Tick each [data-pay-deadline] element directly — JS rewrites the
   * same <strong> the server pre-filled with ago.human(). The two
   * disagree by at most one second after each tick. */
  function tickPayCountdown() {
    var nodes = document.querySelectorAll("[data-pay-deadline]");
    if (!nodes.length) return;
    var now = Date.now();
    Array.prototype.forEach.call(nodes, function (node) {
      var raw = node.getAttribute("data-pay-deadline");
      var deadline = parseInt(raw, 10);
      if (isNaN(deadline)) return;
      node.textContent = fmtRemaining(deadline - now);
    });
  }

  function init() {
    wire();
    watchOfferState();
    tickPayCountdown();
    setInterval(tickPayCountdown, 1000);
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", init);
  } else {
    init();
  }
})();
