import { useEffect, useMemo, useRef, useState } from 'react';
import { SelectField, type SelectFieldOption } from '../core/SelectField';
import { useZino } from '../../api/provider';
import type { FieldRule } from '../../api/types';

export interface DatasetSelectFieldProps {
  label?: string;
  required?: boolean;
  value: string;
  onChange: (v: string) => void;
  activityId: string;
  fieldId: string;
  instanceId?: number | string;
  /** Current form values — sent to /dataset-options so the server applies the
   *  activity's filter_options (e.g. City options scoped to the chosen State). */
  formData?: Record<string, unknown>;
  /** Activity field_rules — used to find which fields this one depends on, so we
   *  only refetch when a dependency value changes (not on every keystroke). */
  fieldRules?: FieldRule[];
  placeholder?: string;
}

/** The form fields whose values drive this field's filter_options. */
function depFieldsFor(rules: FieldRule[] | undefined, fieldId: string): string[] {
  const out = new Set<string>();
  for (const r of rules ?? []) {
    if (r.action !== 'filter_options' || r.active === false || r.target_field !== fieldId) continue;
    for (const c of r.filter_config?.conditions ?? []) {
      if (c.form_field) out.add(c.form_field);
    }
  }
  return [...out];
}

/** Dataset-backed single-select. Fetches options from /dataset-options (the
 *  server applies the activity's filter_options against the dependency values we
 *  send), refetching when a dependency changes, and renders the shared
 *  SelectField so it looks identical to a static select. When a dependency
 *  changes and the current selection is no longer valid, it clears it. */
export function DatasetSelectField({
  label,
  required,
  value,
  onChange,
  activityId,
  fieldId,
  instanceId,
  formData,
  fieldRules,
  placeholder,
}: DatasetSelectFieldProps) {
  const { client } = useZino();
  const [options, setOptions] = useState<SelectFieldOption[]>([]);

  const deps = useMemo(() => depFieldsFor(fieldRules, fieldId), [fieldRules, fieldId]);
  // Stable key over the dependency VALUES — refetch only when these change.
  const depKey = useMemo(
    () => deps.map((f) => `${f}=${JSON.stringify(formData?.[f] ?? null)}`).join('&'),
    [deps, formData],
  );
  // Send only the dependency values — enough to resolve filter_options server-side.
  const depData = useMemo(() => {
    const fd: Record<string, unknown> = {};
    for (const f of deps) fd[f] = formData?.[f];
    return fd;
  }, [deps, formData]);

  // Refs so stale-selection cleanup reads the latest value/onChange without
  // re-running the fetch effect (which keys only on depKey).
  const onChangeRef = useRef(onChange);
  onChangeRef.current = onChange;
  const valueRef = useRef(value);
  valueRef.current = value;
  const firstDepKey = useRef(depKey);

  useEffect(() => {
    let live = true;
    client
      .datasetOptions({ activityId, fieldId, instanceId, limit: 200, formData: depData })
      .then((r) => {
        if (!live) return;
        const opts = (r.options ?? []).map((o) => ({ value: String(o.value), label: o.label }));
        setOptions(opts);
        // After a dependency actually CHANGED, drop a now-invalid selection
        // (skip the initial load so a prefilled value isn't cleared).
        if (depKey !== firstDepKey.current) {
          const v = valueRef.current;
          if (v && !opts.some((o) => o.value === String(v))) onChangeRef.current('');
        }
      })
      .catch(() => {
        if (live) setOptions([]);
      });
    return () => {
      live = false;
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [client, activityId, fieldId, instanceId, depKey]);

  return (
    <SelectField
      label={label}
      required={required}
      value={String(value ?? '')}
      onChange={onChange}
      options={options}
      placeholder={placeholder}
    />
  );
}
