<script lang="ts">
  /**
   * A pincode input is a specialized input field used to enter short numeric codes, such as verification codes, security PINs, or authentication tokens. It typically separates each digit into individual fields to improve readability and ease of entry. This component is commonly used in two-factor authentication (2FA), password recovery, and secure access flows, ensuring a structured and user-friendly experience.
   *
   * @event input {CustomEvent<{ value: string }>} - Emits when the pincode value changes.
   */
  interface Props {
    [key: string]: any;
    /**
     * A unique identifier for the pincode element, used to associate the label with the form element.
     */
    id: string;
    /**
     * The number of input displayed in the pincode element.
     */
    length?: 4 | 5 | 6;
    /**
     * The name attribute for the pincode element, typically used for form submission.
     */
    name?: string;
    /**
     * The current value of the pincode field.
     */
    value?: string | number;
    /**
     * If `true`, applies an invalid state to the pincode.
     */
    isinvalid?: boolean;
    /**
     * If `true`, disables the pincode, making it non-interactive.
     */
    disabled?: boolean;
    /**
     * If `true`, the pincode is read-only (cannot be edited).
     */
    readonly?: boolean;
    /**
     * Callback fired when the pincode value changes.
     */
    onInput?: (detail: { value: string }) => void;
  }

  let {
    id,
    length = 6,
    name,
    value = $bindable(),
    isinvalid,
    disabled,
    readonly,
    onInput,
    class: className,
    ...attrs
  }: Props = $props();

  const L = $derived(typeof length === 'string' ? parseInt(length, 10) || 6 : length);

  let otp = $state<string[]>([]);
  let inputs = $state<HTMLInputElement[]>([]);
  let element: HTMLElement;

  $effect(() => {
    const incoming = String(value ?? '');
    otp = Array(L).fill('');
    otp = Array.from({ length: L }, (_, i) => incoming[i] ?? '');
  });

  const focusInput = (i: number) => {
    queueMicrotask(() => inputs?.[i]?.focus());
  };

  const emitInput = () => {
    const detail = { value: otp.join('') };
    onInput?.(detail);
    const event = new CustomEvent('input', {
      detail,
      bubbles: true,
      composed: true,
    });
    element.dispatchEvent(event);
  };

  const onInputItem = (e: Event, index: number) => {
    const el = e.target as HTMLInputElement;
    const digit = el.value.replace(/\D/g, '').slice(0, 1);
    otp[index] = digit;
    emitInput();
    if (digit && index + 1 < L) focusInput(index + 1);
  };

  const onKeyDown = (e: KeyboardEvent, index: number) => {
    if (e.key === 'ArrowLeft' && index > 0) {
      e.preventDefault();
      focusInput(index - 1);
    } else if (e.key === 'ArrowRight' && index < L - 1) {
      e.preventDefault();
      focusInput(index + 1);
    } else if (e.key === 'Backspace' && otp[index] === '' && index > 0) {
      otp[index - 1] = '';
      emitInput();
      focusInput(index - 1);
    }
  };

  const onPaste = (e: ClipboardEvent) => {
    e.preventDefault();
    const pasted = (e.clipboardData?.getData('text') ?? '').replace(/\D/g, '').slice(0, L);
    otp = Array.from({ length: L }, (_, i) => pasted[i] ?? '');
    emitInput();
    focusInput(Math.min(pasted.length, L - 1));
  };
</script>

<div
  class={{
    'mc-pincode-input': true,
    'mc-pincode-input__disabled': disabled,
    'mc-pincode-input__readonly': readonly,
    'is-invalid': isinvalid,
    className,
  }}
  onpaste={onPaste}
  bind:this={element}
  {...attrs}
