import $ from "jquery";

import { InputBinding } from "./inputBinding";
import { $escape, updateLabel, hasDefinedProperty } from "../../utils";
import type { CheckedHTMLElement } from "./checkbox";

type CheckboxGroupHTMLElement = CheckedHTMLElement;
type ValueLabelObject = {
  value: HTMLInputElement["value"];
  label: string;
};
type CheckboxGroupReceiveMessageData = {
  options?: string;
  value?: Parameters<CheckboxGroupInputBinding["setValue"]>[1];
  label: string;
};

type CheckboxGroupValue = CheckboxGroupHTMLElement["value"];

// Get the DOM element that contains the top-level label
function getLabelNode(el: CheckboxGroupHTMLElement): JQuery<HTMLElement> {
  return $(el).find('label[for="' + $escape(el.id) + '"]');
}
// Given an input DOM object, get the associated label. Handles labels
// that wrap the input as well as labels associated with 'for' attribute.
function getLabel(obj: HTMLElement): string | null {
  const parentNode = obj.parentNode as HTMLElement;

  // If <label><input /><span>label text</span></label>
  if (parentNode.tagName === "LABEL") {
    return $(parentNode).find("span").text().trim();
  }

  return null;
}
// Given an input DOM object, set the associated label. Handles labels
// that wrap the input as well as labels associated with 'for' attribute.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function setLabel(obj: HTMLElement, value: string): null {
  const parentNode = obj.parentNode as HTMLElement;

  // If <label><input /><span>label text</span></label>
  if (parentNode.tagName === "LABEL") {
    $(parentNode).find("span").text(value);
  }

  return null;
}

class CheckboxGroupInputBinding extends InputBinding {
  find(scope: HTMLElement): JQuery<HTMLElement> {
    return $(scope).find(".shiny-input-checkboxgroup");
  }

  getValue(el: CheckboxGroupHTMLElement): CheckboxGroupValue[] {
    // Select the checkbox objects that have name equal to the grouping div's id
    const $objs = $('input:checkbox[name="' + $escape(el.id) + '"]:checked');
    const values = new Array($objs.length);

    for (let i = 0; i < $objs.length; i++) {
      values[i] = ($objs[i] as CheckboxGroupHTMLElement).value;
    }
    return values;
  }
  setValue(el: HTMLElement, value: string[] | string | null): void {
    // Null value should be treated as empty array
    value = value ?? [];

    // Clear all checkboxes
    $('input:checkbox[name="' + $escape(el.id) + '"]').prop("checked", false);

    // Accept array
    if (value instanceof Array) {
      for (let i = 0; i < value.length; i++) {
        $(
          'input:checkbox[name="' +
            $escape(el.id) +
            '"][value="' +
            $escape(value[i]) +
            '"]'
        ).prop("checked", true);
      }
      // Else assume it's a single value
    } else {
      $(
        'input:checkbox[name="' +
          $escape(el.id) +
          '"][value="' +
          $escape(value) +
          '"]'
      ).prop("checked", true);
    }
  }
  getState(el: CheckboxGroupHTMLElement): {
    label: string;
    value: ReturnType<CheckboxGroupInputBinding["getValue"]>;
    options: ValueLabelObject[];
  } {
    const $objs = $(
      'input:checkbox[name="' + $escape(el.id) + '"]'
    ) as JQuery<CheckboxGroupHTMLElement>;

    // Store options in an array of objects, each with with value and label
    const options = new Array($objs.length);

    for (let i = 0; i < options.length; i++) {
      options[i] = { value: $objs[i].value, label: getLabel($objs[i]) };
    }

    return {
      label: getLabelNode(el).text(),
      value: this.getValue(el),
      options: options,
    };
  }
  receiveMessage(
    el: CheckboxGroupHTMLElement,
    data: CheckboxGroupReceiveMessageData
  ): void {
    const $el = $(el);

    // This will replace all the options
    if (hasDefinedProperty(data, "options")) {
      // Clear existing options and add each new one
      $el.find("div.shiny-options-group").remove();
      // Backward compatibility: for HTML generated by shinybootstrap2 package
      $el.find("label.checkbox").remove();
      $el.append(data.options);
    }

    if (hasDefinedProperty(data, "value")) {
      this.setValue(el, data.value);
    }

    updateLabel(data.label, getLabelNode(el));

    $(el).trigger("change");
  }
  subscribe(
    el: CheckboxGroupHTMLElement,
    callback: (x: boolean) => void
  ): void {
    $(el).on("change.checkboxGroupInputBinding", function () {
      callback(false);
    });
  }
  unsubscribe(el: CheckboxGroupHTMLElement): void {
    $(el).off(".checkboxGroupInputBinding");
  }
}

export { CheckboxGroupInputBinding };
export type { CheckboxGroupReceiveMessageData };
