import { useEffect, useRef, useState } from 'react';
import { parseSecretValue } from '../../api';

interface ModelSelectProps {
  /** Currently saved model name. Always shown even if discovery fails. */
  value: string;
  onChange: (model: string) => void;
  /** LLM endpoint to probe `<endpoint>/models` against. */
  endpoint: string | undefined;
  /**
   * Raw `apiKey` string from the draft config. Used to attach a Bearer
   * token to the discovery request when it's a literal secret. Masked
   * / env_ref values cannot be used for direct discovery in Phase 1
   * (the actual literal is not exposed to the browser), and we fall
   * back to manual input in that case.
   */
  apiKeyRaw: string | null | undefined;
}

/**
 * Endpoint + apiKey -aware model dropdown with a manual-input fallback.
 *
 * Behaviour:
 *   - Probes `<endpoint>/models` once whenever endpoint / apiKey
 *     identity changes.
 *   - Success → renders a searchable dropdown of returned ids; the
 *     currently saved `value` is always included even if discovery
 *     dropped it (so a typo doesn't silently overwrite the choice).
 *   - Failure (network error, non-2xx, malformed body) → renders a
 *     plain text input and shows an inline amber warning suggesting
 *     manual entry.
 *   - apiKey is `unchanged` / `env_ref` / `cleared` → also falls back
 *     to manual input. Probing with the masked sentinel would 401 and
 *     leak nothing useful.
 *
 * The component is deliberately self-contained: the parent passes the
 * current draft endpoint+apiKey and the new model name flows back via
 * `onChange`. No global state, no caching across remounts — discovery
 * latency is short enough (< 1s typically) that re-probing on every
 * mount is fine, and avoids stale dropdowns if the endpoint changed.
 */
export function ModelSelect({ value, onChange, endpoint, apiKeyRaw }: ModelSelectProps) {
  const [models, setModels] = useState<string[] | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  // Track the in-flight probe so a fast endpoint edit cancels the
  // previous one instead of racing the latest write.
  const probeIdRef = useRef(0);

  useEffect(() => {
    if (!endpoint || endpoint.trim() === '') {
      setModels(null);
      setError(null);
      return;
    }
    const parsed = parseSecretValue(apiKeyRaw);
    // Phase 1: only `literal` keys can be used for direct discovery
    // from the browser. For `env_ref` / `unchanged` the literal is
    // server-side only — manual fallback. For `cleared` we attempt
    // discovery without an Authorization header (works for Ollama).
    const bearer =
      parsed.type === 'literal' ? parsed.value
      : parsed.type === 'cleared' ? undefined
      : null;  // null = skip discovery
    if (bearer === null) {
      setModels(null);
      setError('API key is masked or env-ref; please enter the model name manually.');
      return;
    }
    const probeId = ++probeIdRef.current;
    setLoading(true);
    setError(null);
    const trimmed = endpoint.replace(/\/+$/, '');
    const url = `${trimmed}/models`;
    const headers: Record<string, string> = { Accept: 'application/json' };
    if (bearer) headers.Authorization = `Bearer ${bearer}`;
    fetch(url, { headers })
      .then(async res => {
        if (probeId !== probeIdRef.current) return;  // stale
        if (!res.ok) {
          setModels(null);
          setError(`model discovery failed (HTTP ${res.status}); please enter manually.`);
          return;
        }
        const body = await res.json().catch(() => null) as { data?: Array<{ id?: unknown }> } | null;
        if (!body || !Array.isArray(body.data)) {
          setModels(null);
          setError('model discovery returned an unexpected payload; please enter manually.');
          return;
        }
        const ids = body.data
          .map(m => (typeof m?.id === 'string' ? m.id.trim() : ''))
          .filter(id => id.length > 0);
        // Surface the discovered set even if empty — distinguishes
        // "endpoint reachable, no models loaded" from "endpoint down".
        setModels(Array.from(new Set(ids)));
        setError(null);
      })
      .catch(err => {
        if (probeId !== probeIdRef.current) return;
        setModels(null);
        setError(
          `model discovery failed (${err instanceof Error ? err.message : 'network error'}); ` +
          'please enter manually.',
        );
      })
      .finally(() => {
        if (probeId === probeIdRef.current) setLoading(false);
      });
  }, [endpoint, apiKeyRaw]);

  // Discovery succeeded — render a datalist-backed combobox so the user
  // can either pick or override. A native datalist is the simplest way
  // to get "dropdown with manual fallback" without a custom popover.
  if (models !== null) {
    const options = value && !models.includes(value) ? [value, ...models] : models;
    return (
      <div>
        <input
          list="llm-workers-model-options"
          value={value}
          onChange={e => onChange(e.target.value)}
          placeholder={loading ? 'loading...' : 'choose or type a model'}
          className="w-full h-8 px-2.5 text-[13px] border border-hairline rounded-md focus:ring-2 focus:ring-accent-ring focus:border-accent outline-none bg-white"
        />
        <datalist id="llm-workers-model-options">
          {options.map(m => <option key={m} value={m} />)}
        </datalist>
        {options.length === 0 && (
          <p className="text-2xs text-slate-500 mt-1">
            endpoint reachable but no models reported.
          </p>
        )}
      </div>
    );
  }

  // Manual fallback (discovery failed or skipped).
  return (
    <div>
      <input
        type="text"
        value={value}
        onChange={e => onChange(e.target.value)}
        placeholder={loading ? 'loading...' : 'qwen3:8b'}
        className="w-full h-8 px-2.5 text-[13px] border border-hairline rounded-md focus:ring-2 focus:ring-accent-ring focus:border-accent outline-none bg-white"
      />
      {error && (
        <p className="text-2xs text-amber-700 bg-amber-50 border border-amber-100 px-2 py-1 rounded mt-1">
          {error}
        </p>
      )}
    </div>
  );
}