>
  {#each [...Array(L).keys()] as i (i)}
    <input
      id={`pincodeItem${i}`}
      bind:this={inputs[i]}
      class="mc-pincode-input__control"
      type="text"
      inputmode="numeric"
      maxlength="1"
      pattern="\d*"
      autocomplete="one-time-code"
      name={name ?? `pincode-${id}`}
      {disabled}
      {readonly}
      value={otp[i]}
      oninput={(e) => onInputItem(e, i)}
      onkeydown={(e) => onKeyDown(e as KeyboardEvent, i)}
    />
  {/each}
</div>

<style>/**
 * Do not edit directly, this file was auto-generated.
 */
.mc-pincode-input {
  background-color: var(--forms-color-background-default, #ffffff);
  border: var(--border-width-s, 0.0625rem) solid var(--forms-color-border-default, #666666);
  border-radius: var(--forms-border-radius, 0.25rem);
  transition: all ease 200ms;
  color: var(--forms-color-text-default, #000000);
  display: block;
  width: auto;
  box-sizing: border-box;
  height: 4rem;
}
.mc-pincode-input.is-invalid {
  border-width: var(--border-width-m, 0.125rem);
  border-color: var(--forms-color-border-invalid, #ea302d);
}
.mc-pincode-input {
  display: inline-flex;
  box-sizing: border-box;
}
.mc-pincode-input__control {
  font-size: var(--font-size-400, 1.75rem);
  box-sizing: border-box;
  text-align: center;
  width: 3rem;
  height: 100%;
  padding: 0;
  border: var(--border-width-s, 0.0625rem) solid transparent;
  border-right: var(--border-width-s, 0.0625rem) solid var(--forms-color-border-default, #666666);
  background: transparent;
  color: var(--forms-color-text-default, #000000);
}
.mc-pincode-input__control:first-child {
  border-radius: var(--border-radius-xs, 0.125rem) 0 0 var(--border-radius-xs, 0.125rem);
}
.mc-pincode-input__control:last-child {
  border-right: none;
  border-radius: 0 var(--border-radius-xs, 0.125rem) var(--border-radius-xs, 0.125rem) 0;
}
.mc-pincode-input__control {
  line-height: var(--line-height-s, 1.3);
}
.mc-pincode-input__control::placeholder {
  color: var(--forms-color-placeholder, #666666);
}
.mc-pincode-input__control:hover {
  background-color: var(--forms-color-background-hover, rgba(70, 78, 99, 0.1));
}
.mc-pincode-input__control:focus {
  position: relative;
  z-index: 1;
  border: var(--border-width-s, 0.0625rem) solid var(--forms-color-border-default, #666666);
  border-radius: var(--border-radius-xs, 0.125rem);
  box-shadow: 0 0 0 0.125rem var(--focus-color-mid, var(--focus-color-outline-mid, #ffffff)), 0 0 0 0.25rem var(--focus-color-outer, var(--focus-color-outline-outer, #000000));
  outline: 0.125rem solid transparent;
  outline-offset: 0.125rem;
}
.mc-pincode-input__control:disabled {
  background-color: var(--forms-color-background-disabled, #d9d9d9);
  cursor: not-allowed;
  border-color: transparent;
  box-shadow: none;
  color: var(--forms-color-text-disabled, #737373);
  pointer-events: none;
}
.mc-pincode-input__control:read-only {
  border: 0;
  pointer-events: none;
}
.mc-pincode-input__readonly {
  background-color: var(--forms-color-background-disabled, #d9d9d9);
  cursor: not-allowed;
  border-color: transparent;
  box-shadow: none;
  color: var(--forms-color-text-disabled, #737373);
  padding: 0;
  border: var(--border-width-s, 0.0625rem) solid var(--forms-color-border-read-only, #cccccc);
  pointer-events: none;
}
.mc-pincode-input__disabled {
  background-color: var(--forms-color-background-disabled, #d9d9d9);
  cursor: not-allowed;
  border-color: transparent;
  box-shadow: none;
  color: var(--forms-color-text-disabled, #737373);
  padding: 0;
  border: none;
  pointer-events: none;
}</style>
