/* Starboard · Fields family
   Field/Fieldset, Input, Textarea, InputGroup (+InputAddon),
   NativeSelect, InputOTP, Combobox.
   Load with: <script type="text/babel" src="Fields.jsx"></script>
   Requires: React 18, fields.css, a11y.js, position.js,
   Primitives.jsx (Label) + primitives.css (.ad-label, .ad-scroll). */

// ─── Fieldset — focal-glow scope ───
// Wrap a logical group of Fields. While one control holds focus,
// sibling labels dim to --opacity-dimmed-sibling (pure CSS,
// .ad-fieldset:focus-within). Scope is one group, never the page.
const Fieldset = ({ columns = 1, ariaLabel, children, className = '' }) => (
  <div
    className={`ad-fieldset${columns === 2 ? ' ad-fieldset--2col' : ''} ${className}`}
    role="group"
    aria-label={ariaLabel}
  >
    {children}
  </div>
);

// ─── Field — label + control + help / error wiring ───
const Field = ({ label, help, error, required, disabled, children, id: idProp, className = '' }) => {
  const autoId = adUseId('fld');
  const id = idProp || autoId;
  const helpId = `${id}-help`;
  const errId = `${id}-err`;

  const child = React.Children.only(children);
  const describedBy = [error ? errId : null, !error && help ? helpId : null]
    .filter(Boolean).join(' ') || undefined;
  const control = React.cloneElement(child, {
    id,
    disabled: disabled || child.props.disabled,
    error: error ? true : child.props.error,
    'aria-describedby': describedBy,
  });

  return (
    <div className={`ad-field${disabled ? ' is-disabled' : ''} ${className}`}>
      {label && <Label htmlFor={id} required={required} disabled={disabled}>{label}</Label>}
      {control}
      {error ? (
        <span className="ad-field__error" id={errId} role="alert">
          <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true">
            <circle cx="12" cy="12" r="9" /><path d="M12 7.5v5.5" /><circle cx="12" cy="16.4" r="0.9" fill="currentColor" stroke="none" />
          </svg>
          {error}
        </span>
      ) : help ? (
        <span className="ad-field__help" id={helpId}>{help}</span>
      ) : null}
    </div>
  );
};

// ─── Input ───
const Input = ({
  size = 'md', error, success, disabled, readOnly,
  leadingIcon, trailingIcon, className = '', ...rest
}) => {
  const cls = [
    'ad-input',
    size === 'sm' && 'ad-input--sm',
    error && 'is-error',
    success && 'is-success',
    disabled && 'is-disabled',
    readOnly && 'is-readonly',
    className,
  ].filter(Boolean).join(' ');
  return (
    <div className={cls}>
      {leadingIcon && <span className="ad-input__icon" aria-hidden="true">{leadingIcon}</span>}
      <input
        className="ad-input__control"
        disabled={disabled}
        readOnly={readOnly}
        aria-invalid={error || undefined}
        {...rest}
      />
      {trailingIcon && <span className="ad-input__icon" aria-hidden="true">{trailingIcon}</span>}
    </div>
  );
};

// ─── Textarea — autoGrow + char counter ───
const Textarea = ({
  error, success, disabled, readOnly, autoGrow, maxLength, rows = 3,
  value, defaultValue, onChange, className = '', ...rest
}) => {
  const ref = React.useRef(null);
  const [count, setCount] = React.useState(String(value ?? defaultValue ?? '').length);

  const grow = () => {
    const el = ref.current;
    if (!autoGrow || !el) return;
    el.style.height = 'auto';
    el.style.height = `${el.scrollHeight}px`;
  };
  React.useEffect(grow, []);

  const handleChange = (e) => {
    setCount(e.target.value.length);
    grow();
    if (onChange) onChange(e);
  };

  const counted = maxLength != null;
  const warn = counted && count >= Math.ceil(maxLength * 0.9);
  const cls = [
    'ad-input', 'ad-textarea',
    counted && 'ad-textarea--counted',
    autoGrow && 'is-autogrow',
    error && 'is-error',
    success && 'is-success',
    disabled && 'is-disabled',
    readOnly && 'is-readonly',
    className,
  ].filter(Boolean).join(' ');

  return (
    <div className={cls}>
      <textarea
        ref={ref}
        className="ad-input__control ad-textarea__control"
        rows={rows}
        maxLength={maxLength}
        disabled={disabled}
        readOnly={readOnly}
        aria-invalid={error || undefined}
        value={value}
        defaultValue={defaultValue}
        onChange={handleChange}
        {...rest}
      />
      {counted && (
        <span className={`ad-textarea__count${warn ? ' is-warn' : ''}`} aria-hidden="true">
          {count}/{maxLength}
        </span>
      )}
    </div>
  );
};

