<template>
  <div class="flex flex-col gap-4">
    <PageHeader :title="t('settings.title')" />

    <!-- Préférences (client) -->
    <section class="card flex flex-col gap-3">
      <h2 class="flex items-center gap-2 text-sm font-semibold text-zinc-100">
        <SlidersHorizontal :size="16" /> {{ t('settings.preferences') }}
      </h2>
      <p class="text-xs text-zinc-500">{{ t('settings.preferencesHint') }}</p>

      <div class="flex items-center justify-between gap-3">
        <span class="text-sm text-zinc-300">{{ t('settings.language') }}</span>
        <LanguageSwitcher />
      </div>

      <div class="flex flex-col gap-2 border-t border-zinc-800/80 pt-3">
        <div class="flex items-center justify-between gap-3">
          <div class="min-w-0">
            <p class="text-sm text-zinc-300">{{ t('settings.notifications') }}</p>
            <p class="text-xs text-zinc-500">
              {{ push.supported ? (push.enabled ? t('settings.notificationsEnabled') : t('settings.notificationsDisabled')) : t('settings.pushUnsupported') }}
            </p>
          </div>
          <BaseButton
            v-if="push.supported"
            :variant="push.enabled ? 'secondary' : 'primary'"
            size="sm"
            :icon="push.enabled ? BellOff : Bell"
            :loading="push.busy"
            @click="push.toggle()"
          >
            {{ push.enabled ? t('settings.disableNotifications') : t('settings.enableNotifications') }}
          </BaseButton>
        </div>
        <div v-if="push.enabled">
          <BaseButton variant="ghost" size="sm" :icon="Send" :loading="testing" @click="sendTest">
            {{ t('settings.testNotification') }}
          </BaseButton>
        </div>
      </div>
    </section>

    <!-- Accès & sécurité : tokens -->
    <section class="card flex flex-col gap-3">
      <h2 class="flex items-center gap-2 text-sm font-semibold text-zinc-100">
        <Shield :size="16" /> {{ t('settings.security') }}
      </h2>
      <p class="text-xs text-zinc-500">{{ t('settings.securityHint') }}</p>

      <!-- Création -->
      <form class="flex flex-wrap items-end gap-2" @submit.prevent="createToken">
        <label class="flex min-w-40 flex-1 flex-col gap-1">
          <span class="text-xs text-zinc-400">{{ t('settings.newTokenLabel') }}</span>
          <input v-model="newLabel" class="input" :placeholder="t('settings.newTokenPlaceholder')" maxlength="64" />
        </label>
        <BaseButton type="submit" variant="primary" :icon="Plus" :loading="creating" :disabled="!newLabel.trim()">
          {{ t('settings.createToken') }}
        </BaseButton>
      </form>

      <!-- Valeur en clair (une seule fois) -->
      <div v-if="createdToken" class="card-inset flex flex-col gap-2 border-emerald-800/60 bg-emerald-950/20">
        <p class="text-xs text-emerald-300">{{ t('settings.copyTokenHint') }}</p>
        <div class="flex items-center gap-2">
          <code class="min-w-0 flex-1 truncate rounded bg-zinc-950 px-2 py-1 font-mono text-xs text-zinc-100">{{ createdToken.token }}</code>
          <BaseButton size="sm" :icon="copiedNew ? Check : Copy" @click="copy(createdToken.token, 'new')">
            {{ copiedNew ? t('settings.copied') : t('settings.copy') }}
          </BaseButton>
          <BaseButton size="sm" variant="ghost" icon-only :icon="X" :aria-label="t('common.close')" @click="createdToken = null" />
        </div>
      </div>

      <!-- Liste -->
      <ul class="flex flex-col gap-2">
        <li
          v-for="tok in tokens"
          :key="tok.id"
          class="card-inset flex flex-wrap items-center gap-x-3 gap-y-1"
        >
          <KeyRound :size="15" class="shrink-0 text-zinc-500" />
          <div class="min-w-0 flex-1">
            <p class="flex items-center gap-2 truncate text-sm text-zinc-200">
              {{ tok.label }}
              <span v-if="tok.current" class="badge bg-emerald-500/15 text-emerald-300">{{ t('settings.current') }}</span>
            </p>
            <p class="text-[11px] text-zinc-500">
              {{ t('settings.created', { date: fmt(tok.createdAt) }) }} ·
              {{ tok.lastUsedAt ? t('settings.lastUsed', { date: fmt(tok.lastUsedAt) }) : t('settings.neverUsed') }}
            </p>
          </div>
          <BaseButton
            v-if="!tok.current"
            size="sm"
            :variant="confirmId === tok.id ? 'danger' : 'ghost'"
            :icon="Trash2"
            @click="revoke(tok)"
          >
            {{ confirmId === tok.id ? t('settings.confirmRevoke') : t('settings.revoke') }}
          </BaseButton>
        </li>
      </ul>
    </section>

    <!-- Intégrations : Gitea -->
    <section class="card flex flex-col gap-3">
      <h2 class="flex items-center gap-2 text-sm font-semibold text-zinc-100">
        <Puzzle :size="16" /> {{ t('settings.integrations') }}
      </h2>
      <form class="flex flex-col gap-2" @submit.prevent="saveGitea">
        <label class="flex flex-col gap-1">
          <span class="text-xs text-zinc-400">{{ t('settings.giteaUrlLabel') }}</span>
          <div class="flex flex-wrap items-center gap-2">
            <input v-model="giteaInput" type="url" class="input min-w-48 flex-1" :placeholder="t('settings.giteaUrlPlaceholder')" />
            <BaseButton type="submit" variant="primary" :loading="settings.saving" :disabled="!giteaDirty">
              {{ t('settings.save') }}
            </BaseButton>
          </div>
        </label>
        <p class="text-xs text-zinc-500">{{ t('settings.giteaUrlHint') }}</p>
      </form>
    </section>

    <!-- Découverte des dépôts -->
    <section class="card flex flex-col gap-3">
      <h2 class="flex items-center gap-2 text-sm font-semibold text-zinc-100">
        <ScanSearch :size="16" /> {{ t('settings.discovery') }}
      </h2>
      <p class="text-xs text-zinc-500">{{ t('settings.discoveryHint') }}</p>

      <div class="flex flex-col gap-2">
        <span class="text-xs text-zinc-400">{{ t('settings.scanRoots') }}</span>
        <p v-if="rootsDraft.length === 0" class="text-xs text-zinc-600">{{ t('settings.scanRootsEmpty') }}</p>
        <ul v-else class="flex flex-col gap-1">
          <li v-for="(root, i) in rootsDraft" :key="root" class="card-inset flex items-center gap-2">
            <span class="min-w-0 flex-1 truncate font-mono text-xs text-zinc-200" :title="root">{{ root }}</span>
            <BaseButton size="sm" variant="ghost" icon-only :icon="X" :aria-label="t('settings.removeRoot')" @click="rootsDraft.splice(i, 1)" />
          </li>
        </ul>
        <div>
          <BaseButton size="sm" :icon="FolderOpen" @click="showRootPicker = !showRootPicker">{{ t('settings.addRoot') }}</BaseButton>
        </div>
        <DirectoryPicker v-if="showRootPicker" mode="dir" @select="onAddRoot" @close="showRootPicker = false" />
      </div>

      <label class="flex flex-col gap-1">
        <span class="text-xs text-zinc-400">{{ t('settings.scanInterval') }}</span>
        <input v-model.number="intervalDraft" type="number" min="0" max="1440" class="input w-32" />
        <span class="text-xs text-zinc-500">{{ t('settings.scanIntervalHint') }}</span>
      </label>

      <div>
        <BaseButton variant="primary" :loading="settings.saving" :disabled="!discoveryDirty" @click="saveDiscovery">
          {{ t('settings.save') }}
        </BaseButton>
      </div>
    </section>

    <!-- Serveur (lecture seule) -->
    <section class="card flex flex-col gap-3">
      <h2 class="flex items-center gap-2 text-sm font-semibold text-zinc-100">
        <Server :size="16" /> {{ t('settings.server') }}
      </h2>
      <p class="text-xs text-zinc-500">{{ t('settings.serverHint') }}</p>
      <dl v-if="settings.server" class="flex flex-col divide-y divide-zinc-800/80">
        <ServerRow :label="t('settings.version')" :value="settings.server.version" />
        <ServerRow :label="t('settings.port')" :value="String(settings.server.port)" flag="--port" />
        <ServerRow :label="t('settings.bind')" :value="settings.server.bind" flag="--bind" />
        <ServerRow
          :label="t('settings.allowedOrigins')"
          :value="settings.server.allowedOrigins.length ? settings.server.allowedOrigins.join(', ') : t('settings.none')"
          flag="--allow-origin"
        />
        <ServerRow :label="t('settings.dataDir')" :value="settings.server.dataDir" />
        <ServerRow :label="t('settings.vapidContact')" :value="settings.server.vapidContact" flag="--vapid-contact" />
        <ServerRow :label="t('settings.vapidPublicKey')" :value="settings.server.vapidPublicKey ?? t('settings.none')" mono />
      </dl>
      <SkeletonRow v-else />
    </section>
  </div>
