{% extends "base.html" %} {% import "partials/icons.html" as icons %}
{% block title %}Brewlog · Admin{% endblock %}
{% block head %}
  <script src="/static/js/webauthn.js"></script>
{% endblock %}
{% block content %}
  <header class="flex flex-col gap-2">
    <h1 class="text-3xl font-semibold">Admin</h1>
    <p class="max-w-2xl text-sm text-text-secondary">
      Manage passkeys, API tokens, and data.
    </p>
  </header>

  <!-- Passkeys -->
  <section
    data-signals:_show-passkey-form="false"
    class="rounded-lg border bg-surface p-5"
  >
    <div class="flex flex-col gap-4">
      <div>
        <h2 class="text-lg font-semibold text-text">Passkeys</h2>
        <p class="mt-1 text-sm text-text-secondary">
          Passkeys enable secure sign-in without a password.
        </p>
      </div>

      <div class="flex flex-col gap-2">
        {% for passkey in passkeys %}
          <div
            class="flex items-center justify-between gap-4 rounded-md bg-surface-alt px-4 py-3"
          >
            <div class="flex items-center gap-3 min-w-0">
              {{ icons::key("h-4 w-4 text-accent shrink-0") }}
              <div class="min-w-0">
                <span class="block text-sm font-semibold text-text"
                  >{{ passkey.name }}</span
                >
                <span class="block text-xs text-text-muted">
                  Added {{ passkey.created_at }} ·
                  {% if let Some(last_used) = passkey.last_used_at %}
                    Last used {{ last_used }}
                  {% else %}
                    Never used
                  {% endif %}
                </span>
              </div>
            </div>
            {% if passkeys.len() > 1 %}
              <button
                type="button"
                class="shrink-0 inline-flex items-center justify-center rounded-md border text-accent transition hover:text-text hover:bg-surface-alt h-8 w-8 sm:h-auto sm:w-auto sm:gap-2 sm:px-4 sm:py-2 sm:text-sm sm:font-medium"
                onclick="deletePasskey({{ passkey.id }}, '{{ passkey.name }}')"
                aria-label="Delete passkey"
              >
                {{ icons::delete("h-4 w-4") }}
                <span class="hidden sm:inline">Delete</span>
              </button>
            {% endif %}
          </div>
        {% endfor %}
      </div>

      {% if passkeys.len() <= 1 %}
        <p class="text-xs text-text-muted">
          Add another passkey before removing the only one.
        </p>
      {% endif %}

      <!-- Add passkey form -->
      <div
        id="add-passkey-form"
        class="rounded-md border bg-surface-alt p-4"
        data-show="$_showPasskeyForm"
        style="display: none"
      >
        <div
          id="add-passkey-error"
          class="mb-3 hidden rounded-md bg-error-bg border border-error-border p-2 text-sm text-error-text"
          role="alert"
        ></div>
        <div class="flex flex-col gap-3 sm:flex-row sm:items-end">
          <label class="flex flex-col gap-1 text-sm sm:flex-1">
            <span class="text-text">Passkey Name</span>
            <input
              type="text"
              id="passkey-name"
              required
              aria-required="true"
              class="input-field"
              placeholder="e.g. MacBook Touch ID, iPhone"
            />
          </label>
          <div class="flex gap-3">
            <button
              id="add-passkey-btn"
              type="button"
              onclick="registerPasskey()"
              class="flex-1 inline-flex items-center justify-center gap-2 rounded-md bg-accent px-4 py-2 text-sm font-semibold text-accent-text transition hover:bg-accent-hover disabled:opacity-50 disabled:cursor-not-allowed sm:flex-initial"
            >
              {{ icons::key("h-4 w-4") }} Register
            </button>
            <button
              type="button"
              data-on:click="$_showPasskeyForm = false; document.getElementById('add-passkey-error').classList.add('hidden'); document.getElementById('passkey-name').value = ''"
              class="flex-1 inline-flex items-center justify-center gap-2 rounded-md border px-4 py-2 text-sm font-medium text-text transition hover:bg-surface-alt sm:flex-initial"
            >
              {{ icons::x_mark("h-4 w-4") }} Cancel
            </button>
          </div>
        </div>
        <div
          id="add-passkey-loading"
          class="mt-3 hidden flex items-center gap-3 text-sm text-accent"
        >
          {{ icons::spinner("h-5 w-5") }} Follow the prompts from the browser or
          device...
        </div>
      </div>

      <div>
        <button
          type="button"
          data-show="!$_showPasskeyForm"
          data-on:click="$_showPasskeyForm = true; setTimeout(() => document.getElementById('passkey-name').focus(), 50)"
          class="inline-flex w-full items-center justify-center gap-2 rounded-md bg-accent px-4 py-2 text-sm font-semibold text-accent-text transition hover:bg-accent-hover sm:w-auto sm:min-w-44"
        >
          {{ icons::key("h-4 w-4") }} New Passkey
        </button>
      </div>
    </div>
  </section>

  <!-- API Tokens -->
  <section
    class="rounded-lg border bg-surface p-5"
    data-signals:_show-token-form="false"
    data-signals:_creating-token="false"
    data-signals:_token-created="false"
    data-signals:_token-value="''"
    data-signals:_token-error="''"
  >
    <div class="flex flex-col gap-4">
      <div>
        <h2 class="text-lg font-semibold text-text">API Tokens</h2>
        <p class="mt-1 text-sm text-text-secondary">
          Use tokens to authenticate the CLI or REST API.
        </p>
      </div>

      {% if tokens.is_empty() %}
        <p class="text-sm text-text-muted">No active tokens.</p>
      {% else %}
        <div class="flex flex-col gap-2">
          {% for token in tokens %}
            <div
              id="token-row-{{ token.id }}"
              class="flex items-center justify-between gap-4 rounded-md bg-surface-alt px-4 py-3"
            >
              <div class="flex items-center gap-3 min-w-0">
                {{ icons::terminal("h-4 w-4 text-accent shrink-0") }}
                <div class="min-w-0">
                  <span class="block text-sm font-semibold text-text"
                    >{{ token.name }}</span
                  >
                  <span class="block text-xs text-text-muted">
                    Created {{ token.created_at }} ·
                    {% if let Some(last_used) = token.last_used_at %}
                      Last used {{ last_used }}
                    {% else %}
                      Never used
                    {% endif %}
                  </span>
                </div>
              </div>
              <button
                type="button"
                class="shrink-0 inline-flex items-center justify-center rounded-md border text-accent transition hover:text-text hover:bg-surface-alt h-8 w-8 sm:h-auto sm:w-auto sm:gap-2 sm:px-4 sm:py-2 sm:text-sm sm:font-medium"
                onclick="revokeToken({{ token.id }}, '{{ token.name }}')"
                aria-label="Revoke token"
              >
                {{ icons::delete("h-4 w-4") }}
                <span class="hidden sm:inline">Revoke</span>
              </button>
            </div>
          {% endfor %}
        </div>
      {% endif %}

      <!-- Create token form -->
      <div
        class="rounded-md border bg-surface-alt p-4"
        data-show="$_showTokenForm && !$_tokenCreated"
        style="display: none"
      >
        <p
          data-show="$_tokenError"
          data-text="$_tokenError"
          style="display: none"
          class="mb-3 rounded-md bg-error-bg border border-error-border p-2 text-sm text-error-text"
          role="alert"
        ></p>
        <form
          data-on:submit="$_creatingToken = true; $_tokenError = ''; @post('/api/v1/tokens', {contentType: 'form'})"
          data-on:datastar-fetch="if (!$_creatingToken) return;
          if (evt.detail.type === 'finished') { $_creatingToken = false; document.getElementById('token-name').value = '' }
          else if (evt.detail.type === 'error') { $_creatingToken = false; $_tokenError = 'Failed to create token.' }"
        >
          <div class="flex flex-col gap-3 sm:flex-row sm:items-end">
            <label class="flex flex-col gap-1 text-sm sm:flex-1">
              <span class="text-text">Token Name</span>
              <input
                type="text"
                name="name"
                id="token-name"
                required
                aria-required="true"
                class="input-field"
                placeholder="e.g. laptop-cli, ci-server"
              />
            </label>
            <div class="flex gap-3">
              <button
                type="submit"
                class="flex-1 inline-flex items-center justify-center gap-2 rounded-md bg-accent px-4 py-2 text-sm font-semibold text-accent-text transition hover:bg-accent-hover disabled:opacity-50 disabled:cursor-not-allowed sm:flex-initial"
                data-attr:disabled="$_creatingToken"
              >
                {{ icons::plus("h-4 w-4") }} Create
              </button>
              <button
                type="button"
                data-on:click="$_showTokenForm = false; $_tokenError = ''"
                class="flex-1 inline-flex items-center justify-center gap-2 rounded-md border px-4 py-2 text-sm font-medium text-text transition hover:bg-surface-alt sm:flex-initial"
              >
                {{ icons::x_mark("h-4 w-4") }} Cancel
              </button>
            </div>
          </div>
        </form>
      </div>

      <!-- One-time token display -->
      <div
        data-show="$_tokenCreated"
        style="display: none"
        class="relative rounded-md border border-success-border bg-success-bg p-4"
      >
        <button
          type="button"
          onclick="window.location.reload()"
          class="absolute top-3.5 right-3 inline-flex h-6 w-6 items-center justify-center rounded text-success-text transition hover:text-accent-text"
          aria-label="Dismiss"
        >
          {{ icons::x_mark("h-4 w-4") }}
        </button>
        <p class="pr-6 text-sm font-medium text-success-text">
          Token created! Copy it now — it will not be shown again.
        </p>
        <div class="mt-3 flex items-center gap-2">
          <code
            id="token-value"
            data-text="$_tokenValue"
            class="flex-1 rounded bg-surface px-3 py-2 text-sm font-mono text-text border border-success-border break-all select-all"
          ></code>
          <button
            type="button"
            onclick="copyToken(this)"
            class="shrink-0 inline-flex items-center gap-2 rounded-md bg-success px-3 py-1.5 text-sm font-medium text-accent-text transition hover:bg-success"
          >
            {{ icons::clipboard("h-4 w-4") }} Copy
          </button>
        </div>
      </div>

      <div>
        <button
          type="button"
          data-show="!$_showTokenForm && !$_tokenCreated"
          data-on:click="$_showTokenForm = true; setTimeout(() => document.getElementById('token-name').focus(), 50)"
          class="inline-flex w-full items-center justify-center gap-2 rounded-md bg-accent px-4 py-2 text-sm font-semibold text-accent-text transition hover:bg-accent-hover sm:w-auto sm:min-w-44"
        >
          {{ icons::terminal("h-4 w-4") }} New Token
        </button>
      </div>
    </div>
  </section>

  <!-- Data -->
  <section class="rounded-lg border bg-surface p-5">
    <div class="flex flex-col gap-4">
      <div>
        <h2 class="text-lg font-semibold text-text">Data</h2>
        <p class="mt-1 text-sm text-text-secondary">
          Export all coffee data as JSON, restore from a previous backup, or
          reset to start fresh.
        </p>
      </div>

      <div class="flex flex-col gap-3 sm:flex-row sm:flex-wrap">
        <a
          href="/api/v1/backup"
          download
          class="inline-flex w-full items-center justify-center gap-2 rounded-md bg-accent px-4 py-2 text-sm font-semibold text-accent-text transition hover:bg-accent-hover sm:w-auto sm:min-w-44"
        >
          {{ icons::arrow_down_tray("h-4 w-4") }} New Backup
        </a>
        <button
          type="button"
          class="inline-flex w-full items-center justify-center gap-2 rounded-md border px-4 py-2 text-sm font-medium text-accent transition hover:text-text hover:bg-surface-alt sm:w-auto sm:min-w-44"
          onclick="document.getElementById('restore-file-input').click()"
        >
          {{ icons::arrow_up_tray("h-4 w-4") }} Restore
        </button>
        <button
          type="button"
          data-signals:_recomputing="false"
          data-on:click="$_recomputing = true; @post('/api/v1/stats/recompute')"
          data-on:datastar-fetch="if (!$_recomputing) return;
          if (evt.detail.type === 'finished') { $_recomputing = false; document.getElementById('backup-status').textContent = 'Stats recomputed successfully.'; document.getElementById('backup-status').classList.remove('hidden') }
          else if (evt.detail.type === 'error') { $_recomputing = false; document.getElementById('backup-error').textContent = 'Stats recompute failed.'; document.getElementById('backup-error').classList.remove('hidden') }"
          data-attr:disabled="$_recomputing"
          class="inline-flex w-full items-center justify-center gap-2 rounded-md border px-4 py-2 text-sm font-medium text-accent transition hover:text-text hover:bg-surface-alt disabled:opacity-50 disabled:cursor-not-allowed sm:w-auto sm:min-w-44"
        >
          <span data-show="!$_recomputing"
            >{{ icons::refresh("h-4 w-4") }}</span
          >
          <span data-show="$_recomputing" style="display:none"
            >{{ icons::spinner("h-4 w-4") }}</span
          >
          Recompute Stats
        </button>
        <button
          type="button"
          class="inline-flex w-full items-center justify-center gap-2 rounded-md border px-4 py-2 text-sm font-medium text-accent transition hover:text-text hover:bg-surface-alt sm:w-auto sm:min-w-44"
          onclick="resetDatabase()"
        >
          {{ icons::delete("h-4 w-4") }} Reset
        </button>
      </div>

      <input
        type="file"
        id="restore-file-input"
        accept=".json"
        class="hidden"
        onchange="restoreFromFile(this)"
      />

      <div
        id="backup-status"
        class="hidden rounded-md border border-success-border bg-success-bg p-4 text-sm text-success-text"
      ></div>
      <div
        id="backup-error"
        class="hidden rounded-md bg-error-bg border border-error-border p-3 text-sm text-error-text"
        role="alert"
      ></div>
    </div>
  </section>

  <!-- AI Usage -->
  {% if let Some(usage) = ai_usage %}
    <section class="rounded-lg border bg-surface p-5">
      <div class="flex flex-col gap-4">
        <h2 class="text-lg font-semibold text-text">AI Usage</h2>

        <div class="grid grid-cols-3 gap-4">
          <div>
            <span class="block text-sm text-text-muted">Total Cost</span>
            <span class="mt-1 block text-lg font-semibold text-text"
              >{{ usage.cost }}</span
            >
          </div>
          <div>
            <span class="block text-sm text-text-muted">API Calls</span>
            <span class="mt-1 block text-lg font-semibold text-text"
              >{{ usage.calls }}</span
            >
          </div>
          <div>
            <span class="block text-sm text-text-muted">Total Tokens</span>
            <span class="mt-1 block text-lg font-semibold text-text"
              >{{ usage.tokens }}</span
            >
          </div>
        </div>
      </div>
    </section>
  {% endif %}

  <!-- Sign Out -->
  <section class="rounded-lg border bg-surface p-5">
    <div class="flex flex-col gap-4">
      <div>
        <h2 class="text-lg font-semibold text-text">Sign Out</h2>
        <p class="mt-1 text-sm text-text-secondary">Sign out on this device.</p>
      </div>
      <div>
        <form method="post" action="/logout">
          <button
            type="submit"
            class="inline-flex w-full items-center justify-center gap-2 rounded-md border px-4 py-2 text-sm font-medium text-accent transition hover:text-text hover:bg-surface-alt sm:w-auto sm:min-w-44"
          >
            {{ icons::logout("h-4 w-4") }} Sign Out
          </button>
        </form>
      </div>
    </div>
  </section>

  <script>
    // --- Passkey form ---

    const registerPasskey = async () => {
      const name = document.getElementById("passkey-name").value.trim();
      const errorEl = document.getElementById("add-passkey-error");
      const loadingEl = document.getElementById("add-passkey-loading");
      const btn = document.getElementById("add-passkey-btn");

      if (!name) {
        errorEl.textContent = "Please enter a name for this passkey.";
        errorEl.classList.remove("hidden");
        return;
      }

      errorEl.classList.add("hidden");
      loadingEl.classList.remove("hidden");
      btn.disabled = true;

      try {
        await addPasskey(name);
        window.location.reload();
      } catch (err) {
        errorEl.textContent = err.message;
        errorEl.classList.remove("hidden");
        loadingEl.classList.add("hidden");
        btn.disabled = false;
      }
    };

    // --- Token ---

    const copyToken = (btn) => {
      const token = document.getElementById("token-value").textContent;
      if (navigator.clipboard) {
        navigator.clipboard.writeText(token).then(() => {
          btn.textContent = "Copied!";
          setTimeout(() => {
            btn.textContent = "Copy";
          }, 2000);
        });
      } else {
        const range = document.createRange();
        range.selectNodeContents(document.getElementById("token-value"));
        const selection = window.getSelection();
        selection.removeAllRanges();
        selection.addRange(range);
      }
    };

    // --- Data management ---

    const restoreFromFile = async (input) => {
      const file = input.files[0];
      if (!file) return;
      input.value = "";

      if (
        !confirm(
          "Restore from backup? This will replace all data.\n\nThe database must be empty for restore to succeed.",
        )
      ) {
        return;
      }

      const status = document.getElementById("backup-status");
      const error = document.getElementById("backup-error");
      status.classList.add("hidden");
      error.classList.add("hidden");

      try {
        const text = await file.text();
        JSON.parse(text);

        const response = await fetch("/api/v1/backup/restore", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: text,
        });

        if (response.status === 409) {
          throw new Error(
            "Database is not empty. Restore requires an empty database.",
          );
        }
        if (!response.ok) {
          throw new Error(`Restore failed (HTTP ${response.status}).`);
        }

        status.textContent = "Backup restored successfully.";
        status.classList.remove("hidden");
      } catch (err) {
        error.textContent =
          err instanceof SyntaxError ? "Invalid JSON file." : err.message;
        error.classList.remove("hidden");
      }
    };

    const resetDatabase = async () => {
      if (
        !confirm(
          "Reset all coffee data?\n\nThis will permanently delete all roasters, roasts, bags, gear, brews, cafes, cups, and timeline events.\n\nThis cannot be undone.",
        )
      ) {
        return;
      }

      if (!confirm("Confirm? All coffee data will be permanently deleted.")) {
        return;
      }

      const status = document.getElementById("backup-status");
      const error = document.getElementById("backup-error");
      status.classList.add("hidden");
      error.classList.add("hidden");

      try {
        const response = await fetch("/api/v1/backup/reset", {
          method: "POST",
        });

        if (!response.ok) {
          throw new Error(`Reset failed (HTTP ${response.status}).`);
        }

        status.textContent =
          "Database reset successfully. All coffee data has been deleted.";
        status.classList.remove("hidden");
      } catch (err) {
        error.textContent = err.message;
        error.classList.remove("hidden");
      }
    };

    const deletePasskey = async (id, name) => {
      if (!confirm(`Delete passkey "${name}"? This cannot be undone.`)) return;

      try {
        const response = await fetch(`/api/v1/passkeys/${id}`, {
          method: "DELETE",
        });
        if (response.ok) {
          window.location.reload();
        } else if (response.status === 409) {
          alert("Cannot delete the only passkey. Add another passkey first.");
        } else {
          alert("Failed to delete passkey.");
        }
      } catch (err) {
        alert(`Failed to delete passkey: ${err.message}`);
      }
    };

    const revokeToken = async (id, name) => {
      if (!confirm(`Revoke token "${name}"? This cannot be undone.`)) return;

      try {
        const response = await fetch(`/api/v1/tokens/${id}/revoke`, {
          method: "POST",
        });
        if (response.ok) {
          const row = document.getElementById(`token-row-${id}`);
          if (row) row.remove();
        } else {
          alert("Failed to revoke token.");
        }
      } catch (err) {
        alert(`Failed to revoke token: ${err.message}`);
      }
    };
  </script>
{% endblock %}
