<!-- #region ProviderConfig [C:3] [TYPE Component] [SEMANTICS llm, provider, config, form, crud] -->
<!-- @BRIEF Component component: components/llm/ProviderConfig.svelte -->
<!-- @LAYER UI -->
<!--
@UX_STATE: Loading -> Default
 
@PURPOSE: UI form for managing LLM provider configurations.
@LAYER UI
@RELATION DEPENDS_ON -> [EXT:frontend:requestApi]
-->

<script lang="ts">
  import { t } from '$lib/i18n/index.svelte.js';
  import { requestApi } from "../../lib/api";
  import { addToast } from "$lib/toasts.svelte.js";
  import HelpTooltip from "$lib/ui/HelpTooltip.svelte";

  let {
    providers = [],
    onSave = () => {},
  } = $props();

  let editingProvider = $state(null);
  let showForm = $state(false);

  const DEFAULT_BASE_URLS = {
    openai: "https://api.openai.com/v1",
    openrouter: "https://openrouter.ai/api/v1",
    kilo: "https://api.kilo.ai/api/gateway",
    litellm: "http://localhost:4000/v1",
  };

  let formData = $state({
    name: "",
    provider_type: "openai",
    base_url: "https://api.openai.com/v1",
    api_key: "",
    default_model: "gpt-4o",
    is_active: true,
    is_multimodal: false,
    max_images: null,
  });

  let testStatus = $state({ type: "", message: "" });
  let isTesting = $state(false);
  let togglingProviderIds = $state(new Set());
  let deletingProviderIds = $state(new Set());

  // Model fetching state
  let availableModels = $state([]);
  let isLoadingModels = $state(false);
  let modelsLoadError = $state("");

  let fetchDebounceTimer = $state(null);

  // Probing state
  let isProbing = $state(false);
  let probeResult = $state(null); // { max_images, method } or null
  let probeError = $state("");

  function isMaskedKey(key) {
    if (!key) return false;
    return key === "********" || key.includes("...");
  }

  function updateBaseUrlForType(providerType) {
    // Only auto-update base_url if user hasn't changed it from the default
    // or the base_url matches a previous default for a different type
    const currentUrl = formData.base_url;
    const isCurrentlyDefault = Object.values(DEFAULT_BASE_URLS).includes(currentUrl);
    if (isCurrentlyDefault || !currentUrl) {
      formData.base_url = DEFAULT_BASE_URLS[providerType] || currentUrl;
    }
  }

  function resetForm() {
    formData = {
      name: "",
      provider_type: "openai",
      base_url: "https://api.openai.com/v1",
      api_key: "",
      default_model: "gpt-4o",
      is_active: true,
      is_multimodal: false,
      max_images: null,
    };
    editingProvider = null;
    testStatus = { type: "", message: "" };
    availableModels = [];
    modelsLoadError = "";
    probeResult = null;
    probeError = "";
  }

  function handleEdit(provider) {
    console.log("[ProviderConfig][Action] Editing provider", provider?.id);
    editingProvider = provider;
    // Normalize provider fields to editable form shape.
    // The API key from the backend is a masked placeholder (e.g. "sk-...f8k3");
    // we preserve it in the form so the user can see which key is configured.
    // If the user modifies the field, the new value replaces the stored key.
    formData = {
      name: provider?.name ?? "",
      provider_type: provider?.provider_type ?? "openai",
      base_url: provider?.base_url ?? "https://api.openai.com/v1",
      api_key: provider?.api_key ?? "",
      default_model: provider?.default_model ?? "gpt-4o",
      is_active: Boolean(provider?.is_active),
      is_multimodal: Boolean(provider?.is_multimodal),
      max_images: provider?.max_images ?? null,
    };
    testStatus = { type: "", message: "" };
    availableModels = [];
    modelsLoadError = "";
    probeResult = null;
    probeError = "";
    showForm = true;
    // Auto-fetch models when editing an existing provider
    if (provider?.base_url) {
      fetchModels();
    }
  }

  function isBaseUrlValid(url) {
    if (!url || url.trim() === "") return false;
    return url.includes("://") && (url.includes(".") || url.includes("localhost") || url.includes(":"))
  }

  async function fetchModels() {
    const url = formData.base_url?.trim();
    if (!isBaseUrlValid(url)) return;

    isLoadingModels = true;
    modelsLoadError = "";
    availableModels = [];

    try {
      const payload = {
        base_url: url,
        provider_type: formData.provider_type,
      };

      // Use direct api_key only if it's a real key (not a masked placeholder like "sk-...f8k3")
      if (formData.api_key && !isMaskedKey(formData.api_key)) {
        payload.api_key = formData.api_key;
      } else if (editingProvider) {
        payload.provider_id = editingProvider.id;
      }

      const result = await requestApi("/llm/providers/fetch-models", "POST", payload);

      console.log("[ProviderConfig] fetch-models response:", JSON.stringify(result));

      // Handle both wrapped response and direct array
      const models = result?.models || result?.data || result || [];
      if (Array.isArray(models) && models.length > 0) {
        availableModels = models;
        availableModels = result.models;
        // If current default_model is not in the list, auto-select the first one
        if (!result.models.includes(formData.default_model)) {
          formData.default_model = result.models[0];
        }
      }
    } catch (err) {
      modelsLoadError = err.message || "Failed to load models";
      console.warn("[ProviderConfig] Failed to fetch models:", err.message);
    } finally {
      isLoadingModels = false;
    }
  }

  function debouncedFetchModels() {
    if (fetchDebounceTimer) {
      clearTimeout(fetchDebounceTimer);
    }
    fetchDebounceTimer = setTimeout(() => {
      fetchDebounceTimer = null;
      fetchModels();
    }, 800);
  }

  async function testConnection() {
    console.log("[ProviderConfig][Action] Testing connection", formData);
    isTesting = true;
    testStatus = { type: "info", message: $t.llm.testing };

    try {
      const endpoint = editingProvider
        ? `/llm/providers/${editingProvider.id}/test`
        : "/llm/providers/test";

      // For saved providers, the backend uses the stored key (ignore body).
      // For new providers, only send the real key (reject masked/empty).
      const testData = { ...formData };
      if (isMaskedKey(testData.api_key)) {
        delete testData.api_key;
      }
      const result = await requestApi(endpoint, "POST", testData);

      if (result.success) {
        testStatus = { type: "success", message: $t.llm.connection_success };
      } else {
        testStatus = {
          type: "error",
          message: $t.llm.connection_failed.replace(
            "{error}",
            result.error || "Unknown error",
          ),
        };
      }
    } catch (err) {
      testStatus = {
        type: "error",
        message: $t.llm.connection_failed.replace("{error}", err.message),
      };
    } finally {
      isTesting = false;
    }
  }

  async function detectMaxImages() {
    const providerId = editingProvider?.id;
    if (!providerId) {
      addToast($t.llm.save_before_probe, "warning");
      return;
    }

    console.log("[ProviderConfig][Action] Detecting max images for", providerId);
    isProbing = true;
    probeError = "";
    probeResult = null;

    try {
      const result = await requestApi(`/llm/providers/${providerId}/probe-max-images`, "POST");
      console.log("[ProviderConfig] probe result:", JSON.stringify(result));
      probeResult = result;

      if (result.max_images != null) {
        addToast(
          $t.llm.max_images_detected.replace("{n}", result.max_images),
          "success"
        );
        formData.max_images = result.max_images;
      } else {
        addToast(
          $t.llm.no_max_images_limit,
          "info"
        );
      }
    } catch (err) {
      probeError = err.message || "Failed to detect max images";
      console.warn("[ProviderConfig] Probe failed:", err.message);
      addToast(probeError, "error");
    } finally {
      isProbing = false;
    }
  }

  async function handleSubmit() {
    console.log("[ProviderConfig][Action] Submitting provider config");
    const method = editingProvider ? "PUT" : "POST";
    const endpoint = editingProvider
      ? `/llm/providers/${editingProvider.id}`
      : "/llm/providers";

    // When editing, only include api_key if user entered a new one (not empty and not masked placeholder)
    const submitData = { ...formData };
    if (editingProvider && (!submitData.api_key || isMaskedKey(submitData.api_key))) {
      // If editing and api_key is empty or masked, don't send it (backend will keep existing)
      delete submitData.api_key;
    }

    try {
      await requestApi(endpoint, method, submitData);
      showForm = false;
      resetForm();
      onSave();
    } catch (err) {
      addToast(err.message, "error");
    }
  }

  async function toggleActive(provider) {
    if (togglingProviderIds.has(provider.id)) return;

    const previousState = Boolean(provider.is_active);
    provider.is_active = !previousState;
    providers = [...providers];
    togglingProviderIds.add(provider.id);
    togglingProviderIds = new Set(togglingProviderIds);

    try {
      const updatePayload = {
        id: provider.id,
        name: provider.name,
        provider_type: provider.provider_type,
        base_url: provider.base_url,
        default_model: provider.default_model,
        is_active: provider.is_active,
      };
      await requestApi(`/llm/providers/${provider.id}`, "PUT", updatePayload);
    } catch (err) {
      provider.is_active = previousState;
      providers = [...providers];
      addToast(err.message, "error");
    } finally {
      togglingProviderIds.delete(provider.id);
      togglingProviderIds = new Set(togglingProviderIds);
      onSave();
    }
  }

  async function handleDelete(provider) {
    if (deletingProviderIds.has(provider.id)) return;
    if (
      !confirm(
        $t.llm.delete_confirm.replace("{name}", provider.name || provider.id),
      )
    ) {
      return;
    }

    deletingProviderIds.add(provider.id);
    deletingProviderIds = new Set(deletingProviderIds);

    try {
      await requestApi(`/llm/providers/${provider.id}`, "DELETE");
      providers = providers.filter(({ id }) => id !== provider.id);
      addToast($t.llm.delete_success.replace("{name}", provider.name), "success");
      onSave();
    } catch (err) {
      addToast(
        $t.llm.delete_failed.replace(
          "{error}",
          err?.message || $t.common.error,
        ),
        "error",
      );
    } finally {
      deletingProviderIds.delete(provider.id);
      deletingProviderIds = new Set(deletingProviderIds);
    }
  }
