$(document).ready(function () {
  // Skip loading any time we aren't on the translation page
  if (!/translator/.test(location.pathname.split("/").filter(Boolean)[1])) {
    return;
  }

  const $from = $(".translator-form #from");
  const $to = $(".translator-form #to");
  const $text = $(".translator-form #text");
  const $translation = $(".translator-form #translated_text");

  const StateValidators = {
    checkTextareaSubmittable: function () {
      var validationState = {
        action: "disabled",
        value: false,
        selector: ".submit-translate",
      };
      var text = $text.val();

      // If the input text field is blank, disable the submit button
      if (text === "") {
        return { ...validationState, ...{ value: true } };
      }
      // Otherwise, try to enable it
      return validationState;
    },
    checkTextareaClearable: function () {
      var validationState = {
        action: "disabled",
        value: false,
        selector: ".clear-button",
      };
      var text = $text.val();
      var translated_text = $translation.val();

      // If both text fields are blank, disable the clear button
      if (text === "" && translated_text === "") {
        return { ...validationState, ...{ value: true } };
      }
      // Otherwise, re-enable the clear button
      return validationState;
    },
    checkTargetLanguageSubmittable: function () {
      var validationState = {
        action: "disabled",
        value: false,
        selector: ".submit-translate",
      };
      var selectedValue = $("option:selected", $to).val();

      if (selectedValue == "auto") {
        return { ...validationState, ...{ value: true } };
      } else {
        return validationState;
      }
    },
  };

  const StateManagers = {
    handleClearTextarea: function (e) {
      $text.val("").change();
      $translation.val("").change();
      e.preventDefault();
    },
    handleSwitchLanguageFields: function (e) {
      // Gather the current state
      let from = $from.val(); // "auto"
      let to = $to.val(); // "es"
      let auto_lang = $from.attr("data-auto") || false;

      // Figure out what needs to change

      // Default state is nothing or swap.
      let targets = { to: from, from: to };

      // If a language was auto detected,
      // and our left side language is set
      // to auto detect
      if (!!auto_lang && from === "auto") {
        // the target language should become
        // the detected language
        targets.to = auto_lang;
      }

      // if a language was auto detected,
      // and the right side language before
      // the swap is the detected language
      if (!!auto_lang && to === auto_lang) {
        // the source language should become
        // auto detection
        targets.from = "auto";
      }

      // Make the changes
      $from.val(targets.from).change();
      $to.val(targets.to).change();

      // Swap the text fields
      let text = $text.val();
      let translation = $translation.val();

      $text.val(translation).change();
      $translation.val(text).change();

      e.preventDefault();
    },
    handleStateChange: function (validationState) {
      // Check if the set can coerce to true and is an object
      if (!!validationState !== true || typeof validationState !== "object") {
        return;
      }

      // Destructured assignment of fields we need
      ({ action, value, selector } = validationState);

      // Double check everything has a value
      if (
        typeof action === "undefined" ||
        typeof value === "undefined" ||
        typeof selector === "undefined"
      ) {
        return;
      }

      // Do the work
      $(selector).attr(action, value);

      return;
    },
    handleBulkStateChange: function (validationStateSet) {
      for (const [_, validationState] of Object.entries(validationStateSet)) {
        StateManagers.handleStateChange(validationState);
      }
    },
    handleFormSubmit: function (e) {
      // this should only be possible to do via keyboard
      // so no feedback should be okay, since the submit
      // buttons won't be visible
      if (StateQueries.isFormSubmittable === false) {
        e.preventDefault();
      }
    },
  };

  const StateQueries = {
    getValidationStates: function () {
      let result = {};
      for (const [_, method] of Object.entries(StateValidators)) {
        // Destructured assignment of fields we need
        ({ action, value, selector } = method());

        // Double check everything has a value
        if (
          typeof action === "undefined" ||
          typeof value === "undefined" ||
          typeof selector === "undefined"
        ) {
          continue;
        }

        // Either get the current value for the selector, or treat it as clean
        let state =
          typeof result[selector] === "undefined"
            ? true
            : result[selector].value;
        result[selector] = {
          action: action,
          value: !Boolean(!state & (true & !value)),
          selector: selector,
        };
      }
      return result;
    },
    isFormSubmittable: function () {
      return Boolean(
        Object.values(StateQueries.getValidationStates())
          .filter((state) => state.action === "disabled")
          .map((state) => !state.value)
          .reduce((state, value) => true & state & value, true) & true
      );
    },
  };

  const StateManagedValidators = {};
  for (const [validator, method] of Object.entries(StateValidators)) {
    StateManagedValidators[validator] = function () {
      state = method();
      StateManagers.handleStateChange(state);
    };
  }

  const State = {
    Validator: StateValidators,
    Manager: StateManagers,
    Query: StateQueries,
    Managed: {
      Validator: StateManagedValidators,
    },
  };

  $(".translator-form")
    // Clear `text` and `translated_text` when the clear button is clicked
    .on("click", ".clear-button", function (e) {
      StateManagers.handleClearTextarea(e);
    })
    // Disable the clear and/or submit buttons if text fields are blank
    .on("change keyup", "textarea", function (e) {
      StateManagedValidators.checkTextareaClearable(e);
    })
    .on("change keyup", "textarea", function (e) {
      StateManagedValidators.checkTextareaSubmittable(e);
    })
    // Swap the From and To languages and their text fields
    .on("click", ".switch-language-button", function (e) {
      StateManagers.handleSwitchLanguageFields(e);
    })
    // Disable the Submit buttons when Detect Language is selected as the To language
    .on("change", "#to", function (e) {
      StateManagedValidators.checkTargetLanguageSubmittable(e);
    })
    .on("submit", (e) => {
      StateManagers.handleFormSubmit(e);
    });
  // Trigger the initial check for the button states
  $to.trigger("change");
  $text.trigger("change");
  $translation.trigger("change");
  // General purpose vsync'd debouncer
  const debounced = function (callee, resolve_after_delay) {
    // `window`'s `setTimeout`, `requestAnimationFrame`
    // both return a `Number` type
    let job_frame = 0;
    let job_debouncer = 0;

    // return a function that does the timeout management
    // and calling. This is closed off so only it and its
    // child can manage the job_frame and job_debouncer
    // states.
    return () => {
      // scope args
      const args = arguments;
      // scope context
      const ctx = this;
      // clear any current debouncer
      clearTimeout(job_debouncer);
      // set a new debouncing timer for future execution
      job_debouncer = setTimeout(() => {
        // If a current job already exists, cancel it
        if (job_frame !== 0) {
          window.cancelAnimationFrame(job_frame);
        }
        // build the function to call
        let closure = () => {
          callee.apply(ctx, args);
        };
        // Request an animation frame do to work with
        // and call the function that was handed to us
        // during creation
        job_frame = window.requestAnimationFrame(closure);
      }, resolve_after_delay);
    };
  };

  // 32ms = 2 frames @ 60fps
  const job_wait_time = 32;
  // Write the --width property to :root on resize
  const scaler = function () {
    //set the "--width" variable property to the viewport width
    document.documentElement.style.setProperty("--width", window.innerWidth);
    // reset the global job handler
    job_frame = 0;
  };
  // delayed until there's at least two frames free

  // Bind to the resize handler with our job function
  $(window).bind("resize.downscale", debounced(scaler, job_wait_time));
});