</template>

<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { Bell, BellOff, Check, Copy, FolderOpen, KeyRound, Plus, Puzzle, ScanSearch, Send, Server, Shield, SlidersHorizontal, Trash2, X } from '@lucide/vue';
import type { CreateTokenResponse, TokenInfo, TokensListResponse } from '@arboretum/shared';
import { api, ApiError } from '../lib/api';
import { useSettingsStore } from '../stores/settings';
import { usePushStore } from '../stores/push';
import { useToastsStore } from '../stores/toasts';
import PageHeader from '../components/layout/PageHeader.vue';
import LanguageSwitcher from '../components/LanguageSwitcher.vue';
import BaseButton from '../components/ui/BaseButton.vue';
import SkeletonRow from '../components/ui/SkeletonRow.vue';
import ServerRow from '../components/settings/ServerRow.vue';
import DirectoryPicker from '../components/DirectoryPicker.vue';

const { t, locale } = useI18n();
const settings = useSettingsStore();
const push = usePushStore();
const toasts = useToastsStore();

// ---- Préférences ----
const testing = ref(false);
async function sendTest(): Promise<void> {
  testing.value = true;
  try {
    await api.post('/api/v1/push/test');
    toasts.success(t('settings.testSent'));
  } catch (e) {
    toasts.error(e);
  } finally {
    testing.value = false;
  }
}

