import {
  ChangeDetectionStrategy,
  Component,
  contentChildren,
  model,
  effect,
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { ChecklistValueDirective } from './checklist-value.directive';
import { isEqual } from 'lodash';

/**
 * A component that implements a list of checkboxes with form integration.
 *
 * ChecklistComponent manages a group of checkboxes that work together to maintain
 * an array of selected values. It implements ControlValueAccessor to work seamlessly
 * with Angular Forms. Each checkbox in the group should use the ChecklistValueDirective.
 *
 * @example
 * ```html
 * <!-- Using with template-driven forms -->
 * <ui-checklist [(ngModel)]="selectedFruits">
 *   <label class="ui-checkbox-label">
 *     <ui-checkbox>
 *       <input type="checkbox" [uiChecklistValue]="'apple'">
 *     </ui-checkbox>
 *     Apple
 *   </label>
 *   <label class="ui-checkbox-label">
 *     <ui-checkbox>
 *       <input type="checkbox" [uiChecklistValue]="'orange'">
 *     </ui-checkbox>
 *     Orange
 *   </label>
 * </ui-checklist>
 *
 * <!-- Using with reactive forms -->
 * <ui-checklist [formControl]="fruitsControl">
 *   <!-- Checkbox items -->
 * </ui-checklist>
 * ```
 */
@Component({
  selector: 'ui-checklist',
  template: '<ng-content></ng-content>',
  changeDetection: ChangeDetectionStrategy.OnPush,
  host: {
    '[class]': '["ui-checklist"]',
  },
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: ChecklistComponent,
      multi: true,
    },
  ],
})
export class ChecklistComponent implements ControlValueAccessor {
  /**
   * Signal that holds the current value of the checklist (array of selected values)
   */
  value = model<unknown[]>([]);

  /**
   * Callback function that is registered via registerOnChange
   * and called when the checklist value changes
   */
  onChange?: (value: unknown[]) => void;

  /**
   * Callback function that is registered via registerOnTouched
   * and called when the checklist is touched
   */
  onTouched?: () => void;

  /**
   * Collection of all ChecklistValueDirective instances within this component
   */
  valueDirectives = contentChildren(ChecklistValueDirective, {
    descendants: true,
  });

  /**
   * Effect that synchronizes the checked state of child ChecklistValueDirectives
   * with the overall value of this checklist component
   */
  valueDirectivesEffect = effect(() => {
    const valueDirectives = this.valueDirectives();

    const currentValue = this.value() ?? [];
    // Use slice() instead of structuredClone for compatibility with test environment
    const nextValue = [...currentValue];

    for (const directive of valueDirectives) {
      const value = directive.uiChecklistValue();
      const checked = directive.checked();

      if (checked) {
        if (!nextValue.includes(value)) {
          nextValue.push(value);
        }
      } else {
        const index = nextValue.indexOf(value);
        if (index !== -1) {
          nextValue.splice(index, 1);
        }
      }
    }

    if (isEqual(currentValue, nextValue)) {
      return;
    }

    this.value.set(nextValue);
    this.onChange?.(nextValue);
    this.onTouched?.();
  });

  /**
   * Sets the value of the checklist component. Part of the ControlValueAccessor interface.
   * Updates the checked state of all child checkboxes to match the provided value.
   *
   * @param obj - The value to write to the component
   */
  writeValue(obj: unknown): void {
    if (Array.isArray(obj)) {
      this.value.set(obj);
      for (const directive of this.valueDirectives()) {
        const value = directive.uiChecklistValue();
        const checked = obj.includes(value);
        directive.checked.set(checked);
      }
    } else {
      this.value.set([]);
    }
  }

  /**
   * Registers a callback function that is called when the checklist value changes.
   * Part of the ControlValueAccessor interface.
   *
   * @param fn - The callback function
   */
  registerOnChange(fn: (value: unknown[]) => void): void {
    this.onChange = fn;
  }

  /**
   * Registers a callback function that is called when the checklist is touched.
   * Part of the ControlValueAccessor interface.
   *
   * @param fn - The callback function
   */
  registerOnTouched(fn: () => void): void {
    this.onTouched = fn;
  }
}
