/* Orchard. One file, no libraries. Delete anything you do not use. */

(function () {
  'use strict';

  // 1. Keep the footer year current without editing the HTML each January.
  var year = document.getElementById('year');
  if (year) { year.textContent = new Date().getFullYear(); }

  // 2. Smooth scroll for in-page links, unless the visitor prefers less motion.
  var calm = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  document.addEventListener('click', function (e) {
    var a = e.target.closest('a[href^="#"]');
    if (!a || a.getAttribute('href') === '#') { return; }
    var target = document.querySelector(a.getAttribute('href'));
    if (!target) { return; }
    e.preventDefault();
    target.scrollIntoView({ behavior: calm ? 'auto' : 'smooth', block: 'start' });

    // 3. Clicking a box button picks that box in the form, so the visitor
    //    does not have to choose twice. The data-box value must match the
    //    start of an <option> in the form.
    var wanted = a.getAttribute('data-box');
    var select = document.getElementById('f-box');
    if (!wanted || !select) { return; }
    for (var i = 0; i < select.options.length; i++) {
      if (select.options[i].text.indexOf(wanted) === 0) { select.selectedIndex = i; }
    }
  });

  // 4. Lift the bar off the page once it starts scrolling. At the very top
  //    it sits flush with a hairline; after that it gets a shadow so it reads
  //    as floating above the content rather than stuck to it.
  //    The same listener also decides when the back to top button appears,
  //    because both answer the one question: how far down the page are we?
  var bar = document.querySelector('.topbar');
  var toTop = document.getElementById('to-top');
  if (toTop) {
    toTop.classList.add('to-top-auto');
    // href="#main" is the fallback for a visitor with no JavaScript, and it
    // lands just under the sticky bar. With script running, go to the real
    // top instead. stopPropagation keeps the general smooth scroll handler
    // further up this file from also acting on the same click.
    toTop.addEventListener('click', function (e) {
      e.preventDefault();
      e.stopPropagation();
      window.scrollTo({ top: 0, behavior: calm ? 'auto' : 'smooth' });
    });
  }

  if (bar || toTop) {
    var lifted = false;
    var shown = false;
    var lift = function () {
      var y = window.scrollY;
      var stuck = y > 8;
      if (bar && stuck !== lifted) {
        lifted = stuck;
        bar.classList.toggle('is-stuck', stuck);
      }
      var far = y > 600;          // roughly one screen down. Change to taste.
      if (toTop && far !== shown) {
        shown = far;
        toTop.classList.toggle('is-on', far);
      }
    };
    window.addEventListener('scroll', lift, { passive: true });
    window.addEventListener('pageshow', lift);
    lift();                       // in case the page opens part way down
  }

  // 5. Parallax. The picture inside each [data-parallax] band drifts a little
  //    slower than the page. This is decoration, so it is added by script and
  //    switched off entirely for anyone who prefers less motion. Without it
  //    the band is just a photograph with words on it, which is fine.
  var bands = [].slice.call(document.querySelectorAll('[data-parallax]'));
  if (bands.length && !calm) {
    var DRIFT = 0.16;          // 0 is still, 0.3 is a lot. Change this to taste.
    var queued = 0;

    var place = function () {
      queued = 0;
      var mid = window.innerHeight / 2;
      for (var i = 0; i < bands.length; i++) {
        var pic = bands[i].querySelector('.parallax-img');
        if (!pic) { continue; }
        var box = bands[i].getBoundingClientRect();
        if (box.bottom < -200 || box.top > window.innerHeight + 200) { continue; }
        // The picture is 140% of the band's height, so there is 20% of slack
        // above and below. Never shift further than 18% of it, or the edge of
        // the photograph would slide into view.
        var limit = box.height * 0.18;
        var shift = (box.top + box.height / 2 - mid) * DRIFT;
        if (shift >  limit) { shift =  limit; }
        if (shift < -limit) { shift = -limit; }
        pic.style.transform = 'translate3d(0,' + shift.toFixed(1) + 'px,0)';
      }
    };

    // Cancel and re-request rather than holding a "busy" flag. A background
    // tab never runs requestAnimationFrame, and a plain flag would latch on
    // and leave the picture stuck for the rest of the visit.
    var onScroll = function () {
      if (queued) { window.cancelAnimationFrame(queued); }
      queued = window.requestAnimationFrame(place);
    };

    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', onScroll);
    document.addEventListener('visibilitychange', onScroll);
    window.addEventListener('pageshow', onScroll);
    place();
  }

  // 6. The gallery popup.
  //    Everything here is optional decoration: with JavaScript off the popup
  //    is never opened and the gallery stays a grid of pictures.
  var box = document.getElementById('lightbox');
  var shots = [].slice.call(document.querySelectorAll('.shot-open'));

  if (box && shots.length) {
    var lbImg   = box.querySelector('.lb-img');
    var lbCap   = box.querySelector('.lb-cap');
    var btnPrev = box.querySelector('.lb-prev');
    var btnNext = box.querySelector('.lb-next');
    var btnShut = box.querySelector('.lb-close');
    var at = 0;
    var cameFrom = null;

    function show(i) {
      at = (i + shots.length) % shots.length;
      var b = shots[at];
      var thumb = b.querySelector('img');
      // currentSrc is whatever the <picture> already chose and already
      // fetched, so the popup opens instantly and picks the right format
      // without this file having to guess what the browser supports.
      lbImg.src = thumb.currentSrc || b.getAttribute('data-full');
      lbImg.alt = thumb.getAttribute('alt') || '';
      lbCap.textContent = b.getAttribute('data-caption') || '';
    }

    function open(i) {
      cameFrom = shots[i];
      show(i);
      box.hidden = false;
      document.body.classList.add('lb-open');
      btnShut.focus();
    }

    function shut() {
      box.hidden = true;
      document.body.classList.remove('lb-open');
      lbImg.removeAttribute('src');
      // Put the visitor back where they were, not at the top of the page.
      if (cameFrom) { cameFrom.focus(); cameFrom = null; }
    }

    shots.forEach(function (b, i) {
      b.addEventListener('click', function () { open(i); });
    });

    btnShut.addEventListener('click', shut);
    btnPrev.addEventListener('click', function () { show(at - 1); });
    btnNext.addEventListener('click', function () { show(at + 1); });

    // Clicking the dark area closes it. Clicking the picture does not.
    box.addEventListener('click', function (e) {
      if (e.target === box || e.target.classList.contains('lb-frame')) { shut(); }
    });

    document.addEventListener('keydown', function (e) {
      if (box.hidden) { return; }
      if (e.key === 'Escape')     { shut(); }
      if (e.key === 'ArrowLeft')  { show(at - 1); }
      if (e.key === 'ArrowRight') { show(at + 1); }
      // Keep the tab ring inside the popup while it is open.
      if (e.key === 'Tab') {
        var stops = box.querySelectorAll('button');
        var first = stops[0], last = stops[stops.length - 1];
        if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
        else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
      }
    });

    // Swipe on a phone.
    var startX = null;
    box.addEventListener('touchstart', function (e) { startX = e.touches[0].clientX; }, { passive: true });
    box.addEventListener('touchend', function (e) {
      if (startX === null) { return; }
      var moved = e.changedTouches[0].clientX - startX;
      if (Math.abs(moved) > 50) { show(at + (moved < 0 ? 1 : -1)); }
      startX = null;
    }, { passive: true });
  }

  var form = document.getElementById('order-form');
  var note = document.getElementById('form-note');
  var send = document.getElementById('order-send');
  if (!form) { return; }

  // 7. Stamp the moment the page loaded. send.php rejects anything sent in
  //    under two and a half seconds, because a person cannot type that fast.
  var stamp = document.getElementById('f-ts');
  if (stamp) { stamp.value = String(Date.now()); }

  function say(message, ok) {
    if (!note) { return; }
    note.className = ok ? 'form-note is-ok' : 'form-note';
    note.textContent = message;
  }

  function markWrong(fields) {
    var first = null;
    Object.keys(fields || {}).forEach(function (name) {
      var input = form.querySelector('[name="' + name + '"]');
      if (!input) { return; }
      input.classList.add('is-wrong');
      if (!first) { first = input; }
    });
    if (first) { first.focus(); }
  }

  // 8. A light check before anything is sent. This is a courtesy for the
  //    visitor, not security. send.php checks everything again on the server,
  //    because anyone can skip what happens in a browser.
  function localProblems() {
    var missing = [];
    ['f-name', 'f-email', 'f-phone', 'f-address'].forEach(function (id) {
      var input = document.getElementById(id);
      if (!input) { return; }
      var empty = input.value.trim() === '';
      input.classList.toggle('is-wrong', empty);
      if (empty) { missing.push(input.previousElementSibling.textContent.trim()); }
    });

    var badEmail = false;
    var email = document.getElementById('f-email');
    if (email && email.value.trim() !== '' && email.value.indexOf('@') < 1) {
      email.classList.add('is-wrong');
      badEmail = true;
    }

    var says = [];
    if (missing.length) { says.push('Please fill in: ' + missing.join(', ') + '.'); }
    if (badEmail) { says.push('Please check the email address.'); }
    return says;
  }

  // 9. Send it without leaving the page. If anything at all goes wrong with
  //    that, the form is submitted the ordinary way instead, so the order
  //    still gets through.
  form.addEventListener('submit', function (e) {
    var problems = localProblems();
    if (problems.length) {
      e.preventDefault();
      say(problems.join(' '), false);
      var first = form.querySelector('.is-wrong');
      if (first) { first.focus(); }
      return;
    }

    if (!window.fetch || !window.FormData) { return; }   // very old browser, post normally
    e.preventDefault();

    if (send) { send.disabled = true; send.textContent = 'Sending...'; }
    say('Sending your order...', true);

    fetch(form.getAttribute('action'), {
      method: 'POST',
      body: new FormData(form),
      headers: { 'X-Requested-With': 'fetch', 'Accept': 'application/json' }
    })
      .then(function (res) {
        return res.text().then(function (text) {
          var data = null;
          try { data = JSON.parse(text); } catch (err) { data = null; }
          return { status: res.status, data: data };
        });
      })
      .then(function (res) {
        if (send) { send.disabled = false; send.textContent = 'Send my order'; }

        if (!res.data) {
          // Not JSON. Either PHP is not running here (a static preview) or
          // the server returned an error page. Fall back to a normal submit
          // so the visitor still gets somewhere.
          say('The order could not be sent from this page. Trying the ordinary way...', false);
          form.submit();
          return;
        }

        if (res.data.ok) {
          done(res.data.message);
          return;
        }

        say(res.data.message || 'Please check the form and try again.', false);
        markWrong(res.data.errors);
      })
      .catch(function () {
        if (send) { send.disabled = false; send.textContent = 'Send my order'; }
        say('The connection dropped before the order was sent. Please try again.', false);
      });
  });

  // 10. Replace the form with a thank-you, so nobody sends the same order twice.
  function done(message) {
    var box = document.createElement('div');
    box.className = 'form form-done';
    box.setAttribute('role', 'status');
    var h = document.createElement('h3');
    h.textContent = 'Thank you';
    var p = document.createElement('p');
    p.textContent = message || 'Your order is with us and we will reply by email.';
    box.appendChild(h);
    box.appendChild(p);
    form.parentNode.replaceChild(box, form);
    box.scrollIntoView({ behavior: calm ? 'auto' : 'smooth', block: 'center' });
  }

  // Clear a warning as soon as the visitor starts fixing it.
  form.addEventListener('input', function (e) {
    e.target.classList.remove('is-wrong');
    if (note) { note.textContent = ''; }
  });
})();