// ─── InputGroup — compound adornments; whole group glows ───
// Compose: <InputGroup><InputAddon>https://</InputAddon><input …/>
//          <select …/> or <Button …/></InputGroup>
const InputGroup = ({ size = 'md', error, disabled, children, className = '' }) => (
  <div
    className={[
      'ad-igrp',
      size === 'sm' && 'ad-igrp--sm',
      error && 'is-error',
      disabled && 'is-disabled',
      className,
    ].filter(Boolean).join(' ')}
  >
    {children}
  </div>
);

const InputAddon = ({ mono, children }) => (
  <span className={`ad-igrp__addon${mono ? ' ad-igrp__addon--mono' : ''}`}>{children}</span>
);

// ─── NativeSelect — styled native <select>, inline SVG chevron ───
const NativeSelect = ({
  size = 'md', error, success, disabled, children, className = '', ...rest
}) => (
  <div
    className={[
      'ad-nsel',
      size === 'sm' && 'ad-nsel--sm',
      error && 'is-error',
      success && 'is-success',
      disabled && 'is-disabled',
      className,
    ].filter(Boolean).join(' ')}
  >
    <select
      className="ad-nsel__control"
      disabled={disabled}
      aria-invalid={error || undefined}
      {...rest}
    >
      {children}
    </select>
    <span className="ad-nsel__chev" aria-hidden="true">
      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m6 9 6 6 6-6" /></svg>
    </span>
  </div>
);

// ─── InputOTP — segmented one-time-code entry ───
const InputOTP = ({
  length = 6, groups, value, onChange, onComplete,
  name, disabled, error, ariaLabel = 'One-time code', className = '',
}) => {
  const [cells, setCells] = React.useState(() =>
    Array.from({ length }, (_, i) => (value || '')[i] || '')
  );
  const refs = React.useRef([]);
  const joined = cells.join('');

  const commit = (next) => {
    setCells(next);
    const str = next.join('');
    if (onChange) onChange(str);
    if (str.length === length && next.every(Boolean) && onComplete) onComplete(str);
  };

  const fillFrom = (start, digits) => {
    if (!digits.length) return;
    const next = cells.slice();
    let i = start;
    for (const d of digits) {
      if (i >= length) break;
      next[i] = d;
      i += 1;
    }
    commit(next);
    const focusAt = Math.min(i, length - 1);
    const el = refs.current[focusAt];
    if (el) el.focus();
  };

  const handleInput = (i, e) => {
    const digits = e.target.value.replace(/\D/g, '').split('');
    if (!digits.length) {
      const next = cells.slice();
      next[i] = '';
      commit(next);
      return;
    }
    // Last typed digit wins for single entry; longer strings (autofill)
    // distribute forward from this cell.
    fillFrom(i, digits.length > 1 ? digits : [digits[digits.length - 1]]);
  };

  const handleKeyDown = (i, e) => {
    if (e.key === 'Backspace') {
      e.preventDefault();
      const next = cells.slice();
      if (cells[i]) {
        next[i] = '';
        commit(next);
      } else if (i > 0) {
        next[i - 1] = '';
        commit(next);
        const el = refs.current[i - 1];
        if (el) el.focus();
      }
    } else if (e.key === 'ArrowLeft' && i > 0) {
      e.preventDefault();
      refs.current[i - 1].focus();
    } else if (e.key === 'ArrowRight' && i < length - 1) {
      e.preventDefault();
      refs.current[i + 1].focus();
    } else if (e.key === 'Home') {
      e.preventDefault();
      refs.current[0].focus();
    } else if (e.key === 'End') {
      e.preventDefault();
      refs.current[length - 1].focus();
    }
  };

  const handlePaste = (e) => {
    e.preventDefault();
    const digits = (e.clipboardData.getData('text') || '').replace(/\D/g, '').split('');
    fillFrom(0, digits.slice(0, length));
  };

  const nodes = [];
  for (let i = 0; i < length; i += 1) {
    if (groups && i > 0 && i % groups === 0) {
      nodes.push(<span key={`dot-${i}`} className="ad-otp__dot" aria-hidden="true" />);
    }
    nodes.push(
      <input
        key={i}
        ref={(el) => { refs.current[i] = el; }}
        className={`ad-otp__cell${cells[i] ? ' is-filled' : ''}`}
        type="text"
        inputMode="numeric"
        autoComplete={i === 0 ? 'one-time-code' : 'off'}
        pattern="[0-9]*"
        value={cells[i]}
        disabled={disabled}
        aria-label={`Digit ${i + 1} of ${length}`}
        aria-invalid={error || undefined}
        onChange={(e) => handleInput(i, e)}
        onKeyDown={(e) => handleKeyDown(i, e)}
        onPaste={handlePaste}
        onFocus={(e) => e.target.select()}
      />
    );
  }

  return (
    <div
      className={`ad-otp${error ? ' is-error' : ''}${disabled ? ' is-disabled' : ''} ${className}`}
      role="group"
      aria-label={ariaLabel}
    >
      {nodes}
      {name && <input type="hidden" name={name} value={joined} />}
    </div>
  );
};

