{% extends "pages/base.html" %}
{% load i18n %}

{% block title %}{% trans "Login" %}{% endblock %}

{% block content %}
<div class="d-flex justify-content-center">
  <div class="card p-4" style="max-width: 400px; width: 100%;">
    <h1 class="text-center mb-4">{% trans "Login" %}</h1>
    {% if restricted_notice %}
    <div class="alert alert-info" role="alert">
      {{ restricted_notice }}
    </div>
    {% endif %}
    {% with current_method=form.auth_method.value|default:'password' otp_password_required=form.otp_requires_password.value|default:'1' otp_ready=form.otp_ready.value|default:'0' %}
    <form method="post" novalidate data-auth-form data-auth-check-url="{{ authenticator_check_url }}">
      {% csrf_token %}
      {% if login_check_mode %}
      <input type="hidden" name="check" value="1">
      {% endif %}
      {% if form.non_field_errors %}
      <div class="alert alert-danger" role="alert">
        {% for error in form.non_field_errors %}{{ error }}{% if not forloop.last %}<br>{% endif %}{% endfor %}
      </div>
      {% endif %}
      <div class="mb-3">
        <label for="{{ form.username.id_for_label }}" class="form-label">{{ form.username.label }}</label>
        <input
          type="text"
          name="{{ form.username.html_name }}"
          class="form-control{% if form.username.errors %} is-invalid{% endif %}"
          id="{{ form.username.id_for_label }}"
          value="{{ form.username.value|default_if_none:'' }}"
          autocomplete="username"
          required
          autofocus
          {% if username_readonly %}readonly aria-readonly="true"{% endif %}
        >
        {% for error in form.username.errors %}
        <div class="invalid-feedback">{{ error }}</div>
        {% endfor %}
      </div>
      {{ form.auth_method }}
      {{ form.otp_ready }}
      {{ form.otp_requires_password }}
      {{ form.otp_password_optional }}
      <div class="mb-3{% if current_method != 'otp' or otp_ready == '1' %} d-none{% endif %}" data-otp-precheck>
        <div class="alert alert-info mb-3" role="status">
          {% trans "Enter your username and continue so we can check your authenticator settings." %}
        </div>
        <div class="alert alert-danger d-none" role="alert" data-otp-precheck-error></div>
        <button
          type="button"
          class="btn btn-outline-primary w-100"
          data-otp-precheck-button
          data-checking-label="{% trans "Checking…" %}"
        >
          {% trans "Continue" %}
        </button>
      </div>
      <div class="mb-3{% if current_method == 'otp' and otp_password_required != '1' %} d-none{% endif %}" data-password-group>
        <label for="{{ form.password.id_for_label }}" class="form-label">{{ form.password.label }}</label>
        <input
          type="password"
          name="{{ form.password.html_name }}"
          class="form-control{% if form.password.errors %} is-invalid{% endif %}"
          id="{{ form.password.id_for_label }}"
          autocomplete="current-password"
          {% if current_method != 'otp' %}required{% endif %}
        >
        {% for error in form.password.errors %}
        <div class="invalid-feedback">{{ error }}</div>
        {% endfor %}
      </div>
      <div class="mb-3{% if current_method != 'otp' or otp_ready != '1' %} d-none{% endif %}" data-otp-group>
        <label for="{{ form.otp_token.id_for_label }}" class="form-label">{{ form.otp_token.label }}</label>
        <input
          type="text"
          name="{{ form.otp_token.html_name }}"
          class="form-control{% if form.otp_token.errors %} is-invalid{% endif %}"
          id="{{ form.otp_token.id_for_label }}"
          value="{{ form.otp_token.value|default_if_none:'' }}"
          inputmode="numeric"
          pattern="[0-9]*"
          autocomplete="one-time-code"
          {% if current_method == 'otp' %}required{% endif %}
        >
        {% for error in form.otp_token.errors %}
        <div class="invalid-feedback">{{ error }}</div>
        {% endfor %}
        <div class="form-text">{% trans "Enter the code from your authenticator app." %}</div>
      </div>
      {% if next %}<input type="hidden" name="next" value="{{ next }}">{% endif %}
      <button type="submit" class="btn btn-primary w-100">{% trans "Log in" %}</button>
    </form>
    {% if passkey_login_enabled %}
    <div class="mt-3" data-passkey-container>
      <button
        type="button"
        class="btn btn-outline-primary w-100"
        data-passkey-login
        data-options-url="{{ passkey_login_options_url }}"
        data-verify-url="{{ passkey_login_verify_url }}"
        data-next-value="{{ next|default_if_none:'' }}"
        data-error-label="{% trans "Unable to use your passkey. Please try again." %}"
        data-progress-label="{% trans "Confirm the passkey prompt to continue." %}"
        data-success-label="{% trans "Passkey accepted. Signing you in…" %}"
        data-browser-error="{% trans "Passkeys are not supported in this browser yet." %}"
        data-redirect-field="{{ view.redirect_field_name }}"
      >
        {% trans "Use a passkey" %}
      </button>
      <div class="mt-2 small text-muted d-none" data-passkey-status></div>
    </div>
    {% endif %}
    <div class="mt-3 text-center">
      <a
        href="#"
        data-auth-toggle
        data-password-label="{% trans "Use Password" %}"
        data-otp-label="{% trans "Use Authenticator app" %}"
      >
        {% if current_method == 'otp' %}{% trans "Use Password" %}{% else %}{% trans "Use Authenticator app" %}{% endif %}
      </a>
    </div>
    {% if show_rfid_login %}
    <div class="mt-3 text-center">
      <a href="{% if rfid_login_url %}{{ rfid_login_url }}{% else %}{% url 'pages:rfid-login' %}{% endif %}">{% trans "Log in with RFID" %}</a>
    </div>
    {% endif %}
    {% endwith %}
    {% if can_request_invite %}
    <div class="mt-3 text-center">
      <a href="{% url 'pages:request-invite' %}">{% trans "Request email invitation" %}</a>
    </div>
    {% endif %}
  </div>
