/* Pi-hole: A black hole for Internet advertisements
 *  (c) 2017 Pi-hole, LLC (https://pi-hole.net)
 *  Network-wide ad blocking via your own hardware.
 *
 *  This file is copyright under the latest version of the EUPL.
 *  Please see LICENSE file for your rights under this license. */

/* global utils:false, apiFailure:false*/

"use strict";

$(() => {
  // Handle hiding of alerts
  $("[data-hide]").on("click", function () {
    $(this)
      .closest("." + $(this).attr("data-hide"))
      .hide();
  });

  // Handle saving of settings
  $(".save-button").on("click", () => {
    saveSettings();
  });
});

// Globally available function to set config values
// eslint-disable-next-line no-unused-vars
function setConfigValues(topic, key, value) {
  // If the value is an object, we need to recurse
  if (!("description" in value)) {
    for (const [subkey, subvalue] of Object.entries(value)) {
      // If the key is empty, we are at the top level
      const newKey = key === "" ? subkey : key + "." + subkey;
      setConfigValues(topic, newKey, subvalue);
    }

    return;
  }

  // else: we have a setting we can set
  const escapedKey = key.replaceAll(".", "\\.");
  const envTitle = $(`[data-configkeys~='${key}']`);

  if (
    envTitle.parents().parents().hasClass("settings-level-expert") &&
    envTitle.find(".expert-warning").length === 0
  ) {
    envTitle.append(
      '<span class="expert-warning">&nbsp;&nbsp;<i class="fas fa-wrench" title="Expert level setting"></i></span>'
    );
  }

  if (value.flags.restart_dnsmasq && envTitle.find(".restart-warning").length === 0) {
    envTitle.append(
      '<span class="restart-warning">&nbsp;&nbsp;<i class="fas fa-redo text-orange" title="Setting requires FTL restart on change"></i></span>'
    );
  }

  if (value.flags.env_var) {
    // If this setting has been set by environment variable, display a padlock in the section title
    if (envTitle.find(".env-warning").length === 0) {
      envTitle.append(
        '<span class="env-warning">&nbsp;&nbsp;<i class="fas fa-lock text-orange env-warning" title="Settings overwritten by an environmental variable are read-only"></i></span>'
      );
    }

    $(`#${escapedKey}`).prop("disabled", "disabled");
  }

  switch (value.type) {
    case "enum (unsigned integer)": // fallthrough
    case "enum (string)": {
      // Remove all options from select
      $("#" + escapedKey + " option").remove();
      // Add allowed select items (if available)
      for (const allowedValue of value.allowed) {
        $("#" + escapedKey + "-" + allowedValue.item).prop("disabled", value.flags.env_var);
        const newopt = $("<option></option>")
          .attr("value", allowedValue.item)
          .text(allowedValue.description);
        $("#" + escapedKey).append(newopt);
      }

      // Select the current value
      $("#" + escapedKey)
        .val(value.value)
        .trigger("click");

      // Also select matching radio button (if any)
      $("#" + escapedKey + "-" + value.value).prop("checked", true);

      break;
    }

    case "boolean": {
      // Select checkboxes (if available)
      $("#" + escapedKey).prop("checked", value.value);

      break;
    }

    case "string array": {
      // Set input field values from array (if available)
      $("#" + escapedKey).val(value.value.join("\n"));
      break;
    }

    default: {
      // Set input field values (if available)
      // Set text if this is a <span> or <code> element
      if ($("#" + escapedKey).is("span") || $("#" + escapedKey).is("code")) {
        $("#" + escapedKey).text(value.value);
      } else {
        // Set value if this is an <input> element
        $("#" + escapedKey).val(value.value);
      }
    }
  }
}

function saveSettings() {
  const settings = {};
  utils.disableAll();
  $("[data-key]").each(function () {
    const key = $(this).data("key");
    let value = $(this).val();

    // If this is a checkbox, use the checked state
    if ($(this).is(":checkbox")) {
      value = $(this).is(":checked");
    }

    // If this is a radio button, skip all but the checked one
    if ($(this).is(":radio") && !$(this).is(":checked")) {
      return;
    }

    // If this is a string array, split the value into an array
    if ($(this).is("textarea")) {
      value = $(this).val();
      value = value === "" ? [] : value.split("\n");

      // On DNS settings page, upstream table selections are merged at save
      // time so the textarea can remain manual-only in the UI.
      if (key === "dns.upstreams" && typeof globalThis.getMergedDNSupstreams === "function") {
        value = globalThis.getMergedDNSupstreams(value);
      }
    }

    // If this is an integer number, parse it accordingly
    if ($(this).data("type") === "integer") {
      value = value === "" ? NaN : Math.trunc(value);
    }

    // If this is a floating point value, parse it accordingly
    if ($(this).data("type") === "float") {
      value = value === "" ? NaN : Number(value);
    }

    // Build deep object
    // Transform "foo.bar.baz" into {foo: {bar: {baz: value}}}
    const parts = key.split(".");
    const obj = {};
    let tmp = obj;
    for (let i = 0; i < parts.length - 1; i++) {
      tmp[parts[i]] = {};
      tmp = tmp[parts[i]];
    }

    tmp[parts.at(-1)] = value;

    // Merge deep object into settings
    $.extend(true, settings, obj);
  });

  // Apply changes
  $.ajax({
    url: document.body.dataset.apiurl + "/config",
    method: "PATCH",
    dataType: "json",
    processData: false,
    data: JSON.stringify({ config: settings }),
    contentType: "application/json; charset=utf-8",
  })
    .done(() => {
      utils.enableAll();
      // Success
      utils.showAlert(
        "success",
        "fa-solid fa-fw fa-floppy-disk",
        "Successfully saved and applied settings",
        ""
      );
      // Show loading overlay
      utils.loadingOverlay(true);
    })
    .fail((data, exception) => {
      utils.enableAll();
      utils.showAlert("error", "", "Error while applying settings", data.responseText);
      console.log(exception); // eslint-disable-line no-console
      apiFailure(data);
    });
}
