(function ($) {
  $.fn.ContentBlockEditor = function (options) {
    let settings = $.extend({}, options);

    const delete_active_class = "fa-trash";
    const delete_disabled_class = "fa-trash-slash";

    $(document).ajaxError(function (e, j, s, thrownError) {
      showStatus("Something went wrong. Please reload the page and try again.");
      console.log(thrownError);
    });

    // Initialise dragon drops.
    $(".content-blocks").each(function () {
      dragonDrop($(this));
    });

    // Import button and select
    let $cb_import_select = $("#id_master");
    let $cb_import_label = $("#cb_import_label");
    updateButtonLabel($cb_import_select, $cb_import_label);

    $(document).on("change", "#id_master", function () {
      updateButtonLabel($cb_import_select, $cb_import_label);
    });

    $(document).on("submit", ".import-form", function () {
      let $form = $(this);
      let $target = $("#content-blocks-root");
      let $loader = $($(this).data("loader"));

      showLoader($loader);

      let title = $form.find("#cb_import_label").html();
      showStatus("Importing content blocks from " + title);
      $form.ajaxSubmit({
        success: function (data) {
          renderAjaxResponse(data, $target, true);
          hideLoader($loader);
          showStatus("Content blocks imported from " + title);
        },
      });

      return false;
    });

    // Create button and select
    let $cb_template_select = $("#new_cb_content_block_template");
    let $cb_create_button_label = $("#cb_create_label");
    updateButtonLabel($cb_template_select, $cb_create_button_label);

    $(document).on("change", "#new_cb_content_block_template", function () {
      updateButtonLabel($cb_template_select, $cb_create_button_label);
    });

    $(document).on("submit", ".create-form", function () {
      let $form = $(this);
      let $target = $("#content-blocks-root");
      let $position = $form.find("#new_cb_position");

      $position.val($target.children(".content-block-form").length);

      let title = $form.find("#cb_create_label").html();
      showStatus("Creating " + title);

      $form.ajaxSubmit({
        success: function (data) {
          renderAjaxResponse(data, $target, true);
          showStatus(title + " created");
        },
      });

      return false;
    });

    // Create nested buttons and select
    $(".nested-create-form").each(function () {
      updateButtonLabel($(this).children("select"), $(this).find(".nested-create-label"));
    });

    $(document).on("change", ".nested-create-form select", function () {
      updateButtonLabel($(this), $(this).parent().find(".nested-create-label"));
    });

    $(document).on("submit", ".nested-create-form", function () {
      let $form = $(this);

      if ($form.hasClass("disabled")) {
        return false;
      }

      let $target = $($form.data("target"));
      let $position = $form.find('input[name="position"]');

      $position.val($target.children(".content-block-form").length);

      let title = $form.find(".nested-create-label").html();
      showStatus("Creating " + title);

      $form.ajaxSubmit({
        success: function (data) {
          renderAjaxResponse(data, $target, true);
          enforceLimits($target);
          showStatus(title + " created");
        },
      });
      return false;
    });

    // Content block save button
    $(document).on("click", "button.save", function () {
      let $btn = $(this);
      saveContentBlock($btn);
    });

    // Toggle visible button
    $(document).on("click", "button.visible", function () {
      let $btn = $(this);
      let ajax_url = $btn.data("ajax_url");
      let label = $($btn.data("label")).children("input").val();

      $.ajax({
        type: "POST",
        url: ajax_url,
        success: function (data) {
          let $icon = $btn.children("i");
          $icon.removeClass("fa-eye fa-eye-slash");
          if (data.visible) {
            $icon.addClass("fa-eye");
            showStatus(label + " shown");
          } else {
            $icon.addClass("fa-eye-slash");
            showStatus(label + " hidden");
          }
        },
      });
    });

    // Preview button
    $(document).on("click", "button.preview", function (e) {
      let $btn = $(this);
      let $target = $($btn.data("target"));

      if ($target.hasClass("open")) {
        $target.slideUp().removeClass("open");
      } else {
        saveForPreview($btn, function () {
          loadPreview($btn);
        });
      }
    });

    // Delete button
    $(document).on("click", "button.delete", function () {
      let $btn = $(this);
      if ($btn.children("i").hasClass(delete_disabled_class)) {
        return false;
      }

      let $target = $($btn.data("target"));
      let $loader = $($btn.data("loader"));
      let ajax_url = $btn.data("ajax_url");

      let $popup_content = $($("#delete_popup_content").html());
      let label = $($btn.data("label")).children("input").val();
      $popup_content.find(".label").text(label);

      showPopup($btn, {
        css_class: "small",
        content: $popup_content,
        confirm_callback: function ($popup) {
          showLoader($loader);
          showStatus("Deleting " + label);
          $.ajax({
            url: ajax_url,
            type: "POST",
            dataType: "json",
            success: function (data) {
              if (data.error) {
                console.log(data.error);
              } else {
                let $wrapper = $target.closest(".content-blocks");
                $target.remove();

                $(".content-blocks").sortable("refresh");
                let positions = $wrapper.sortable("serialize");
                ajaxDragonDrop(positions);
                refreshDragonDrop();

                enforceLimits($wrapper);
                toggleImportForm();
                showStatus(label + " deleted");
              }
            },
          });
        },
      });
    });

    const expand_open_class = "fa-chevron-up";
    const expand_closed_class = "fa-chevron-down";

    // Expander button
    $(document).on("click", "button.expand", function () {
      let $button = $(this);
      let $icon = $button.children("i");
      let $expanders = $($button.data("target"));
      let $wrapper = $button.closest(".ui-sortable");

      $wrapper.css("overflow", "hidden");

      if ($icon.hasClass(expand_open_class)) {
        $icon.removeClass(expand_open_class).addClass(expand_closed_class);
        $expanders.slideUp(function () {
          $wrapper.css("overflow", "auto");
        });
      } else {
        $icon.removeClass(expand_closed_class).addClass(expand_open_class);
        $expanders.slideDown(function () {
          $wrapper.css("overflow", "auto");
        });
      }
    });

    // Collapse all button
    $(document).on("click", ".collapse-all", function () {
      let $target = $($(this).data("target"));
      $target.css("overflow", "hidden");
      $target
        .children(".content-block-form")
        .children(".pos-rel")
        .children(".controls")
        .children("button.expand")
        .each(function () {
          let $button = $(this);
          let $icon = $button.children("i");
          let $expanders = $($button.data("target"));
          $icon.removeClass(expand_open_class).addClass(expand_closed_class);
          $expanders.slideUp(function () {
            $target.css("overflow", "auto");
          });
        });
    });

    // Expand all button
    $(document).on("click", ".expand-all", function () {
      let $target = $($(this).data("target"));
      $target.css("overflow", "hidden");
      $target
        .children(".content-block-form")
        .children(".pos-rel")
        .children(".controls")
        .children("button.expand")
        .each(function () {
          let $button = $(this);
          let $icon = $button.children("i");
          let $expanders = $($button.data("target"));
          $icon.removeClass(expand_closed_class).addClass(expand_open_class);
          $expanders.slideDown(function () {
            $target.css("overflow", "auto");
          });
        });
    });

    // Remove saved class on change
    $(document).on("change", ".cb-form", function () {
      let $btn = $(this);
      setSaveState($($btn.data("btn")), false);
    });

    // Save & Continue button
    let saving_all = false;
    $(document).on("click", "#save_continue", function () {
      let $loader = $($(this).data("loader"));
      saveAll($loader);
    });

    // Save & Exit button
    $(document).on("click", "#save_exit", function () {
      let return_url = $(this).data("return_url");
      let $loader = $($(this).data("loader"));
      saveAll($loader, function () {
        exit(return_url);
      });
    });

    // Publish button
    $(document).on("submit", "#publish_form", function () {
      let $form = $(this);
      let $loader = $($(this).data("loader"));

      saveAll($loader, function () {
        showStatus("Publishing content blocks");
        $form.ajaxSubmit({
          success: function (data) {
            hideLoader($loader);
            showStatus("Content blocks published");
          },
        });
      });

      return false;
    });

    // Discard changes button
    $(document).on("submit", "#discard_form", function (e) {
      e.preventDefault();
      let $btn = $(this);
      showPopup($btn, {
        css_class: "small",
        content: $("#reset_popup_content").html(),
        confirm_callback: function ($popup) {
          let $loader = $($btn.data("loader"));
          let $target = $("#content-blocks-root");

          showLoader($loader);
          showStatus("Resetting content blocks");
          $btn.ajaxSubmit({
            success: function (data) {
              $(".content-block-form").remove();
              renderAjaxResponse(data, $target, true);
              toggleImportForm();
              hideLoader($loader);
              showStatus("Content blocks reset");
            },
          });
        },
      });

      return false;
    });

    // Functions
    function renderAjaxResponse(data, $target, append = false, callback) {
      // Handle ajax which returns html
      if (data.error) {
        console.log(data.error);
        return;
      }

      if (data.html) {
        if (append) {
          $target.append(data.html);
        } else {
          $target.html(data.html);
        }
        refreshDragonDrop();
        toggleImportForm();
      }

      if (callback) {
        return callback();
      }
    }

    function saveContentBlock($btn, saved_callback) {
      // Save a content block given it's save button.  Optionally call a callback on successful save.
      let $form = $($btn.data("form"));
      let $target = $($form.data("target"));
      let $loader = $($btn.data("loader"));
      showLoader($loader);
      let title = $form.find(".title input").val();
      showStatus("Saving " + title);
      $form.ajaxSubmit({
        success: function (data) {
          setSaveState($btn, data.saved);
          renderAjaxResponse(data, $target);
          hideLoader($loader);

          if (data.saved) {
            showStatus(title + " saved");
          } else {
            showStatus("Please correct the errors");
          }

          if (saved_callback && data.saved) {
            return saved_callback();
          }
        },
        uploadProgress: function (event, position, total, percentComplete) {
          $loader.find(".progress-bar").width(percentComplete + "%");
        },
      });
    }

    function updateButtonLabel($cb_type_select, $cb_create_button_label) {
      // Update the text in the create content block form submit button to match the selected content block type
      $cb_create_button_label.html($cb_type_select.children("option:selected").text());
    }

    function saveAll($loader, callback) {
      // Save all unsaved content blocks sequentially.
      if (saving_all) {
        return;
      }

      saving_all = true;
      showLoader($loader);
      showStatus("Saving all content blocks");
      expandAll();
      let $buttons = $("button.save").not(".saved");
      return saveNext($buttons, callback, $loader);
    }

    function saveForPreview($btn, callback) {
      // Save unsaved content blocks and nested blocks for preview
      let $buttons = $btn.closest(".content-block-form").find("button.save").not(".saved");
      return saveNext($buttons, callback);
    }

    function saveNext($buttons, callback, $loader, buttons) {
      // Used by save all to save sequentially then call callback if all saved OK or scroll to errors.
      let scroll_offset = 30;

      if (!buttons) buttons = $buttons.toArray();

      if (!buttons.length) {
        // Finished saving everything
        saving_all = false;
        let $unsaved = $buttons.not(".saved");
        if ($unsaved.length) {
          $("html").animate({ scrollTop: $unsaved.first().offset().top - scroll_offset }, 220);
          showStatus("Please correct the errors");
        } else {
          showStatus("All content blocks saved");
          if (callback) {
            return callback();
          }
        }
        if ($loader) hideLoader($loader);
        return;
      }

      let $btn = $(buttons.shift());

      let $inline_loader = undefined;
      if (!$loader) {
        $inline_loader = $($btn.data("loader"));
        showLoader($inline_loader);
      }

      let $form = $($btn.data("form"));
      let $target = $($form.data("target"));

      $form.ajaxSubmit({
        success: function (data) {
          setSaveState($btn, data.saved);
          renderAjaxResponse(data, $target, false, function () {
            if ($inline_loader) hideLoader($inline_loader);
            saveNext($buttons, callback, $loader, buttons);
          });
        },
        uploadProgress: function (event, position, total, percentComplete) {
          let $progress_bar;
          if ($inline_loader) {
            $progress_bar = $inline_loader.find(".progress-bar");
          } else {
            $progress_bar = $loader.find(".progress-bar");
          }
          $progress_bar.width(percentComplete + "%");
        },
      });
    }

    function exit(return_url) {
      // Redirect user to url.
      window.location.href = return_url;
    }

    function exitAjax(data, return_url) {
      // ajax calls which exit the editor come through here incase of errors.
      if (data.error) {
        console.log(data.error);
        return;
      }

      exit(return_url);
    }

    let loader_timers = {};
    function showLoader($loader) {
      // Show a loader.  The loader starts invisible and becomes visible to the user after a short time.
      $loader.show();
      loader_timers[$loader.attr("id")] = setTimeout(function () {
        showSpinner($loader);
      }, 750);
    }

    function hideLoader($loader) {
      // Hide a loader
      $loader.find(".progress-bar").width("100%");
      $loader.hide().children(".bg").hide();
      clearTimeout(loader_timers[$loader.attr("id")]);
    }

    function showSpinner($loader) {
      // Make the loader visible to the user
      $loader.find(".progress-bar").width("0%");
      $loader.children(".bg").show();
    }

    function enforceLimits($content_block_wrapper) {
      // Disable delete and add nested block buttons in the wrapper based on min_num and max_num
      let min_num = $content_block_wrapper.data("min_num");
      let max_num = $content_block_wrapper.data("max_num");

      let num_blocks = $content_block_wrapper.children(".content-block-form").length;

      let delete_buttons = $content_block_wrapper
        .children(".content-block-form")
        .children(".pos-rel")
        .children(".controls")
        .children("button.delete");
      if (num_blocks <= min_num) {
        // Min num reached so disabled delete button.
        delete_buttons.children("i").removeClass(delete_active_class).addClass(delete_disabled_class);
      } else {
        delete_buttons.children("i").removeClass(delete_disabled_class).addClass(delete_active_class);
      }

      let nested_form = $content_block_wrapper.siblings(".cb-nested-create-form").children(".nested-create-form");
      if (num_blocks >= max_num) {
        // Max num reached so disable nested form.
        nested_form.addClass("disabled");
      } else {
        nested_form.removeClass("disabled");
      }
    }

    function dragonDrop($content_block) {
      // Make a group of content blocks dragon sortable with jquery ui sortable.
      $content_block.sortable({
        axis: "y",
        handle: ".move",
        cancel: "",
        containment: $content_block,
        update: function (event, ui) {
          let positions = $(this).sortable("serialize");
          ajaxDragonDrop(positions);
        },
      });
    }

    function ajaxDragonDrop(positions) {
      // Post to updated positions in db.
      $.ajax({
        data: { positions: positions },
        type: "POST",
        url: settings.update_position_url,
      });
    }

    function refreshDragonDrop() {
      // Refresh dragon drops and create new for new content blocks.
      let $content_blocks = $(".content-blocks");
      $content_blocks.not(".ui-sortable").each(function () {
        dragonDrop($(this));
      });
      $content_blocks.sortable("refresh");
    }

    function expandAll() {
      // Opens all expanders
      $(".expander").slideDown();
      $("." + expand_closed_class)
        .removeClass(expand_closed_class)
        .addClass(expand_open_class);
    }

    const saved_class = "fa-floppy-disk";
    const unsaved_class = "fa-floppy-disk-circle-xmark";
    function setSaveState($btn, saved) {
      // Update the save state of the save button and change the icon.
      $btn.removeClass("saved");

      let $icon = $btn.children("i");
      $icon.removeClass(saved_class + " " + unsaved_class);
      if (saved) {
        $btn.addClass("saved");
        $icon.addClass(saved_class);
      } else {
        $icon.addClass(unsaved_class);
      }
    }

    function loadPreview($btn) {
      // Render a content block and get the html via ajax
      let $target = $($btn.data("target"));
      let $iframe = $target.children("iframe");

      $iframe
        .iFrameResize({
          checkOrigin: false,
        })
        .on("load", function () {
          $target.slideDown().addClass("open");
          $iframe.off("load");
        });

      $iframe.attr("src", $iframe.data("src"));
    }

    function toggleImportForm() {
      let $cb_import_form = $(".cb-import-form");
      if ($(".content-block-form").length) {
        $cb_import_form.hide();
      } else {
        $cb_import_form.show();
      }
    }

    let $status_bar = $("div.status");
    let status_off = $status_bar.html();
    let $status_timer;
    function renderStatus(status) {
      $status_bar.fadeOut(function () {
        $(this).html(status).fadeIn();
      });
    }

    function showStatus(status) {
      clearTimeout($status_timer);
      renderStatus(status);
      $status_timer = setTimeout(function () {
        renderStatus(status_off);
      }, 5500);
    }

    return this;
  };
})(jQuery);
