import { classMap } from 'lit/directives/class-map.js';
import { customElement, property, query, state } from 'lit/decorators.js';
import { HasSlotController } from '../internal/slot';
import { html } from 'lit';
import { watch } from '../internal/watch';
import NileElement from '../internal/nile-element';
import { styles } from './nile-checkbox-group.css';
import { Nile_Events } from '../internal/enum';
import type { CSSResultGroup, PropertyValues } from 'lit';
import type NileCheckbox from '../nile-checkbox/nile-checkbox';

/**
 * @summary Checkbox groups manage multiple [checkboxes](/components/checkbox) so they function as a multi-select form control.
 * Supports two association modes: slotted children or property-based remote association via `for`/`group`.
 *
 * @slot - The default slot where `<nile-checkbox>` elements are placed (slotted mode).
 * @slot label - The checkbox group's label. Required for proper accessibility. Alternatively, use the `label` attribute.
 *
 * @event change - Emitted when the checkbox group's selected values change.
 * @event input - Emitted when the checkbox group receives user input.
 *
 * @csspart form-control - The fieldset wrapper.
 * @csspart form-control-label - The group label.
 * @csspart form-control-input - The wrapper around the checkbox options.
 * @csspart options-base - The container for the checkbox options.
 */
@customElement('nile-checkbox-group')
export class NileCheckboxGroup extends NileElement {
  static styles: CSSResultGroup = styles;

  private readonly hasSlotController = new HasSlotController(this, 'help-text', 'label');
  private boundDocumentListener: ((event: Event) => void) | null = null;

  @query('slot:not([name])') defaultSlot: HTMLSlotElement;

  @state() defaultValue: string[] = [];

  /** The checkbox group's label. Required for proper accessibility. */
  @property() label = '';

  /** The name of the checkbox group, submitted as a name/value pair with form data. */
  @property() name = '';

  /** The current selected values, as an array of checkbox value strings. */
  @property({ type: Array }) value: string[] = [];

  /**
   * Group name for property-based association. When set, the group discovers
   * checkboxes anywhere in the DOM that have a matching `group` attribute
   * instead of relying on slotted children.
   */
  @property({ reflect: true, attribute: true}) for = '';

  /**
   * Associates the group with a `<form>` element by its `id`. The form must
   * be in the same document or shadow root.
   */
  @property({ reflect: true }) form = '';

  /** At least one checkbox must be checked before the form can submit. */
  @property({ type: Boolean, reflect: true }) required = false;

  /** Layout direction of the checkboxes. */
  @property({ reflect: true }) orientation: 'vertical' | 'horizontal' = 'vertical';

  /** Display checkboxes in an inline (horizontal) layout. Alias for orientation="horizontal". */
  @property({ type: Boolean, reflect: true }) labelInline = false;

  /** Disables all associated checkboxes. */
  @property({ type: Boolean, reflect: true }) disabled = false;

  @property({ attribute: true, reflect: true }) helpText = '';

  @property({ attribute: true, reflect: true }) errorMessage = '';

  /** Maximum number of checkboxes that can be checked. Unchecked boxes are disabled when the limit is reached. */
  @property({ type: Number, reflect: true }) max: number | undefined;

  /** Minimum selections required for validation. */
  @property({ type: Number, reflect: true }) min: number | undefined;

  /** Shows a visible border around the fieldset wrapper. */
  @property({ type: Boolean, reflect: true }) bordered = false;

  /** Legend text displayed at the top of the fieldset when bordered is enabled. */
  @property({ type: String, reflect: true }) legend = '';

  private get isHorizontal(): boolean {
    return this.orientation === 'horizontal' || this.labelInline;
  }

  connectedCallback() {
    super.connectedCallback();
    this.defaultValue = [...this.value];
    this.setupDocumentListener();
    this.emit(Nile_Events.NILE_INIT);
  }

  disconnectedCallback() {
    super.disconnectedCallback();
    this.teardownDocumentListener();
    this.emit(Nile_Events.NILE_DESTROY);
  }

  private setupDocumentListener() {
    if (this.for) {
      this.boundDocumentListener = (event: Event) => {
        const target = event.target as NileCheckbox;
        if (
          target.tagName?.toLowerCase() === 'nile-checkbox' &&
          target.group === this.for
        ) {
          this.handleCheckboxChange(target);
        }
      };
      document.addEventListener(Nile_Events.NILE_CHANGE, this.boundDocumentListener);
    }
  }

  private teardownDocumentListener() {
    if (this.boundDocumentListener) {
      document.removeEventListener(Nile_Events.NILE_CHANGE, this.boundDocumentListener);
      this.boundDocumentListener = null;
    }
  }

  @watch('for')
  handleForChange() {
    this.teardownDocumentListener();
    this.setupDocumentListener();
    this.syncCheckboxStates();
    this.enforceMaxDisabled();
  }

  private getAllCheckboxes(): NileCheckbox[] {
    if (this.for) {
      const root = this.getRootNode() as Document | ShadowRoot;
      return [...root.querySelectorAll<NileCheckbox>(`nile-checkbox[group="${this.for}"]`)];
    }
    return [...this.querySelectorAll<NileCheckbox>('nile-checkbox')];
  }

  private handleCheckboxClick(event: MouseEvent) {
    const target = (event.target as HTMLElement).closest<NileCheckbox>('nile-checkbox');
    if (!target) return;
    this.handleCheckboxChange(target);
  }

  private handleChildInput(event: Event) {
    event.stopPropagation();
  }

