<script lang="ts">
  import { tick } from 'svelte';
  import { pageTitle } from '$lib/branding';
  import { Save, RefreshCw, Trash2 } from 'lucide-svelte';
  import { useQueryClient } from '@tanstack/svelte-query';
  import {
    createLidarrConfigQuery,
    createQualityProfilesQuery,
    createMetadataProfilesQuery,
    createRootFoldersQuery,
    putLidarrConfig,
    testLidarrConnection,
    createCoverProvidersQuery,
    updateCoverProvider,
    testCoverProvider,
    createTagProvidersQuery,
    updateTagProvider,
    testTagProvider,
    updateSMTPConfig,
    testSMTPConfig,
    createSMTPConfigQuery,
    type CoverProvider,
    type TagProvider,
    type SMTPConfig
  } from '$lib/api/admin';
  import { qk } from '$lib/api/queries';
  import { errCode } from '$lib/api/errors';
  import { pushToast } from '$lib/stores/toast.svelte';
  import Modal from '$lib/components/Modal.svelte';
  import NetworkSettingsCard from '$lib/components/NetworkSettingsCard.svelte';
  import type { LidarrConfig, LidarrTestResult } from '$lib/api/types';

  // Lidarr connection panel. The "saved api key" is masked as "***" on GET —
  // never displayed in the input. The input always starts empty; sending an
  // empty string on PUT tells the backend "preserve the saved key". This is
  // the one field with that semantics; everything else is sent as-typed.

  const client = useQueryClient();

  const configStore = createLidarrConfigQuery();
  const config = $derived($configStore);

  // Local form state — initialized once when config first loads. Subsequent
  // refetches don't clobber whatever the operator is in the middle of editing.
  let baseUrl = $state('');
  let apiKeyInput = $state(''); // intentionally never seeded with '***'
  let qualityId = $state<number>(0);
  let metadataId = $state<number>(0);
  let rootPath = $state<string>('');
  let initialized = false;

  // Test-derived lists. Populated by onTest or as the first step of
  // onSave. When non-empty, dropdowns prefer these over the query data
  // (the query path only works once Lidarr is enabled, which on a
  // fresh deploy isn't true until after the first successful save).
  let testQualityProfiles = $state<{ id: number; name: string }[]>([]);
  let testMetadataProfiles = $state<{ id: number; name: string }[]>([]);
  let testRootFolders = $state<{ path: string; accessible: boolean; free_space: number }[]>([]);
  let testListErrors = $state<Record<string, string>>({});

  $effect(() => {
    const c = config.data;
    if (c && !initialized) {
      baseUrl = c.base_url;
      qualityId = c.default_quality_profile_id;
      metadataId = c.default_metadata_profile_id;
      rootPath = c.default_root_folder_path;
      initialized = true;
    }
  });

  // Auto-default to the first available option. Sources, in priority
  // order: the test response (works pre-save), then the query data
  // (works post-save). The operator can still override after auto-fill.
  $effect(() => {
    if (qualityId === 0) {
      const list = testQualityProfiles.length ? testQualityProfiles : (profiles.data ?? []);
      if (list.length > 0) qualityId = list[0].id;
    }
  });
  $effect(() => {
    if (metadataId === 0) {
      const list = testMetadataProfiles.length ? testMetadataProfiles : (metadataProfiles.data ?? []);
      if (list.length > 0) metadataId = list[0].id;
    }
  });
  $effect(() => {
    if (rootPath === '') {
      const list = testRootFolders.length ? testRootFolders : (folders.data ?? []);
      if (list.length > 0) rootPath = list[0].path;
    }
  });

  // The dropdown queries only fire once Lidarr is configured; otherwise the
  // backend has no client to call and would 4xx.
  const profilesEnabled = $derived(!!config.data?.enabled);

  const profilesStore = $derived(createQualityProfilesQuery(profilesEnabled));
  const profiles = $derived($profilesStore);

  const metadataProfilesStore = $derived(createMetadataProfilesQuery(profilesEnabled));
  const metadataProfiles = $derived($metadataProfilesStore);

  const foldersStore = $derived(createRootFoldersQuery(profilesEnabled));
  const folders = $derived($foldersStore);

  let testResult: LidarrTestResult | null = $state(null);
  let saveError: string | null = $state(null);
  let isSaving = $state(false);
  let isTesting = $state(false);

  async function onSave() {
    isSaving = true;
    saveError = null;
    try {
      // Test first: confirm the connection AND fetch profiles/folders so
      // the auto-default $effects can fill any zero/empty defaults.
      const test = await testLidarrConnection({
        base_url: baseUrl,
        api_key: apiKeyInput
      });
      testResult = test;
      if (!test.ok) {
        saveError = test.error;
        return;
      }
      testQualityProfiles = test.quality_profiles ?? [];
      testMetadataProfiles = test.metadata_profiles ?? [];
      testRootFolders = test.root_folders ?? [];
      testListErrors = test.list_errors ?? {};

      // Wait one tick for the auto-default $effects to fire on the
      // newly-populated lists.
      await tick();

      if (qualityId === 0 || metadataId === 0 || rootPath === '') {
        saveError = 'missing_defaults_after_test';
        return;
      }

      const cfg: LidarrConfig = {
        enabled: true,
        base_url: baseUrl,
        api_key: apiKeyInput, // empty string tells backend "preserve saved key"
        default_quality_profile_id: qualityId,
        default_metadata_profile_id: metadataId,
        default_root_folder_path: rootPath
      };
      await putLidarrConfig(cfg);
      // Invalidate config + profile/folder lists. A new base_url means a
      // different server, so the cached lists must refetch.
      await Promise.all([
        client.invalidateQueries({ queryKey: qk.lidarrConfig() }),
        client.invalidateQueries({ queryKey: qk.lidarrQualityProfiles() }),
        client.invalidateQueries({ queryKey: qk.lidarrMetadataProfiles() }),
        client.invalidateQueries({ queryKey: qk.lidarrRootFolders() })
      ]);
      apiKeyInput = '';
    } catch (e) {
      const code = errCode(e);
      saveError = code === 'unknown' ? 'save_failed' : code;
    } finally {
      isSaving = false;
    }
  }

  async function onTest() {
    isTesting = true;
    testResult = null;
    try {
      const result = await testLidarrConnection({
        base_url: baseUrl,
        api_key: apiKeyInput
      });
      testResult = result;
      if (result.ok) {
        testQualityProfiles = result.quality_profiles ?? [];
        testMetadataProfiles = result.metadata_profiles ?? [];
        testRootFolders = result.root_folders ?? [];
        testListErrors = result.list_errors ?? {};
      }
    } finally {
      isTesting = false;
    }
  }

  // Disconnect typed-confirm modal. Requires the literal "DISCONNECT" string
  // (whitespace-trimmed) to avoid muscle-memory clearing of an integration the
  // operator depends on.
  let modalOpen = $state(false);
  let disconnectInput = $state('');
  let disconnectError = $state<string | null>(null);
  const canDisconnect = $derived(disconnectInput.trim() === 'DISCONNECT');

  async function onConfirmDisconnect() {
    if (!canDisconnect) return;
    disconnectError = null;
    try {
      await putLidarrConfig({
        enabled: false,
        base_url: '',
        api_key: '',
        default_quality_profile_id: 0,
        default_metadata_profile_id: 0,
        default_root_folder_path: ''
      });
      await Promise.all([
        client.invalidateQueries({ queryKey: qk.lidarrConfig() }),
        client.invalidateQueries({ queryKey: qk.lidarrQualityProfiles() }),
        client.invalidateQueries({ queryKey: qk.lidarrMetadataProfiles() }),
        client.invalidateQueries({ queryKey: qk.lidarrRootFolders() })
      ]);
      modalOpen = false;
      disconnectInput = '';
    } catch (e) {
      const code = errCode(e);
      disconnectError = code === 'unknown' ? 'disconnect_failed' : code;
    }
  }

  function cancelDisconnect() {
    modalOpen = false;
    disconnectInput = '';
    disconnectError = null;
  }

  // ---- Cover art providers ----
  const coverProvidersStore = $derived(createCoverProvidersQuery());
  const coverProvidersQ = $derived($coverProvidersStore);

  // Local edit state — keyed by provider_id. Initialised lazily when
  // the query data lands, then preserved across refetches so the
  // operator's in-flight edits don't get clobbered.
  let coverLocalState = $state<Record<string, { enabled: boolean; apiKey: string }>>({});
  let coverTestResults = $state<Record<string, { ok: boolean; duration_ms?: number; error?: string }>>({});
  let coverSaving = $state<Record<string, boolean>>({});
  let coverTesting = $state<Record<string, boolean>>({});

  $effect(() => {
    if (coverProvidersQ.data) {
      for (const p of coverProvidersQ.data.providers) {
        if (!coverLocalState[p.id]) {
          coverLocalState[p.id] = { enabled: p.enabled, apiKey: '' };
        }
      }
    }
  });

  function coverHasChanges(provider: CoverProvider): boolean {
    const local = coverLocalState[provider.id];
    if (!local) return false;
    return provider.enabled !== local.enabled || local.apiKey !== '';
  }

  async function coverSave(provider: CoverProvider) {
    const local = coverLocalState[provider.id];
    if (!local || !coverHasChanges(provider)) return;
    const patch: { enabled?: boolean; api_key?: string } = {};
    if (provider.enabled !== local.enabled) patch.enabled = local.enabled;
    if (local.apiKey !== '') patch.api_key = local.apiKey;

    coverSaving[provider.id] = true;
    try {
      const result = await updateCoverProvider(provider.id, patch);
      coverLocalState[provider.id] = { enabled: result.enabled, apiKey: '' };
      await client.invalidateQueries({ queryKey: qk.coverProviders() });
      if (result.version_bumped) {
        await client.invalidateQueries({ queryKey: qk.coverage() });
      }
    } catch (_e) {
      // Error display is handled inline via saveError pattern; swallow here.
    } finally {
      coverSaving[provider.id] = false;
    }
  }

  async function coverTest(provider: CoverProvider) {
    coverTesting[provider.id] = true;
    try {
      coverTestResults[provider.id] = await testCoverProvider(provider.id);
    } catch (e) {
      coverTestResults[provider.id] = { ok: false, error: (e as Error).message };
    } finally {
      coverTesting[provider.id] = false;
    }
  }

  // ---- Tag enrichment providers ----
  // Mirrors the cover-art providers panel over /api/admin/tag-sources (#1490).
  const tagProvidersStore = $derived(createTagProvidersQuery());
  const tagProvidersQ = $derived($tagProvidersStore);

  let tagLocalState = $state<Record<string, { enabled: boolean; apiKey: string }>>({});
  let tagTestResults = $state<Record<string, { ok: boolean; duration_ms?: number; error?: string }>>({});
  let tagSaving = $state<Record<string, boolean>>({});
  let tagTesting = $state<Record<string, boolean>>({});

  $effect(() => {
    if (tagProvidersQ.data) {
      for (const p of tagProvidersQ.data.providers) {
        if (!tagLocalState[p.id]) {
          tagLocalState[p.id] = { enabled: p.enabled, apiKey: '' };
        }
      }
    }
  });

  function tagHasChanges(provider: TagProvider): boolean {
    const local = tagLocalState[provider.id];
    if (!local) return false;
    return provider.enabled !== local.enabled || local.apiKey !== '';
  }

  async function tagSave(provider: TagProvider) {
    const local = tagLocalState[provider.id];
    if (!local || !tagHasChanges(provider)) return;
    const patch: { enabled?: boolean; api_key?: string } = {};
    if (provider.enabled !== local.enabled) patch.enabled = local.enabled;
    if (local.apiKey !== '') patch.api_key = local.apiKey;

    tagSaving[provider.id] = true;
    try {
      const result = await updateTagProvider(provider.id, patch);
      tagLocalState[provider.id] = { enabled: result.enabled, apiKey: '' };
      await client.invalidateQueries({ queryKey: qk.tagProviders() });
    } catch (_e) {
      // Inline error surfacing handled by the mutation toast layer; swallow here.
    } finally {
      tagSaving[provider.id] = false;
    }
  }

  async function tagTest(provider: TagProvider) {
    tagTesting[provider.id] = true;
    try {
      tagTestResults[provider.id] = await testTagProvider(provider.id);
    } catch (e) {
      tagTestResults[provider.id] = { ok: false, error: (e as Error).message };
    } finally {
      tagTesting[provider.id] = false;
    }
  }

  // SMTP config -------------------------------------------------------------

  const smtpStore = $derived(createSMTPConfigQuery());
  const smtpQ = $derived($smtpStore);

  let smtpForm = $state<SMTPConfig>({
    enabled: false, host: '', port: 587, username: '',
    password: '', from_address: '', from_name: 'Minstrel', use_tls: true,
  });
  let smtpPasswordInput = $state('');
  let smtpSaving = $state(false);
  let smtpTesting = $state(false);

  $effect(() => {
    if (smtpQ.data) {
      smtpForm = smtpQ.data;
      smtpPasswordInput = '';
    }
  });

  async function onSaveSMTP() {
    smtpSaving = true;
    try {
      await updateSMTPConfig({ ...smtpForm, password: smtpPasswordInput });
      await client.invalidateQueries({ queryKey: qk.smtpConfig() });
      smtpPasswordInput = '';
      pushToast('SMTP config saved.');
    } catch (e: unknown) {
      const code = errCode(e);
      if (code === 'missing_fields') pushToast('Host and from address are required when enabled.', 'error');
      else pushToast(`Save failed: ${code}`, 'error');
    } finally {
      smtpSaving = false;
    }
  }

  async function onTestSMTP() {
    smtpTesting = true;
    try {
      await testSMTPConfig();
      pushToast('Test email sent. Check your inbox.');
    } catch (e: unknown) {
      const code = errCode(e);
      const message = (e as { message?: string })?.message;
      if (code === 'no_email_on_file') pushToast('Set your email in /settings before testing.', 'error');
      else if (code === 'not_configured') pushToast('Save the SMTP config first.', 'error');
      else if (code === 'send_failed') pushToast(`Send failed: ${message || 'see server logs'}`, 'error');
      else pushToast(`Test failed: ${code}`, 'error');
    } finally {
      smtpTesting = false;
    }
  }
