import { renderHTML } from './render';
import CSS from './jb-checkbox.css';
import VariablesCSS from './variables.css';

import { ValidationHelper, type ValidationItem, type ValidationResult, type ShowValidationErrorParameters, type WithValidation } from 'jb-validation';
import type { JBFormInputStandards, JBFormWebComponent } from 'jb-form';
import type { ElementsObject, ValidationValue } from './types.js';
import { registerDefaultVariables } from 'jb-core/theme';
import { dictionary } from './i18n';
import { i18n } from 'jb-core/i18n';
export * from './types.js';

export class JBCheckboxWebComponent extends HTMLElement implements WithValidation, JBFormInputStandards<boolean> {
  static get formAssociated() { return true; }
  #value = false;
  //when we call on before change we save new value here so when user use event.target.value he will see new value but after the event bubble done we null it.
  //it mostly defined here for react eco-system
  #ChangeEventPreservedValue: boolean | null = null;
  elements!: ElementsObject;
  #disabled = false;
  #internals?: ElementInternals;
  get value(): boolean {
    if (this.#ChangeEventPreservedValue !== null) {
      return this.#ChangeEventPreservedValue;
    }
    return this.#value;
  }
  set value(value: boolean) {
    if (this.#value !== value) {
      this.#value = value;
    }
    this.#updateDomForValueChange();
    if (this.#internals) {
      this.#internals.ariaChecked = value ? "true" : "false";
      if (value) {
        this.#internals.states?.add("checked");
      } else {
        this.#internals.states?.delete("checked");
      }
      if (typeof this.#internals.setFormValue == "function") {
        this.#internals.setFormValue(`${value}`);
      }
    }

  }
  get checked() {
    return this.value === true;
  }
  #validation = new ValidationHelper({
    getValue: () => (this.value),
    getValidations: this.#getInsideValidationsCallback.bind(this),
    getValueString: () => (this.value ? 'true' : 'false'),
    setValidationResult: this.#setValidationResult.bind(this),
    showValidationError: this.showValidationError.bind(this),
    clearValidationError: this.clearValidationError.bind(this),
  })
  get validation() {
    return this.#validation;
  }
  get name() {
    return this.getAttribute('name') || '';
  }
  initialValue = false;
  get isDirty(): boolean {
    return this.#value !== this.initialValue;
  }
  #required = false;
  set required(value: boolean) {
    this.#required = value;
    this.#validation.checkValiditySync({ showError: false });
  }
  get required() {
    return this.#required;
  }
  isAutoValidationDisabled = false;
  get disabled() {
    return this.#disabled;
  }
  set disabled(value: boolean) {
    this.#disabled = value;
    if (value) {
      this.#internals?.states?.add("disabled");
      this.#internals!.ariaDisabled = "true";
    } else {
      this.#internals?.states?.delete("disabled");
      this.#internals!.ariaDisabled = "false";
    }
    this.#syncFocusableState();
  }
  constructor() {
    super();
    if (typeof this.attachInternals == "function") {
      //some browser don't support attachInternals
      this.#internals = this.attachInternals();
      this.#internals.role = "checkbox";
    }
    this.#initWebComponent();
  }
  get form(): HTMLFormElement | JBFormWebComponent | null {
    return this.#internals?.form??null;
  }
  formAssociatedCallback?: ((form: HTMLFormElement | null) => void) | undefined;
  formResetCallback?: (() => void) | undefined;
  formDisabledCallback?: ((disabled: boolean) => void) | undefined;
  formStateRestoreCallback?: ((state: string | File | FormData | null, mode: 'autocomplete' | 'restore') => void) | undefined;
  connectedCallback(): void {
    // standard web component event that called when all of dom is bound
    this.callOnLoadEvent();
    this.initProp();
    this.callOnInitEvent();

  }
  callOnLoadEvent(): void {
    const event = new CustomEvent('load', { bubbles: true, composed: false });
    this.dispatchEvent(event);
  }
  callOnInitEvent(): void {
    const event = new CustomEvent('init', { bubbles: true, composed: false });
    this.dispatchEvent(event);
  }
  #initWebComponent(): void {
    if (!this.shadowRoot) {
      this.attachShadow({
        mode: 'open',
        delegatesFocus: true,
        serializable: true,
        clonable: true,
      });
    }

    registerDefaultVariables();
    const html = `<style>${CSS} ${VariablesCSS}</style>\n${renderHTML()}`;
    const element = document.createElement('template');
    element.innerHTML = html;
    this.shadowRoot!.appendChild(element.content.cloneNode(true));
    this.elements = {
      componentWrapper: this.shadowRoot!.querySelector('.jb-checkbox-web-component')!,
      label: this.shadowRoot!.querySelector('.label-wrapper slot')!,
      svgWrapper: this.shadowRoot!.querySelector('.svg-wrapper')!,
      svg: this.shadowRoot!.querySelector('.check-box-svg')!,
      message: this.shadowRoot!.querySelector('.message-box')!,
    };
    this.registerEventListener();
  }
  registerEventListener(): void {
    this.elements.componentWrapper.addEventListener('click', () => this.#onComponentClick());
    this.elements.componentWrapper.addEventListener('keydown', (event) => this.#onKeyDown(event));
    this.elements.componentWrapper.addEventListener('keyup', (event) => this.#onKeyUp(event));
  }
  initProp() {
    this.value = this.getAttribute('value') === "true" || false;
  }
  static get observedAttributes(): string[] {
    return ["label", "message", 'value', 'name', 'disabled', 'required', 'error'];
  }
  attributeChangedCallback(name: string, _oldValue: string, newValue: string): void {
    // do something when an attribute has changed
    this.onAttributeChange(name, newValue);
  }
  onAttributeChange(name: string, value: string): void {
    switch (name) {
      case 'value':
        this.value = Boolean(value);
        break;
      case 'label':
        this.elements.label.innerText = value;
        this.#internals!.ariaLabel = value;
        break;
      case 'disabled':
        if (value == '' || value === "true") {
          this.disabled = true;
        } else if (value == "false" || value == null || value == undefined) {
          this.disabled = false;
        }
        break;
      case 'required':
        this.required = !!((value || value === '') && value !== 'false');
        break;
      case 'message':
        this.elements.message.innerHTML = value;
        break;
      case 'error':
        this.reportValidity();
    }

  }
  #onComponentClick(): void {
    if (this.#disabled) {
      return;
    }
    this.#ChangeEventPreservedValue = !this.#value;
    const isEventPrevented = this.#dispatchOnBeforeChangeEvent();
    this.#ChangeEventPreservedValue = null;
    if (!isEventPrevented) {
      this.value = !this.#value;
      this.#validation.checkValidity({ showError: true });
      const DispatchedEvent = this.#dispatchOnChangeEvent();
      if (DispatchedEvent.defaultPrevented) {
        this.value = !this.#value;
      }
    }
  }
  #onKeyDown(event: KeyboardEvent): void {
    if (this.#isSpaceKey(event)) {
      event.preventDefault();
    }
  }
  #onKeyUp(event: KeyboardEvent): void {
    if (this.#disabled) {
      return;
    }
    if (this.#isSpaceKey(event)) {
      event.preventDefault();
      this.#onComponentClick();
    }
  }
  #isSpaceKey(event: KeyboardEvent): boolean {
    return event.key === ' ' || event.key === 'Spacebar';
  }
  #dispatchOnBeforeChangeEvent(): boolean {
    const event = new CustomEvent('before-change', { cancelable: true });
    this.dispatchEvent(event);
    const prevented = event.defaultPrevented;
    return prevented;
  }
  #dispatchOnChangeEvent() {
    const event = new Event('change', { bubbles: true, cancelable: true, composed: true });
    this.dispatchEvent(event);
    return event;
  }
  /**
   * @public
   */
  focus(options?: FocusOptions) {
    //public method
    if (!this.#disabled) {
      this.elements.componentWrapper.focus(options);
    }
  }
  #syncFocusableState() {
    this.elements.componentWrapper.tabIndex = this.#disabled ? -1 : 0;
  }
  #updateDomForValueChange() {
    if (this.value) {
      this.elements.svg.classList.add('--active');
    } else {
      this.elements.svg.classList.remove('--active');
    }

  }
  /**
* @description this method called on every checkValidity calls and update validation result of #internal
*/
  #setValidationResult(result: ValidationResult<ValidationValue>) {
    if (result.isAllValid) {
      this.#internals?.setValidity({}, '');
    } else {
      const states: ValidityStateFlags = {};
      let message = "";
      result.validationList.forEach((res) => {
        if (!res.isValid) {
          if (res.validation.stateType) { states[res.validation.stateType] = true; }
          if (message == '') { message = res.message ?? ""; }
        }
      });
      this.#internals?.setValidity(states, message);
    }
  }
  #getInsideValidationsCallback(): ValidationItem<ValidationValue>[] {
    const validationList: ValidationItem<ValidationValue>[] = []

    if (this.#required) {
      const message: string = this.getAttribute("required") ?? "".length > 0 ? this.getAttribute("required")! : dictionary.get(i18n, "requiredMessage")
      validationList.push({
        validator: (value) => value !== false,
        message,
        stateType: "valueMissing"
      });
    }
    if (this.getAttribute("error") !== null && (this.getAttribute("error") ?? "").trim().length > 0) {
      validationList.push({
        validator: undefined,
        message: this.getAttribute("error") ?? "",
        stateType: "customError"
      });
    }
    return validationList;
  }
  showValidationError(error: ShowValidationErrorParameters) {
    this.elements.message.innerHTML = error.message;
    //invalid state is used for ui purpose
    this.#internals?.states?.add("invalid");
    this.#internals!.ariaInvalid = "true"
  }
  clearValidationError() {
    const text = this.getAttribute("message") || "";
    this.elements.message.innerHTML = text;
    (this.#internals as any).states?.delete("invalid");
    this.#internals!.ariaInvalid = "false"
  }
  get validationMessage() {
    return this.#internals!.validationMessage;
  }

  checkValidity() {
    return this.#validation.checkValiditySync({ showError: false }).isAllValid;
  }
  reportValidity() {
    return this.#validation.checkValiditySync({ showError: true }).isAllValid;
  }
}
const myElementNotExists = !customElements.get('jb-checkbox');
if (myElementNotExists) {
  window.customElements.define('jb-checkbox', JBCheckboxWebComponent);
}