</script>

<div class="p-4">
  <div class="flex justify-between items-center mb-6">
    <h2 class="text-xl font-bold">{$t.llm.providers_title}</h2>
    <button
      type="button"
      class="bg-primary text-white px-4 py-2 rounded hover:bg-primary-hover transition"
      onclick={() => {
        resetForm();
        showForm = true;
      }}
    >
        {$t.llm.add_provider}
    </button>
  </div>

  {#if showForm}
    <div
      class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
      role="dialog"
      aria-modal="true"
    >
      <div class="bg-white p-6 rounded-lg shadow-xl w-full max-w-md">
        <h3 class="text-lg font-semibold mb-4">
          {editingProvider ? $t.llm.edit_provider : $t.llm.new_provider}
        </h3>

        <div class="space-y-4">
          <div>
            <label
              for="provider-name"
              class="block text-sm font-medium text-gray-700"
              >{$t.llm.name}</label
            >
            <input
              id="provider-name"
              type="text"
              bind:value={formData.name}
              class="mt-1 block w-full border rounded-md p-2"
              placeholder="e.g. My OpenAI"
            />
          </div>

          <div>
            <label
              for="provider-type"
              class="block text-sm font-medium text-gray-700"
              >{$t.llm.type}</label
            >
            <select
              id="provider-type"
              bind:value={formData.provider_type}
              onchange={() => updateBaseUrlForType(formData.provider_type)}
              class="mt-1 block w-full border rounded-md p-2"
            >
              <option value="openai">OpenAI</option>
              <option value="openrouter">OpenRouter</option>
              <option value="kilo">Kilo</option>
              <option value="litellm">LiteLLM</option>
            </select>
          </div>

          <div>
            <label
              for="provider-base-url"
              class="block text-sm font-medium text-gray-700"
              >{$t.llm.base_url}</label
            >
            <div class="flex gap-2">
              <input
                id="provider-base-url"
                type="text"
                bind:value={formData.base_url}
                class="mt-1 block w-full border rounded-md p-2"
                oninput={debouncedFetchModels}
              />
              <button
                type="button"
                class="mt-1 px-3 py-2 text-sm border rounded-md hover:bg-gray-50 disabled:opacity-50"
                title={$t.llm.refresh_models}
                onclick={fetchModels}
                disabled={isLoadingModels || !isBaseUrlValid(formData.base_url)}
              >
                {isLoadingModels ? "..." : "↻"}
              </button>
            </div>
          </div>

          <div>
            <label
              for="provider-api-key"
              class="block text-sm font-medium text-gray-700"
              >{$t.llm.api_key}</label
            >
            {#if editingProvider && formData.api_key && isMaskedKey(formData.api_key)}
              <div class="mt-1 flex items-center gap-2">
                <code class="block w-full rounded-md border border-gray-300 bg-gray-50 p-2 font-mono text-sm text-gray-700 select-all">
                  {formData.api_key}
                </code>
                <button
                  type="button"
                  class="shrink-0 rounded bg-primary px-3 py-2 text-xs font-medium text-white hover:bg-primary-hover"
                  onclick={() => { formData.api_key = ""; }}
                >
                  {$t.llm?.change_key || "Change"}
                </button>
              </div>
              <p class="mt-1 text-xs text-gray-400">
                {$t.llm?.key_masked_hint || "Masked for security. Click \"Change\" to enter a new key."}
              </p>
            {:else}
              <input
                id="provider-api-key"
                type="password"
                bind:value={formData.api_key}
                class="mt-1 block w-full border rounded-md p-2"
                placeholder={$t.llm?.api_key_placeholder || "sk-..."}
                oninput={debouncedFetchModels}
              />
            {/if}
          </div>

          <div>
            <label
              for="provider-default-model"
              class="block text-sm font-medium text-gray-700"
              >{$t.llm.default_model}</label
            >

            {#if isLoadingModels}
              <div class="mt-1 text-sm text-gray-500 italic flex items-center gap-2">
                <span class="inline-block w-3 h-3 border-2 border-gray-400 border-t-transparent rounded-full animate-spin"></span>
                {$t.llm.loading_models}
              </div>
            {:else if modelsLoadError}
              <div class="mt-1 text-xs mb-1 {modelsLoadError.startsWith('Enter') ? 'text-amber-600' : 'text-red-600'}">{modelsLoadError}</div>
            {/if}

            {#if availableModels.length > 0 && !isLoadingModels}
              <select
                id="provider-default-model"
                bind:value={formData.default_model}
                class="mt-1 block w-full border rounded-md p-2"
              >
                {#each availableModels as model}
                  <option value={model}>{model}</option>
                {/each}
              </select>
              <div class="mt-1 text-xs text-gray-400">
                {($t.llm.models_loaded || "{count} models loaded").replace("{count}", availableModels.length)}
              </div>
            {:else}
              <input
                id="provider-default-model"
                type="text"
                bind:value={formData.default_model}
                class="mt-1 block w-full border rounded-md p-2"
                placeholder="gpt-4o"
              />
            {/if}
          </div>

          <div class="flex items-center">
            <input
              id="provider-active"
              type="checkbox"
              bind:checked={formData.is_active}
              class="mr-2"
            />
            <label
              for="provider-active"
              class="text-sm font-medium text-gray-700">{$t.llm.active}</label
            >
          </div>

          <div class="flex items-center">
            <input
              id="provider-multimodal"
              type="checkbox"
              bind:checked={formData.is_multimodal}
              class="mr-2"
            />
            <label
              for="provider-multimodal"
              class="text-sm font-medium text-gray-700"
            >
              {$t.llm.multimodal_checkbox}
            </label>
          </div>

          <div class="border-t pt-3">
            <div class="flex items-center justify-between">
              <div class="flex items-center gap-1">
                <span class="text-sm font-medium text-gray-700">
                  {$t.llm.max_images_label}
                </span>
                <HelpTooltip text={$t.llm.help_max_images} />
                {#if formData.max_images != null}
                  <span class="ml-1 text-sm font-mono text-gray-600 bg-gray-100 px-2 py-0.5 rounded">
                    {formData.max_images}
                  </span>
                {:else}
                  <span class="ml-1 text-xs text-gray-400">
                    {$t.llm.not_detected}
                  </span>
                {/if}
              </div>
              <button
                type="button"
                class="text-xs px-3 py-1.5 border rounded-md hover:bg-gray-50 disabled:opacity-50"
                onclick={detectMaxImages}
                disabled={isProbing || !editingProvider}
                title={editingProvider ? $t.llm.detect_max_images : $t.llm.save_before_probe}
              >
                {#if isProbing}
                  <span class="inline-block w-3 h-3 border-2 border-gray-400 border-t-transparent rounded-full animate-spin mr-1"></span>
                  {$t.llm.detecting}
                {:else}
                  {$t.llm.detect_max_images}
                {/if}
              </button>
            </div>
            {#if probeError}
              <div class="mt-1 text-xs text-red-600">{probeError}</div>
            {/if}
            {#if probeResult && probeResult.max_images != null}
              <div class="mt-1 text-xs text-green-600">
                {$t.llm.detected_via.replace("{method}", probeResult.method === "parse_error" ? $t.llm.detection_method_error : $t.llm.detection_method_binary)}
              </div>
            {/if}
          </div>
        </div>

        {#if testStatus.message}
          <div
            class={`mt-4 p-2 rounded text-sm ${testStatus.type === "success" ? "bg-green-100 text-green-800" : testStatus.type === "error" ? "bg-red-100 text-red-800" : "bg-blue-100 text-blue-800"}`}
          >
            {testStatus.message}
          </div>
        {/if}

        <div class="mt-6 flex justify-between gap-2">
          <button
            type="button"
            class="px-4 py-2 border rounded hover:bg-gray-50 flex-1"
            onclick={() => {
              showForm = false;
            }}
          >
            {$t.llm.cancel}
          </button>
          <button
            type="button"
            class="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700 flex-1"
            disabled={isTesting}
            onclick={testConnection}
          >
            {isTesting ? $t.llm.testing : $t.llm.test}
          </button>
          <button
            type="button"
            class="px-4 py-2 bg-primary text-white rounded hover:bg-primary-hover flex-1"
            onclick={handleSubmit}
          >
            {$t.llm.save}
          </button>
        </div>
      </div>
    </div>
  {/if}

  <div class="grid gap-4">
    {#each providers as provider}
      <div
        class="border rounded-lg p-4 flex justify-between items-center bg-white shadow-sm"
      >
        <div>
          <div class="font-bold flex items-center gap-2">
            {provider.name}
            <span
              class={`text-xs px-2 py-0.5 rounded-full ${provider.is_active ? "bg-green-100 text-green-800" : "bg-gray-100 text-gray-800"}`}
            >
              {provider.is_active ? $t.llm.active : "Inactive"}
            </span>
            <span
              class={`text-xs px-2 py-0.5 rounded-full ${provider.is_multimodal ? "bg-sky-100 text-sky-800" : "bg-amber-100 text-amber-800"}`}
            >
              {provider.is_multimodal
                ? ($t.llm?.multimodal )
                : ($t.llm?.text_only )}
            </span>
          </div>
          <div class="text-sm text-gray-500">
            {provider.provider_type} • {provider.default_model}
          </div>
        </div>
        <div class="flex gap-2">
          <button
            type="button"
            class="text-sm text-primary hover:underline"
            onclick={() => handleEdit(provider)}
          >
            {$t.common.edit}
          </button>
          <button
            type="button"
            class="text-sm text-red-600 hover:underline"
            onclick={() => handleDelete(provider)}
            disabled={deletingProviderIds.has(provider.id)}
          >
            {#if deletingProviderIds.has(provider.id)}
              ...
            {:else}
              {$t.common.delete}
            {/if}
          </button>
          <button
            type="button"
            class={`text-sm ${provider.is_active ? "text-orange-600" : "text-green-600"} hover:underline`}
            onclick={() => toggleActive(provider)}
            disabled={togglingProviderIds.has(provider.id) || deletingProviderIds.has(provider.id)}
          >
            {#if togglingProviderIds.has(provider.id)}
              ...
            {:else}
              {provider.is_active ? "Deactivate" : "Activate"}
            {/if}
          </button>
        </div>
      </div>
    {:else}
      <div
        class="text-center py-8 text-gray-500 border-2 border-dashed rounded-lg"
      >
        {$t.llm.no_providers}
      </div>
    {/each}
  </div>
</div>

<!-- #endregion ProviderConfig -->