</div>
<script>
(function () {
  const form = document.querySelector('[data-auth-form]');
  const toggle = document.querySelector('[data-auth-toggle]');
  const passwordGroup = form?.querySelector('[data-password-group]');
  const otpGroup = form?.querySelector('[data-otp-group]');
  const otpPrecheck = form?.querySelector('[data-otp-precheck]');
  const otpPrecheckButton = form?.querySelector('[data-otp-precheck-button]');
  const otpPrecheckError = form?.querySelector('[data-otp-precheck-error]');
  const authMethodField = form?.querySelector('input[name="{{ form.auth_method.html_name }}"]');
  const otpReadyField = form?.querySelector('input[name="{{ form.otp_ready.html_name }}"]');
  const otpRequiresPasswordField = form?.querySelector('input[name="{{ form.otp_requires_password.html_name }}"]');
  const otpPasswordOptionalField = form?.querySelector('input[name="{{ form.otp_password_optional.html_name }}"]');
  const passwordInput = form?.querySelector('#{{ form.password.id_for_label }}');
  const otpInput = form?.querySelector('#{{ form.otp_token.id_for_label }}');
  const usernameInput = form?.querySelector('#{{ form.username.id_for_label }}');
  const passwordLabel = toggle?.getAttribute('data-password-label');
  const otpLabel = toggle?.getAttribute('data-otp-label');
  const authCheckUrl = form?.getAttribute('data-auth-check-url') || '';

  const clearOtpError = () => {
    if (!otpPrecheckError) {
      return;
    }
    otpPrecheckError.textContent = '';
    otpPrecheckError.classList.add('d-none');
  };

  const showOtpError = (message) => {
    if (!otpPrecheckError) {
      return;
    }
    otpPrecheckError.textContent = message;
    otpPrecheckError.classList.remove('d-none');
  };

  const resetOtpState = () => {
    if (otpReadyField) {
      otpReadyField.value = '0';
    }
    if (otpRequiresPasswordField) {
      otpRequiresPasswordField.value = '1';
    }
    if (otpPasswordOptionalField) {
      otpPasswordOptionalField.value = '0';
    }
    clearOtpError();
  };

  const updateOtpVisibility = () => {
    const ready = otpReadyField?.value === '1';
    const requiresPassword = otpRequiresPasswordField?.value !== '0';
    const passwordOptional = otpPasswordOptionalField?.value === '1';
    if (!ready) {
      otpPrecheck?.classList.remove('d-none');
      otpGroup?.classList.add('d-none');
      otpInput?.removeAttribute('required');
      passwordGroup?.classList.add('d-none');
      passwordInput?.removeAttribute('required');
      return;
    }
    otpPrecheck?.classList.add('d-none');
    otpGroup?.classList.remove('d-none');
    if (otpInput) {
      otpInput.setAttribute('required', 'required');
    }
    if (requiresPassword) {
      passwordGroup?.classList.remove('d-none');
      if (passwordInput) {
        if (passwordOptional) {
          passwordInput.removeAttribute('required');
        } else {
          passwordInput.setAttribute('required', 'required');
        }
      }
    } else {
      passwordGroup?.classList.add('d-none');
      passwordInput?.removeAttribute('required');
    }
  };

  const setMode = (mode, options = {}) => {
    if (!authMethodField) {
      return;
    }
    const preserveOtpState = Boolean(options.preserveOtpState);
    authMethodField.value = mode;
    if (mode === 'otp') {
      if (!preserveOtpState) {
        resetOtpState();
      }
      updateOtpVisibility();
      if (toggle && passwordLabel) {
        toggle.textContent = passwordLabel;
      }
    } else {
      if (!preserveOtpState) {
        resetOtpState();
      }
      otpPrecheck?.classList.add('d-none');
      otpGroup?.classList.add('d-none');
      otpInput?.removeAttribute('required');
      passwordGroup?.classList.remove('d-none');
      if (passwordInput) {
        passwordInput.setAttribute('required', 'required');
        passwordInput.focus();
      }
      if (toggle && otpLabel) {
        toggle.textContent = otpLabel;
      }
    }
  };

  if (toggle) {
    toggle.addEventListener('click', (event) => {
      event.preventDefault();
      const nextMode = authMethodField && authMethodField.value === 'otp' ? 'password' : 'otp';
      setMode(nextMode);
    });
  }

  const initialMode = authMethodField && authMethodField.value === 'otp' ? 'otp' : 'password';
  setMode(initialMode, { preserveOtpState: true });

  const csrfToken = form?.querySelector('input[name="csrfmiddlewaretoken"]')?.value || '';

  const setPrecheckBusy = (isBusy) => {
    if (!otpPrecheckButton) {
      return;
    }
    otpPrecheckButton.disabled = isBusy;
    const defaultLabel = otpPrecheckButton.dataset.originalLabel || otpPrecheckButton.textContent;
    if (!otpPrecheckButton.dataset.originalLabel) {
      otpPrecheckButton.dataset.originalLabel = defaultLabel;
    }
    const checkingLabel = otpPrecheckButton.getAttribute('data-checking-label') || defaultLabel;
    otpPrecheckButton.textContent = isBusy ? checkingLabel : otpPrecheckButton.dataset.originalLabel;
  };

  const runOtpPrecheck = async () => {
    if (!authCheckUrl) {
      return;
    }
    const usernameValue = (usernameInput?.value || '').trim();
    if (!usernameValue) {
      showOtpError('{% trans "Enter your username before continuing." %}');
      usernameInput?.focus();
      return;
    }
    setPrecheckBusy(true);
    clearOtpError();
    try {
      const params = new URLSearchParams();
      params.append('username', usernameValue);
      if (csrfToken) {
        params.append('csrfmiddlewaretoken', csrfToken);
      }
      const response = await fetch(authCheckUrl, {
        method: 'POST',
        headers: { 'X-Requested-With': 'XMLHttpRequest' },
        body: params,
      });
      const data = await response.json();
      if (!response.ok) {
        showOtpError(data?.error || '{% trans "Unable to check the authenticator settings right now." %}');
        return;
      }
      if (otpReadyField) {
        otpReadyField.value = '1';
      }
      if (otpRequiresPasswordField) {
        otpRequiresPasswordField.value = data.requires_password ? '1' : '0';
      }
      if (otpPasswordOptionalField) {
        otpPasswordOptionalField.value = data.password_optional ? '1' : '0';
      }
      updateOtpVisibility();
      if (data.requires_password) {
        passwordInput?.focus();
      } else {
        otpInput?.focus();
      }
    } catch (error) {
      showOtpError('{% trans "Unable to check the authenticator settings right now." %}');
    } finally {
      setPrecheckBusy(false);
    }
  };

  if (otpPrecheckButton) {
    otpPrecheckButton.addEventListener('click', (event) => {
      event.preventDefault();
      if (authMethodField && authMethodField.value !== 'otp') {
        setMode('otp');
      }
      runOtpPrecheck();
    });
  }

  const replaceAll = (source, search, replacement) => {
    if (!search) {
      return source;
    }
    return source.split(search).join(replacement);
  };

  const stripPadding = (value, padChar) => {
    if (!padChar) {
      return value;
    }
    let end = value.length;
    while (end > 0 && value.charAt(end - 1) === padChar) {
      end -= 1;
    }
    return value.slice(0, end);
  };

  const base64urlToBuffer = (value) => {
    if (!value) {
      return new ArrayBuffer(0);
    }
    const padded = replaceAll(replaceAll(value, '-', '+'), '_', '/');
    const base64 = padded + '='.repeat((4 - (padded.length % 4)) % 4);
    const binary = window.atob(base64);
    const buffer = new ArrayBuffer(binary.length);
    const bytes = new Uint8Array(buffer);
    for (let i = 0; i < binary.length; i += 1) {
      bytes[i] = binary.charCodeAt(i);
    }
    return buffer;
  };

  const bufferToBase64url = (buffer) => {
    const bytes = new Uint8Array(buffer);
    let binary = '';
    bytes.forEach((value) => {
      binary += String.fromCharCode(value);
    });
    const base64 = window.btoa(binary);
    const urlSafe = replaceAll(replaceAll(base64, '+', '-'), '/', '_');
    return stripPadding(urlSafe, '=');
  };

  const preparePublicKeyOptions = (options) => {
    const prepared = { ...options };
    if (prepared.challenge) {
      prepared.challenge = base64urlToBuffer(prepared.challenge);
    }
    if (Array.isArray(prepared.allowCredentials)) {
      prepared.allowCredentials = prepared.allowCredentials.map((item) => ({
        ...item,
        id: base64urlToBuffer(item.id),
      }));
    }
    return prepared;
  };

  const serializeCredential = (credential) => {
    const response = credential.response || {};
    const serialized = {
      id: credential.id,
      type: credential.type,
      rawId: bufferToBase64url(credential.rawId),
      response: {},
      clientExtensionResults:
        typeof credential.getClientExtensionResults === 'function'
          ? credential.getClientExtensionResults()
          : {},
    };
    if (response.clientDataJSON) {
      serialized.response.clientDataJSON = bufferToBase64url(response.clientDataJSON);
    }
    if (response.authenticatorData) {
      serialized.response.authenticatorData = bufferToBase64url(response.authenticatorData);
    }
    if (response.signature) {
      serialized.response.signature = bufferToBase64url(response.signature);
    }
    if (response.userHandle) {
      serialized.response.userHandle = bufferToBase64url(response.userHandle);
    }
    if (response.attestationObject) {
      serialized.response.attestationObject = bufferToBase64url(response.attestationObject);
    }
    if (typeof response.getTransports === 'function') {
      serialized.transports = response.getTransports();
    }
    return serialized;
  };

  const postJson = async (url, payload) => {
    const headers = { 'Content-Type': 'application/json' };
    if (csrfToken) {
      headers['X-CSRFToken'] = csrfToken;
    }
    const response = await fetch(url, {
      method: 'POST',
      headers,
      body: JSON.stringify(payload),
      credentials: 'same-origin',
    });
    let data = {};
    try {
      data = await response.json();
    } catch (error) {
      data = {};
    }
    if (!response.ok) {
      const requestError = new Error(data.error || 'Request failed');
      requestError.payload = data;
      throw requestError;
    }
    return data;
  };

  const passkeyContainer = document.querySelector('[data-passkey-container]');
  const passkeyButton = passkeyContainer?.querySelector('[data-passkey-login]');
  const passkeyStatus = passkeyContainer?.querySelector('[data-passkey-status]');
  const updateStatus = (message, tone = 'info') => {
    if (!passkeyStatus) {
      return;
    }
    passkeyStatus.classList.remove('d-none', 'text-danger', 'text-success', 'text-muted');
    if (!message) {
      passkeyStatus.classList.add('d-none');
      passkeyStatus.textContent = '';
      return;
    }
    passkeyStatus.textContent = message;
    if (tone === 'error') {
      passkeyStatus.classList.add('text-danger');
    } else if (tone === 'success') {
      passkeyStatus.classList.add('text-success');
    } else {
      passkeyStatus.classList.add('text-muted');
    }
  };

  if (passkeyContainer && passkeyButton) {
    if (!window.PublicKeyCredential) {
      passkeyContainer.classList.add('d-none');
    } else {
      passkeyContainer.classList.remove('d-none');
      passkeyButton.addEventListener('click', async () => {
        if (!window.PublicKeyCredential) {
          updateStatus(passkeyButton.dataset.browserError || '', 'error');
          return;
        }
        passkeyButton.disabled = true;
        updateStatus(passkeyButton.dataset.progressLabel || '', 'info');
        try {
          const optionsData = await postJson(passkeyButton.dataset.optionsUrl, {});
          const publicKeyOptions = optionsData && optionsData.publicKey;
          if (!publicKeyOptions) {
            throw new Error('missing-options');
          }
          const credential = await navigator.credentials.get({
            publicKey: preparePublicKeyOptions(publicKeyOptions),
          });
          const payload = {
            credential: serializeCredential(credential),
          };
          const redirectField = passkeyButton.dataset.redirectField || 'next';
          const nextValue = passkeyButton.dataset.nextValue || '';
          if (nextValue) {
            payload[redirectField] = nextValue;
          }
          const verifyData = await postJson(passkeyButton.dataset.verifyUrl, payload);
          updateStatus(passkeyButton.dataset.successLabel || '', 'success');
          const target = verifyData && verifyData.redirect;
          if (target) {
            window.location.href = target;
          } else {
            window.location.reload();
          }
        } catch (error) {
          if (error && error.name === 'AbortError') {
            updateStatus('', 'info');
          } else {
            const message = (error && error.payload && error.payload.error) || passkeyButton.dataset.errorLabel || '';
            updateStatus(message, 'error');
          }
        } finally {
          passkeyButton.disabled = false;
        }
      });
    }
  }
})();
</script>
{% endblock %}