  private handleCheckboxChange(target: NileCheckbox) {
    if (target.disabled || this.disabled) return;

    const checkboxes = this.getAllCheckboxes();
    const checkedCount = checkboxes.filter(cb => cb.checked).length;

    if (this.max !== undefined && checkedCount > this.max) {
      target.checked = false;
      return;
    }

    const newValue = checkboxes
      .filter(cb => cb.checked)
      .map(cb => String(cb.value));

    const oldValue = this.value;
    this.value = newValue;

    this.enforceMaxDisabled();

    if (JSON.stringify(oldValue) !== JSON.stringify(newValue)) {
      this.emit(Nile_Events.NILE_CHANGE, { value: this.value });
    }
  }

  private enforceMaxDisabled() {
    if (this.max === undefined) return;

    const checkboxes = this.getAllCheckboxes();
    const checkedCount = checkboxes.filter(cb => cb.checked).length;
    const atLimit = checkedCount >= this.max;

    checkboxes.forEach(cb => {
      if (!cb.checked && !this.disabled) {
        cb.disabled = atLimit;
      }
    });
  }

  protected updated(changedProperties: PropertyValues): void {
    super.updated(changedProperties);

    if (changedProperties.has('max')) {
      this.enforceMaxDisabled();
    }
  }

  @watch('disabled', { waitUntilFirstUpdate: true })
  handleDisabledChange() {
    if (this.disabled) {
      this.getAllCheckboxes().forEach(cb => {
        cb.disabled = true;
      });
    } else {
      this.getAllCheckboxes().forEach(cb => {
        cb.disabled = cb.hasAttribute('disabled');
      });
      this.enforceMaxDisabled();
    }
  }

  private handleInitialDisabledState() {
    if (this.disabled) {
      this.getAllCheckboxes().forEach(cb => {
        cb.disabled = true;
      });
    } else {
      this.getAllCheckboxes().forEach(cb => {
        cb.disabled = cb.hasAttribute('disabled') ? true : false;
      });
    }
  }

  private handleLabelClick() {
    if (this.disabled) return;
    const checkboxes = this.getAllCheckboxes();
    const first = checkboxes[0];
    if (first) {
      first.focus();
    }
  }

  private handleSlotChange() {
    const checkboxes = this.getAllCheckboxes();

    checkboxes.forEach(cb => {
      cb.checked = this.value.includes(String(cb.value));
    });

    this.handleInitialDisabledState();
    this.enforceMaxDisabled();
  }

  private syncCheckboxStates() {
    const checkboxes = this.getAllCheckboxes();
    checkboxes.forEach(cb => {
      cb.checked = this.value.includes(String(cb.value));
    });
  }

  @watch('value')
  handleValueChange() {
    if (this.hasUpdated) {
      if (this.max !== undefined && this.value.length > this.max) {
        this.value = this.value.slice(0, this.max);
      }
      this.syncCheckboxStates();
      this.enforceMaxDisabled();
    }
  }

  render() {
    const hasLabelSlot = this.hasSlotController.test('label');
    const hasLabelSuffixSlot = this.hasSlotController.test('label-suffix');
    const hasLabel = this.label ? true : !!hasLabelSlot;
    const hasHelpText = this.helpText ? true : false;
    const hasErrorMessage = this.errorMessage ? true : false;

    const defaultSlot = html`
      <slot
        @click=${this.handleCheckboxClick}
        @input=${this.handleChildInput}
        @slotchange=${this.handleSlotChange}
        role="presentation"
      ></slot>
    `;

    return html`
      <fieldset
        part="form-control"
        class=${classMap({
          'form-control': true,
          'form-control--medium': true,
          'form-control--checkbox-group': true,
          'form-control--has-label': hasLabel,
          'form-control--has-help-text': hasHelpText,
          'form-control--bordered': this.bordered,
        })}
        role="group"
        aria-labelledby="label"
        aria-describedby="help-text"
        aria-errormessage="error-message"
      >
        ${this.legend
          ? html`<legend part="legend legend-label" class="form-control__legend">${this.legend}</legend>`
          : ``}

        <label
          part="form-control-label"
          id="label"
          class="form-control__label"
          aria-hidden=${hasLabel ? 'false' : 'true'}
          @click=${this.handleLabelClick}
        >
          <slot name="label">${this.label}</slot>
        </label>

        ${hasLabelSuffixSlot ? html` <slot name="label-suffix"></slot> ` : ``}

        <div part="form-control-input" class="form-control-input" >
          <div class="visually-hidden" part="validation-hidden">
            <label class="checkbox-group__validation" part="validation-label" >
              <input
                type="text"
                class="checkbox-group__validation-input"
                part="validation-input"
                ?required=${this.required}
                tabindex="-1"
                hidden
              />
            </label>
          </div>

          <div part="options-base" class="${this.isHorizontal ? `form-control--inline-checkbox` : ``}">
            ${defaultSlot}
          </div>
        </div>

        ${hasHelpText
  ? html` <nile-form-help-text id="help-text">${this.helpText}</nile-form-help-text> `
  : ``}

${hasErrorMessage
  ? html`
      <nile-form-error-message id="error-message">${this.errorMessage}</nile-form-error-message>
            `
          : ``}
      </fieldset>
    `;
  }
}

export default NileCheckboxGroup;

declare global {
  interface HTMLElementTagNameMap {
    'nile-checkbox-group': NileCheckboxGroup;
  }
}