// ─── Combobox — APG combobox + listbox, single or multi-select ───
// options: string[] or [{ value, label, meta }]
const Combobox = ({
  options = [], value, onChange, multiple,
  placeholder = 'Search…', size = 'md', disabled,
  ariaLabel, id: idProp, className = '', ...rest
}) => {
  const opts = options.map((o) => (typeof o === 'string' ? { value: o, label: o } : o));
  const autoId = adUseId('cbx');
  const baseId = idProp || autoId;
  const listId = `${baseId}-list`;

  const rootRef = React.useRef(null);
  const anchorRef = React.useRef(null);
  const floatRef = React.useRef(null);
  const inputRef = React.useRef(null);

  const [open, setOpen] = React.useState(false);
  const [query, setQuery] = React.useState(() => {
    if (multiple || value == null) return '';
    const sel = opts.find((o) => o.value === value);
    return sel ? sel.label : '';
  });
  const [active, setActive] = React.useState(0);

  const selected = multiple ? (value || []) : value;
  const q = query.trim().toLowerCase();
  const filtered = opts.filter((o) => !q || o.label.toLowerCase().includes(q));

  const { style } = adUseAnchorPosition(anchorRef, floatRef, open, { placement: 'bottom-start', offset: 6 });
  adUseDismiss(rootRef, open, () => setOpen(false));

  const announceResults = (list) =>
    adAnnounce(list.length === 0 ? 'No matches' : `${list.length} result${list.length === 1 ? '' : 's'} available`);

  const openList = () => {
    if (open || disabled) return;
    setOpen(true);
    setActive(0);
    announceResults(filtered);
  };

  const labelOf = (v) => {
    const o = opts.find((x) => x.value === v);
    return o ? o.label : String(v);
  };

  const commitOption = (opt) => {
    if (!opt) return;
    if (multiple) {
      const has = selected.includes(opt.value);
      if (onChange) onChange(has ? selected.filter((v) => v !== opt.value) : [...selected, opt.value]);
      setQuery('');
      adAnnounce(`${opt.label} ${has ? 'removed' : 'added'}`);
      if (inputRef.current) inputRef.current.focus();
    } else {
      if (onChange) onChange(opt.value);
      setQuery(opt.label);
      setOpen(false);
      adAnnounce(`${opt.label} selected`);
    }
  };

  const handleKeyDown = (e) => {
    if (e.key === 'ArrowDown') {
      e.preventDefault();
      if (!open) openList();
      else setActive((i) => Math.min(filtered.length - 1, i + 1));
    } else if (e.key === 'ArrowUp') {
      e.preventDefault();
      if (!open) openList();
      else setActive((i) => Math.max(0, i - 1));
    } else if (e.key === 'Enter') {
      if (open) {
        e.preventDefault();
        commitOption(filtered[active]);
      }
    } else if (e.key === 'Escape') {
      if (open) {
        e.preventDefault();
        setOpen(false);
      }
    } else if (e.key === 'Backspace' && multiple && query === '' && selected.length) {
      if (onChange) onChange(selected.slice(0, -1));
      adAnnounce(`${labelOf(selected[selected.length - 1])} removed`);
    }
  };

  const handleInput = (e) => {
    const next = e.target.value;
    setQuery(next);
    setActive(0);
    if (!open) setOpen(true);
    const nq = next.trim().toLowerCase();
    announceResults(opts.filter((o) => !nq || o.label.toLowerCase().includes(nq)));
  };

  // Highlight the matched substring — primary-700, weight 600
  const highlight = (text) => {
    if (!q) return text;
    const i = text.toLowerCase().indexOf(q);
    if (i < 0) return text;
    return (
      <React.Fragment>
        {text.slice(0, i)}
        <mark className="ad-cbx__hl">{text.slice(i, i + q.length)}</mark>
        {text.slice(i + q.length)}
      </React.Fragment>
    );
  };

  const anchorCls = [
    'ad-input',
    size === 'sm' && 'ad-input--sm',
    'ad-cbx__anchor',
    multiple && selected.length > 0 && 'ad-cbx__anchor--chips',
    disabled && 'is-disabled',
  ].filter(Boolean).join(' ');

  return (
    <div className={`ad-cbx${open ? ' is-open' : ''} ${className}`} ref={rootRef}>
      <div
        className={anchorCls}
        ref={anchorRef}
        onClick={() => { if (!disabled) { openList(); if (inputRef.current) inputRef.current.focus(); } }}
      >
        {multiple && selected.map((v) => (
          <span className="ad-chip" key={v}>
            {labelOf(v)}
            <button
              type="button"
              className="ad-chip__x"
              aria-label={`Remove ${labelOf(v)}`}
              disabled={disabled}
              onClick={(e) => {
                e.stopPropagation();
                if (onChange) onChange(selected.filter((x) => x !== v));
                adAnnounce(`${labelOf(v)} removed`);
              }}
            >
              <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6 6 18" /></svg>
            </button>
          </span>
        ))}
        <input
          ref={inputRef}
          id={baseId}
          className="ad-input__control"
          role="combobox"
          aria-expanded={open}
          aria-controls={listId}
          aria-autocomplete="list"
          aria-activedescendant={open && filtered.length ? `${baseId}-opt-${active}` : undefined}
          aria-label={ariaLabel}
          placeholder={multiple && selected.length ? '' : placeholder}
          value={query}
          disabled={disabled}
          onChange={handleInput}
          onKeyDown={handleKeyDown}
          {...rest}
        />
        <span className="ad-input__icon ad-cbx__chev" aria-hidden="true">
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m6 9 6 6 6-6" /></svg>
        </span>
      </div>

      {open && (
        <div className="ad-cbx__pop" ref={floatRef} style={style}>
          <div className="ad-scroll" style={{ maxHeight: 280 }}>
            <ul className="ad-cbx__list" role="listbox" id={listId} aria-multiselectable={multiple || undefined}>
              {filtered.length === 0 && (
                <li className="ad-cbx__empty" role="presentation">No matches for “{query}”.</li>
              )}
              {filtered.map((o, i) => {
                const isSel = multiple ? selected.includes(o.value) : selected === o.value;
                return (
                  <li
                    key={o.value}
                    id={`${baseId}-opt-${i}`}
                    role="option"
                    aria-selected={isSel}
                    className={`ad-cbx__opt${i === active ? ' is-active' : ''}`}
                    onMouseEnter={() => setActive(i)}
                    onMouseDown={(e) => e.preventDefault()}
                    onClick={() => commitOption(o)}
                  >
                    <span className="ad-cbx__opt-label">{highlight(o.label)}</span>
                    {o.meta && <span className="ad-cbx__opt-meta">{o.meta}</span>}
                    {isSel && (
                      <span className="ad-cbx__check" aria-hidden="true">
                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="m5 12 5 5 9-11" /></svg>
                      </span>
                    )}
                  </li>
                );
              })}
            </ul>
          </div>
        </div>
      )}
    </div>
  );
};

// Export
Object.assign(window, {
  Fieldset, Field, Input, Textarea, InputGroup, InputAddon,
  NativeSelect, InputOTP, Combobox,
});
