/**
 * AjaxSelect Component
 *
 * A searchable select that fetches options from REST API
 *
 * @package ArraySubs
 */

import React, { useState, useEffect, useRef, useCallback } from "react";
import { __ } from "@wordpress/i18n";
import { ChevronDown, X, Search, Loader2 } from "lucide-react";
import { buildRestUrl } from "@/libs/url";

/**
 * AjaxSelect Component
 *
 * @param {Object} props Component props
 * @param {*} props.value Current value (array for multiple, string/number for single)
 * @param {Function} props.onChange Callback when value changes
 * @param {string} props.endpoint REST API endpoint for fetching options
 * @param {boolean} props.multiple Whether multiple selection is allowed
 * @param {string} props.placeholder Placeholder text
 * @param {*} props.dependsOnValue Value of the field this depends on
 */
const AjaxSelect = ({
  value,
  onChange,
  endpoint,
  multiple = false,
  placeholder = __("Select...", "arraysubs"),
  dependsOnValue = null,
}) => {
  const { env } = window.arraySubs || {};

  const [isOpen, setIsOpen] = useState(false);
  const [searchTerm, setSearchTerm] = useState("");
  const [options, setOptions] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [cachedOptions, setCachedOptions] = useState({});

  const wrapperRef = useRef(null);
  const searchInputRef = useRef(null);
  const abortControllerRef = useRef(null);

  /**
   * Normalize endpoint config into route type + query params.
   */
  const getEndpointConfig = useCallback(() => {
    const [endpointType = "", endpointQuery = ""] = String(
      endpoint || "",
    ).split("?");
    const params = Object.fromEntries(
      new URLSearchParams(endpointQuery).entries(),
    );

    if (
      dependsOnValue !== null &&
      dependsOnValue !== undefined &&
      dependsOnValue !== ""
    ) {
      if (endpointType === "terms") {
        params.taxonomy = dependsOnValue;
      } else if (endpointType === "variations") {
        params.product_id = dependsOnValue;
      } else {
        params.depends_on = dependsOnValue;
      }
    }

    if (searchTerm.trim()) {
      params.search = searchTerm.trim();
    }

    return {
      endpointType,
      params,
    };
  }, [dependsOnValue, endpoint, searchTerm]);

  /**
   * Generate cache key based on endpoint and dependency
   */
  const getCacheKey = useCallback(() => {
    const { endpointType, params } = getEndpointConfig();
    const paramString = new URLSearchParams(params).toString();

    return `${endpointType}:${paramString || "none"}`;
  }, [getEndpointConfig]);

  /**
   * Fetch options from API
   */
  const fetchOptions = useCallback(async () => {
    const { endpointType, params } = getEndpointConfig();

    if (!endpointType) {
      setOptions([]);
      return;
    }

    const cacheKey = getCacheKey();

    // Check cache first
    if (cachedOptions[cacheKey]) {
      setOptions(cachedOptions[cacheKey]);
      return;
    }

    // Cancel previous request
    if (abortControllerRef.current) {
      abortControllerRef.current.abort();
    }

    abortControllerRef.current = new AbortController();

    setLoading(true);
    setError(null);

    try {
      const url = buildRestUrl(
        env?.apiBaseUrl || "/wp-json",
        "arraysubs/v1/members-access/select-options",
        {
          type: endpointType,
          ...params,
        },
      );

      const response = await fetch(url, {
        headers: {
          "X-WP-Nonce": env?.nonce,
        },
        signal: abortControllerRef.current.signal,
      });

      if (!response.ok) {
        throw new Error(__("Failed to load options", "arraysubs"));
      }

      const data = await response.json();
      const fetchedOptions = data.options || data.data || data || [];

      // Normalize options format
      const normalizedOptions = fetchedOptions.map((opt) =>
        typeof opt === "object"
          ? {
              value: opt.value || opt.id,
              label: opt.label || opt.name || opt.title,
            }
          : { value: opt, label: opt },
      );

      setOptions(normalizedOptions);

      // Cache results
      setCachedOptions((prev) => ({
        ...prev,
        [cacheKey]: normalizedOptions,
      }));
    } catch (err) {
      if (err.name !== "AbortError") {
        setError(err.message);
        setOptions([]);
      }
    } finally {
      setLoading(false);
    }
  }, [cachedOptions, env, getCacheKey, getEndpointConfig]);

  /**
   * Load options when opening, searching, or dependency changes
   */
  useEffect(() => {
    if (!isOpen) {
      return undefined;
    }

    const timeoutId = window.setTimeout(() => {
      fetchOptions();
    }, 250);

    return () => window.clearTimeout(timeoutId);
  }, [fetchOptions, isOpen, searchTerm, dependsOnValue]);

  /**
   * Clear value when dependency changes
   */
  useEffect(() => {
    if (dependsOnValue !== null && dependsOnValue !== undefined) {
      // Reset cache for this endpoint when dependency changes
      const cacheKey = getCacheKey();
      if (!cachedOptions[cacheKey]) {
        if (multiple) {
          onChange([]);
        } else {
          onChange("");
        }
      }
    }
  }, [dependsOnValue]);

  /**
   * Handle click outside to close dropdown
   */
  useEffect(() => {
    const handleClickOutside = (event) => {
      if (wrapperRef.current && !wrapperRef.current.contains(event.target)) {
        setIsOpen(false);
        setSearchTerm("");
      }
    };

    document.addEventListener("mousedown", handleClickOutside);
    return () => document.removeEventListener("mousedown", handleClickOutside);
  }, []);

  /**
   * Focus search input when opening
   */
  useEffect(() => {
    if (isOpen && searchInputRef.current) {
      searchInputRef.current.focus();
    }
  }, [isOpen]);

  /**
   * Filter options based on search term
   */
  const filteredOptions = options.filter((opt) =>
    opt.label.toLowerCase().includes(searchTerm.toLowerCase()),
  );

  /**
   * Get display label for a value
   */
  const getLabel = (val) => {
    const opt = options.find((o) => String(o.value) === String(val));
    return opt?.label || val;
  };

  /**
   * Handle option selection
   */
  const handleSelect = (optValue) => {
    if (multiple) {
      const currentValues = Array.isArray(value) ? value : [];
      const valueStr = String(optValue);

      if (
        currentValues.includes(valueStr) ||
        currentValues.includes(optValue)
      ) {
        // Remove if already selected
        onChange(currentValues.filter((v) => String(v) !== valueStr));
      } else {
        // Add to selection
        onChange([...currentValues, optValue]);
      }
    } else {
      onChange(optValue);
      setIsOpen(false);
      setSearchTerm("");
    }
  };

  /**
   * Remove a selected value (multiple mode)
   */
  const handleRemove = (valToRemove, e) => {
    e.stopPropagation();
    if (multiple && Array.isArray(value)) {
      onChange(value.filter((v) => String(v) !== String(valToRemove)));
    }
  };

  /**
   * Clear all selections
   */
  const handleClear = (e) => {
    e.stopPropagation();
    onChange(multiple ? [] : "");
    setIsOpen(false);
  };

  /**
   * Check if a value is selected
   */
  const isSelected = (optValue) => {
    if (multiple) {
      const currentValues = Array.isArray(value) ? value : [];
      return currentValues.some((v) => String(v) === String(optValue));
    }
    return String(value) === String(optValue);
  };

  /**
   * Check if there's any value selected
   */
  const hasValue = multiple
    ? Array.isArray(value) && value.length > 0
    : value !== "" && value !== null && value !== undefined;

  return (
    <div
      ref={wrapperRef}
      className={`arraysubs-ajax-select ${
        isOpen ? "arraysubs-ajax-select--open" : ""
      }`}
    >
      {/* Selected Value Display */}
      <div
        className="arraysubs-ajax-select__trigger"
        onClick={() => setIsOpen(!isOpen)}
      >
        <div className="arraysubs-ajax-select__value">
          {multiple && Array.isArray(value) && value.length > 0 ? (
            <div className="arraysubs-ajax-select__tags">
              {value.map((v) => (
                <span key={v} className="arraysubs-ajax-select__tag">
                  {getLabel(v)}
                  <button
                    type="button"
                    className="arraysubs-ajax-select__tag-remove"
                    onClick={(e) => handleRemove(v, e)}
                  >
                    <X size={12} />
                  </button>
                </span>
              ))}
            </div>
          ) : !multiple && hasValue ? (
            <span className="arraysubs-ajax-select__single-value">
              {getLabel(value)}
            </span>
          ) : (
            <span className="arraysubs-ajax-select__placeholder">
              {placeholder}
            </span>
          )}
        </div>

        <div className="arraysubs-ajax-select__indicators">
          {hasValue && (
            <button
              type="button"
              className="arraysubs-ajax-select__clear"
              onClick={handleClear}
            >
              <X size={14} />
            </button>
          )}
          <ChevronDown
            size={16}
            className={`arraysubs-ajax-select__chevron ${
              isOpen ? "arraysubs-ajax-select__chevron--open" : ""
            }`}
          />
        </div>
      </div>

      {/* Dropdown */}
      {isOpen && (
        <div className="arraysubs-ajax-select__dropdown">
          {/* Search Input */}
          <div className="arraysubs-ajax-select__search">
            <Search size={14} className="arraysubs-ajax-select__search-icon" />
            <input
              ref={searchInputRef}
              type="text"
              value={searchTerm}
              onChange={(e) => setSearchTerm(e.target.value)}
              placeholder={__("Search...", "arraysubs")}
              className="arraysubs-ajax-select__search-input"
            />
          </div>

          {/* Options List */}
          <div className="arraysubs-ajax-select__options">
            {loading ? (
              <div className="arraysubs-ajax-select__loading">
                <Loader2 size={16} className="arraysubs-ajax-select__spinner" />
                {__("Loading...", "arraysubs")}
              </div>
            ) : error ? (
              <div className="arraysubs-ajax-select__error">{error}</div>
            ) : filteredOptions.length === 0 ? (
              <div className="arraysubs-ajax-select__empty">
                {searchTerm
                  ? __("No matching options", "arraysubs")
                  : __("No options available", "arraysubs")}
              </div>
            ) : (
              filteredOptions.map((opt) => (
                <div
                  key={opt.value}
                  className={`arraysubs-ajax-select__option ${
                    isSelected(opt.value)
                      ? "arraysubs-ajax-select__option--selected"
                      : ""
                  }`}
                  onClick={() => handleSelect(opt.value)}
                >
                  {multiple && (
                    <span className="arraysubs-ajax-select__checkbox">
                      {isSelected(opt.value) && "✓"}
                    </span>
                  )}
                  <span className="arraysubs-ajax-select__option-label">
                    {opt.label}
                  </span>
                </div>
              ))
            )}
          </div>
        </div>
      )}
    </div>
  );
};

export default AjaxSelect;