// ---- Tokens ----
const tokens = ref<TokenInfo[]>([]);
const newLabel = ref('');
const creating = ref(false);
const createdToken = ref<CreateTokenResponse | null>(null);
const confirmId = ref<string | null>(null);
const copiedNew = ref(false);

const fmt = (iso: string): string => new Date(iso).toLocaleString(locale.value);

async function loadTokens(): Promise<void> {
  tokens.value = (await api.get<TokensListResponse>('/api/v1/auth/tokens')).tokens;
}

async function createToken(): Promise<void> {
  const label = newLabel.value.trim();
  if (!label) return;
  creating.value = true;
  try {
    createdToken.value = await api.post<CreateTokenResponse>('/api/v1/auth/tokens', { label });
    newLabel.value = '';
    toasts.success(t('settings.tokenCreated'));
    await loadTokens();
  } catch (e) {
    toasts.error(e);
  } finally {
    creating.value = false;
  }
}

async function revoke(tok: TokenInfo): Promise<void> {
  if (confirmId.value !== tok.id) {
    confirmId.value = tok.id;
    return;
  }
  confirmId.value = null;
  try {
    await api.delete(`/api/v1/auth/tokens/${tok.id}`);
    toasts.success(t('settings.tokenRevoked'));
    await loadTokens();
  } catch (e) {
    toasts.error(e instanceof ApiError && e.code === 'LAST_TOKEN' ? t('settings.lastTokenError') : e);
  }
}

async function copy(text: string, target?: 'new'): Promise<void> {
  try {
    await navigator.clipboard.writeText(text);
    if (target === 'new') {
      copiedNew.value = true;
      setTimeout(() => (copiedNew.value = false), 1500);
    }
  } catch {
    /* presse-papiers indisponible (http non sécurisé) : silencieux */
  }
}

// ---- Gitea ----
const giteaInput = ref('');
const giteaDirty = computed(() => giteaInput.value.trim() !== (settings.giteaUrl ?? ''));
async function saveGitea(): Promise<void> {
  try {
    await settings.save({ giteaUrl: giteaInput.value.trim() || null });
    giteaInput.value = settings.giteaUrl ?? '';
    toasts.success(t('settings.saved'));
  } catch (e) {
    toasts.error(e);
  }
}

// ---- Découverte des dépôts ----
const rootsDraft = ref<string[]>([]);
const intervalDraft = ref(0);
const showRootPicker = ref(false);
const discoveryDirty = computed(
  () =>
    intervalDraft.value !== settings.scanIntervalMin ||
    rootsDraft.value.length !== settings.scanRoots.length ||
    rootsDraft.value.some((r, i) => r !== settings.scanRoots[i]),
);
function syncDiscoveryDraft(): void {
  rootsDraft.value = [...settings.scanRoots];
  intervalDraft.value = settings.scanIntervalMin;
}
function onAddRoot(path: string): void {
  if (!rootsDraft.value.includes(path)) rootsDraft.value.push(path);
  showRootPicker.value = false;
}
async function saveDiscovery(): Promise<void> {
  try {
    await settings.save({ scanRoots: rootsDraft.value, scanIntervalMin: intervalDraft.value });
    syncDiscoveryDraft();
    toasts.success(t('settings.saved'));
  } catch (e) {
    toasts.error(e);
  }
}

onMounted(async () => {
  await Promise.all([loadTokens(), settings.loaded ? Promise.resolve() : settings.fetch()]);
  giteaInput.value = settings.giteaUrl ?? '';
  syncDiscoveryDraft();
  void push.refresh();
});
</script>