</script>

<svelte:head><title>{pageTitle('Admin · Integrations')}</title></svelte:head>

<div class="space-y-6">
  <header class="flex items-center justify-between">
    <h2 class="font-display text-2xl font-medium text-text-primary">Integrations</h2>
    {#if config.data?.enabled}
      <span
        class="inline-flex items-center gap-2 rounded-md bg-accent-tint px-3 py-1 text-xs font-medium text-action-primary"
      >
        <span class="h-1.5 w-1.5 rounded-full bg-action-primary"></span>
        Lidarr · connected
      </span>
    {:else}
      <span
        class="inline-flex items-center gap-2 rounded-md border border-border px-3 py-1 text-xs text-text-muted"
      >
        <span class="h-1.5 w-1.5 rounded-full bg-text-muted"></span>
        Lidarr · unset
      </span>
    {/if}
  </header>

  <!-- Lidarr panel -->
  <section class="space-y-4 rounded-xl border border-border bg-surface p-5">
    <div>
      <h3 class="font-display text-lg font-medium text-text-primary">Lidarr</h3>
      <p class="mt-1 text-sm text-text-secondary">
        Search Lidarr from <span class="font-mono text-accent">/discover</span> and
        route approved requests to it.
      </p>
    </div>

    <label class="block">
      <span class="block text-sm text-text-secondary">Base URL</span>
      <input
        type="text"
        bind:value={baseUrl}
        placeholder="http://lidarr.lan:8686"
        class="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 font-mono text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-accent"
      />
    </label>

    <label class="block">
      <span class="block text-sm text-text-secondary">API key</span>
      <input
        type="password"
        bind:value={apiKeyInput}
        placeholder={config.data?.api_key === '***'
          ? '••• (saved — leave empty to keep)'
          : 'Paste API key'}
        class="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 font-mono text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-accent"
      />
    </label>

    <label class="block">
      <span class="block text-sm text-text-secondary">Default quality profile</span>
      <select
        bind:value={qualityId}
        disabled={!profilesEnabled && testQualityProfiles.length === 0}
        class="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-accent disabled:opacity-50"
      >
        {#each (testQualityProfiles.length ? testQualityProfiles : (profiles.data ?? [])) as p (p.id)}
          <option value={p.id}>{p.name}</option>
        {/each}
      </select>
      {#if testListErrors.quality_profiles}
        <p class="mt-1 text-xs text-error">
          Couldn't fetch quality profiles — {testListErrors.quality_profiles}
        </p>
      {/if}
    </label>

    <label class="block">
      <span class="block text-sm text-text-secondary">Default metadata profile</span>
      <select
        bind:value={metadataId}
        disabled={!profilesEnabled && testMetadataProfiles.length === 0}
        class="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-accent disabled:opacity-50"
      >
        {#each (testMetadataProfiles.length ? testMetadataProfiles : (metadataProfiles.data ?? [])) as p (p.id)}
          <option value={p.id}>{p.name}</option>
        {/each}
      </select>
      {#if testListErrors.metadata_profiles}
        <p class="mt-1 text-xs text-error">
          Couldn't fetch metadata profiles — {testListErrors.metadata_profiles}
        </p>
      {/if}
    </label>

    <label class="block">
      <span class="block text-sm text-text-secondary">Default root folder</span>
      <select
        bind:value={rootPath}
        disabled={!profilesEnabled && testRootFolders.length === 0}
        class="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 font-mono text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-accent disabled:opacity-50"
      >
        {#each (testRootFolders.length ? testRootFolders : (folders.data ?? [])) as f (f.path)}
          <option value={f.path}
            >{f.path}{f.accessible ? '' : ' (not accessible)'}</option
          >
        {/each}
      </select>
      {#if testListErrors.root_folders}
        <p class="mt-1 text-xs text-error">
          Couldn't fetch root folders — {testListErrors.root_folders}
        </p>
      {/if}
    </label>

    {#if testResult}
      {#if testResult.ok}
        <p class="text-sm text-action-primary">
          Connected — Lidarr {testResult.version}
        </p>
      {:else}
        <p class="text-sm text-error">Connection failed — {testResult.error}</p>
      {/if}
    {/if}
    {#if saveError}
      <p class="text-sm text-error">Save failed — {saveError}</p>
    {/if}

    <div class="flex items-center gap-2 pt-2">
      <button
        type="button"
        onclick={onSave}
        disabled={isSaving}
        class="inline-flex items-center gap-2 rounded-md bg-action-primary px-3 py-1.5 text-sm text-action-fg disabled:opacity-50"
      >
        <Save size={14} strokeWidth={2} />
        Save changes
      </button>
      <button
        type="button"
        onclick={onTest}
        disabled={isTesting}
        class="inline-flex items-center gap-2 rounded-md border border-border bg-transparent px-3 py-1.5 text-sm text-text-secondary hover:text-text-primary disabled:opacity-50"
      >
        <RefreshCw size={14} strokeWidth={2} />
        Test connection
      </button>
      <button
        type="button"
        onclick={() => (modalOpen = true)}
        class="ml-auto inline-flex items-center gap-2 rounded-md bg-action-destructive px-3 py-1.5 text-sm text-action-fg"
      >
        <Trash2 size={14} strokeWidth={2} />
        Disconnect
      </button>
    </div>
  </section>

  <!-- MusicBrainz overrides — visually present, not implemented. Foreshadows
       future integrations so the page doesn't read like Lidarr-only. -->
  <section class="space-y-2 rounded-xl border border-border bg-surface p-5 opacity-60">
    <div class="flex items-center justify-between">
      <h3 class="font-display text-lg font-medium text-text-primary">
        MusicBrainz overrides
      </h3>
      <span
        class="inline-flex items-center gap-2 rounded-md border border-border px-3 py-1 text-xs text-text-muted"
      >
        <span class="h-1.5 w-1.5 rounded-full bg-text-muted"></span>
        unset
      </span>
    </div>
    <p class="text-sm text-text-secondary">Not yet configured.</p>
  </section>

  <!-- Cover art providers -->
  <section class="space-y-4 rounded-xl border border-border bg-surface p-5">
    <div>
      <h3 class="font-display text-lg font-medium text-text-primary">Cover art providers</h3>
      <p class="mt-1 text-sm text-text-secondary">
        External sources for album covers and artist images. Providers
        are tried in registration order; first hit wins.
      </p>
    </div>

    {#if coverProvidersQ.data}
      {#each coverProvidersQ.data.providers as provider (provider.id)}
        <article class="rounded-lg border border-border bg-background p-4 space-y-3">
          <header class="flex items-baseline justify-between gap-3">
            <div class="flex items-center gap-2">
              <h4 class="font-display text-base font-medium text-text-primary">{provider.display_name}</h4>
              <span
                class="h-1.5 w-1.5 rounded-full {provider.enabled ? 'bg-action-primary' : 'bg-text-muted'}"
                title={provider.enabled ? 'Active' : 'Disabled'}
              ></span>
            </div>
            <div class="flex flex-wrap gap-1">
              {#each provider.supports as cap}
                <span class="rounded bg-surface px-1.5 py-0.5 text-xs text-text-secondary">{cap.replace('_', ' ')}</span>
              {/each}
            </div>
          </header>

          {#if coverLocalState[provider.id]}
            <div class="grid gap-2 sm:grid-cols-[auto_1fr] items-center">
              <label class="text-sm text-text-secondary" for="enabled-{provider.id}">Enabled</label>
              <div>
                <input id="enabled-{provider.id}" type="checkbox"
                       bind:checked={coverLocalState[provider.id].enabled} />
              </div>

              {#if provider.requires_api_key}
                <label class="text-sm text-text-secondary" for="key-{provider.id}">API key</label>
                <div class="flex items-center gap-2">
                  <input id="key-{provider.id}" type="password"
                         placeholder={provider.api_key_set ? '••• (saved — leave empty to keep)' : 'Leave blank to use the public test key'}
                         bind:value={coverLocalState[provider.id].apiKey}
                         class="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 font-mono text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-accent flex-1" />
                  {#if provider.api_key_set}
                    <span class="text-xs text-text-secondary whitespace-nowrap">✓ Set</span>
                  {/if}
                </div>
              {/if}
            </div>

            <div class="flex items-center gap-2 pt-1">
              <button type="button"
                      disabled={!coverHasChanges(provider) || coverSaving[provider.id]}
                      onclick={() => coverSave(provider)}
                      class="inline-flex items-center gap-2 rounded-md bg-action-primary px-3 py-1.5 text-sm text-action-fg disabled:opacity-50">
                {coverSaving[provider.id] ? 'Saving…' : 'Save changes'}
              </button>
              {#if provider.testable}
                <button type="button"
                        disabled={coverTesting[provider.id]}
                        onclick={() => coverTest(provider)}
                        class="inline-flex items-center gap-2 rounded-md border border-border bg-transparent px-3 py-1.5 text-sm text-text-secondary hover:text-text-primary disabled:opacity-50">
                  {coverTesting[provider.id] ? 'Testing…' : 'Test connection'}
                </button>
              {/if}
              {#if coverTestResults[provider.id]}
                {#if coverTestResults[provider.id].ok}
                  <p class="text-sm text-action-primary">
                    OK{coverTestResults[provider.id].duration_ms ? ` (${coverTestResults[provider.id].duration_ms}ms)` : ''}
                  </p>
                {:else}
                  <p class="text-sm text-error">
                    Failed — {coverTestResults[provider.id].error}
                  </p>
                {/if}
              {/if}
            </div>
          {/if}

          {#if provider.id === 'theaudiodb'}
            <p class="text-xs text-text-muted">
              Public test key works out of the box. A free personal key gives separate quota
              accounting — apply at <a class="underline" href="https://www.theaudiodb.com/api_apply.php" target="_blank" rel="noopener noreferrer">theaudiodb.com/api_apply.php</a>.
            </p>
          {/if}
        </article>
      {/each}
    {:else if coverProvidersQ.isPending}
      <p class="text-sm text-text-muted">Loading…</p>
    {:else}
      <p class="text-sm text-text-muted">No providers registered.</p>
    {/if}
  </section>

  <!-- Tag enrichment providers -->
  <section class="space-y-4 rounded-xl border border-border bg-surface p-5">
    <div>
      <h3 class="font-display text-lg font-medium text-text-primary">Tag enrichment sources</h3>
      <p class="mt-1 text-sm text-text-secondary">
        Folksonomy style/mood tags fetched per track and folded into each
        listener's taste profile alongside the raw file genre — so "Rock"
        gains "post-punk / shoegaze / melancholic". All enabled sources are
        merged. Enabling or disabling a source re-opens previously-processed
        tracks for a fresh pass.
      </p>
    </div>

    {#if tagProvidersQ.data}
      {#each tagProvidersQ.data.providers as provider (provider.id)}
        <article class="rounded-lg border border-border bg-background p-4 space-y-3">
          <header class="flex items-baseline justify-between gap-3">
            <div class="flex items-center gap-2">
              <h4 class="font-display text-base font-medium text-text-primary">{provider.display_name}</h4>
              <span
                class="h-1.5 w-1.5 rounded-full {provider.enabled ? 'bg-action-primary' : 'bg-text-muted'}"
                title={provider.enabled ? 'Active' : 'Disabled'}
              ></span>
            </div>
            <div class="flex flex-wrap gap-1">
              {#each provider.supports as cap}
                <span class="rounded bg-surface px-1.5 py-0.5 text-xs text-text-secondary">{cap.replace('_', ' ')}</span>
              {/each}
            </div>
          </header>

          {#if tagLocalState[provider.id]}
            <div class="grid gap-2 sm:grid-cols-[auto_1fr] items-center">
              <label class="text-sm text-text-secondary" for="tag-enabled-{provider.id}">Enabled</label>
              <div>
                <input id="tag-enabled-{provider.id}" type="checkbox"
                       bind:checked={tagLocalState[provider.id].enabled} />
              </div>

              {#if provider.requires_api_key}
                <label class="text-sm text-text-secondary" for="tag-key-{provider.id}">API key</label>
                <div class="flex items-center gap-2">
                  <input id="tag-key-{provider.id}" type="password"
                         placeholder={provider.api_key_set ? '••• (saved — leave empty to keep)' : 'Paste your API key to enable this source'}
                         bind:value={tagLocalState[provider.id].apiKey}
                         class="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 font-mono text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-accent flex-1" />
                  {#if provider.api_key_set}
                    <span class="text-xs text-text-secondary whitespace-nowrap">✓ Set</span>
                  {/if}
                </div>
              {/if}
            </div>

            <div class="flex items-center gap-2 pt-1">
              <button type="button"
                      disabled={!tagHasChanges(provider) || tagSaving[provider.id]}
                      onclick={() => tagSave(provider)}
                      class="inline-flex items-center gap-2 rounded-md bg-action-primary px-3 py-1.5 text-sm text-action-fg disabled:opacity-50">
                {tagSaving[provider.id] ? 'Saving…' : 'Save changes'}
              </button>
              {#if provider.testable}
                <button type="button"
                        disabled={tagTesting[provider.id]}
                        onclick={() => tagTest(provider)}
                        class="inline-flex items-center gap-2 rounded-md border border-border bg-transparent px-3 py-1.5 text-sm text-text-secondary hover:text-text-primary disabled:opacity-50">
                  {tagTesting[provider.id] ? 'Testing…' : 'Test connection'}
                </button>
              {/if}
              {#if tagTestResults[provider.id]}
                {#if tagTestResults[provider.id].ok}
                  <p class="text-sm text-action-primary">
                    OK{tagTestResults[provider.id].duration_ms ? ` (${tagTestResults[provider.id].duration_ms}ms)` : ''}
                  </p>
                {:else}
                  <p class="text-sm text-error">
                    Failed — {tagTestResults[provider.id].error}
                  </p>
                {/if}
              {/if}
            </div>
          {/if}

          {#if provider.id === 'musicbrainz'}
            <p class="text-xs text-text-muted">
              Keyless and on by default — the always-available baseline. Matches tags by
              recording MBID, so coverage tracks how well your library is MusicBrainz-tagged.
            </p>
          {:else if provider.id === 'lastfm'}
            <p class="text-xs text-text-muted">
              Name-based (artist + title), so it covers tracks without an MBID. Requires a free
              API key — get one at <a class="underline" href="https://www.last.fm/api/account/create" target="_blank" rel="noopener noreferrer">last.fm/api</a>.
            </p>
          {/if}
        </article>
      {/each}
    {:else if tagProvidersQ.isPending}
      <p class="text-sm text-text-muted">Loading…</p>
    {:else}
      <p class="text-sm text-text-muted">No providers registered.</p>
    {/if}
  </section>
  <!-- SMTP / email config -->
  <section class="space-y-4 rounded-xl border border-border bg-surface p-5">
    <div>
      <h3 class="font-display text-lg font-medium text-text-primary">Email (SMTP)</h3>
      <p class="mt-1 text-sm text-text-secondary">
        Configure outgoing email for password resets. The "Send test email"
        button verifies your config end-to-end.
      </p>
    </div>

    <label class="flex items-center gap-2 text-sm">
      <input type="checkbox" bind:checked={smtpForm.enabled} />
      Enabled
    </label>

    <div class="grid gap-3 sm:grid-cols-[auto_1fr] items-center">
      <label for="smtp-host" class="text-sm text-text-secondary">Host</label>
      <input id="smtp-host" type="text" bind:value={smtpForm.host}
             class="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 font-mono text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-accent" />

      <label for="smtp-port" class="text-sm text-text-secondary">Port</label>
      <input id="smtp-port" type="number" min="1" max="65535"
             bind:value={smtpForm.port}
             class="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 font-mono text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-accent" />

      <label for="smtp-username" class="text-sm text-text-secondary">Username</label>
      <input id="smtp-username" type="text" bind:value={smtpForm.username}
             class="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 font-mono text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-accent" />

      <label for="smtp-password" class="text-sm text-text-secondary">Password</label>
      <input id="smtp-password" type="password"
             placeholder={smtpForm.password === '***' ? 'Leave blank to keep current' : ''}
             bind:value={smtpPasswordInput}
             class="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 font-mono text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-accent" />

      <label for="smtp-from" class="text-sm text-text-secondary">From address</label>
      <input id="smtp-from" type="email" bind:value={smtpForm.from_address}
             class="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 font-mono text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-accent" />

      <label for="smtp-from-name" class="text-sm text-text-secondary">From name</label>
      <input id="smtp-from-name" type="text" bind:value={smtpForm.from_name}
             class="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 font-mono text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-accent" />

      <span class="text-sm text-text-secondary">TLS</span>
      <label class="flex items-center gap-2 text-sm">
        <input type="checkbox" bind:checked={smtpForm.use_tls} />
        Use STARTTLS
      </label>
    </div>

    <div class="flex items-center gap-2 pt-2">
      <button type="button" disabled={smtpSaving} onclick={onSaveSMTP}
              class="inline-flex items-center gap-2 rounded-md bg-action-primary px-3 py-1.5 text-sm text-action-fg disabled:opacity-50">
        {smtpSaving ? 'Saving…' : 'Save'}
      </button>
      <button type="button" disabled={smtpTesting} onclick={onTestSMTP}
              class="inline-flex items-center gap-2 rounded-md border border-border bg-transparent px-3 py-1.5 text-sm text-text-secondary hover:text-text-primary disabled:opacity-50">
        {smtpTesting ? 'Sending…' : 'Send test email'}
      </button>
    </div>
  </section>

  <!-- Client IP detection. Belongs here rather than in user Settings: it
       describes how Minstrel sits behind other infrastructure, same as every
       other card on this page, and it's an operator-wide setting rather than
       a per-user preference. -->
  <NetworkSettingsCard />
</div>

<Modal
  title="Disconnect Lidarr?"
  open={modalOpen}
  onClose={cancelDisconnect}
>
  <p class="-mt-2 text-text-secondary">
    This clears the saved configuration. Type
    <span class="font-mono text-text-primary">DISCONNECT</span> to remove the
    Lidarr connection.
  </p>
  <input
    type="text"
    bind:value={disconnectInput}
    placeholder="DISCONNECT"
    aria-label="Type DISCONNECT to confirm"
    class="mt-3 w-full rounded-md border border-border bg-background px-3 py-2 font-mono text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-accent"
  />
  {#if disconnectError}
    <p class="mt-2 text-sm text-error">Disconnect failed — {disconnectError}</p>
  {/if}
  <div class="mt-5 flex justify-end gap-2">
    <button
      type="button"
      onclick={cancelDisconnect}
      class="rounded-md bg-action-secondary px-3 py-1.5 text-sm text-action-fg"
    >
      Cancel
    </button>
    <button
      type="button"
      onclick={onConfirmDisconnect}
      disabled={!canDisconnect}
      class="rounded-md bg-action-destructive px-3 py-1.5 text-sm text-action-fg disabled:opacity-50"
    >
      Disconnect
    </button>
  </div>
</Modal>
