// Экран входа и регистрации — docs/ui.md, «Вход и регистрация».

import { exportHistory } from "../export.js";
import { EXPORT_FAILED, clear, confirmPanel, el, field, mark, message, setError, setNote } from "./dom.js";

const HINT = "пароль — это ключ шифрования, а не запись в базе. восстановления нет. "
  + "не короче 12 символов; лучше — фраза из нескольких слов.";

const MODES = [
  ["in", "вход"],
  ["up", "регистрация"],
];

export function renderAuth(root, ctx) {
  // Состояние экрана переживает перерисовку: режим, введённый ник и ошибка.
  const view = { mode: "in", nick: "", error: "" };
  const paint = () => {
    clear(root);
    root.append(screen(ctx, view, paint));
  };
  paint();
}

function screen(ctx, view, paint) {
  const main = el("main", "auth");
  const inner = el("div", "auth__inner");
  main.append(inner);

  const brand = el("div", "auth__brand");
  brand.append(mark(), el("span", null, "bare"));
  inner.append(brand);
  inner.append(tabs(view, paint));

  const form = el("form", "form");
  form.noValidate = true;

  const register = view.mode === "up";
  const nick = field("ник", { name: "nick", autocomplete: "username" });
  nick.input.value = view.nick;
  const password = field("пароль", {
    type: "password",
    name: "password",
    autocomplete: register ? "new-password" : "current-password",
  });
  form.append(nick.wrap, password.wrap);

  let invite = null;
  if (register) {
    form.append(el("p", "hint", HINT));
    if (ctx.config?.inviteRequired) {
      invite = field("инвайт-код", { name: "invite", autocomplete: "off" });
      form.append(invite.wrap);
    }
  }

  const label = register ? "регистрация" : "вход";
  const submit = el("button", "button", label);
  submit.type = "submit";
  form.append(submit);

  // На устройстве могут лежать ключи другого ника: вход под этим сотрёт
  // историю прежнего, поэтому сначала подтверждение (ADR-029). История
  // на устройстве — единственная копия, и подтверждение предлагает сначала
  // сохранить её: секрет прежнего аккаунта ещё здесь, экспорту сессия
  // не нужна (ADR-014).
  const wipe = confirmPanel("", "удалить", "экспортировать");
  form.append(wipe.root);

  const note = message();
  form.append(note);
  if (view.error) {
    setError(note, view.error);
  }

  const fail = (text) => {
    view.error = text;
    if (text) {
      setError(note, text);
    } else {
      setNote(note, "");
    }
  };

  // run — то, ради чего экран: PBKDF2 и запрос к серверу. Вызывается либо
  // сразу, либо после подтверждения стирания.
  const run = async () => {
    submit.disabled = true;
    submit.textContent = "вычисляем ключ…";
    const pass = password.input.value;
    try {
      if (register) {
        await ctx.signUp(view.nick, pass, invite ? invite.input.value.trim() : "");
      } else {
        await ctx.signIn(view.nick, pass);
      }
      ctx.go("#/");
    } catch (err) {
      submit.disabled = false;
      submit.textContent = label;
      fail(ctx.errorText(err));
    }
  };

  const hideWipe = () => {
    wipe.root.hidden = true;
    submit.hidden = false;
  };
  // Экспорт подтверждение не закрывает: архив скачался, а входить или нет —
  // отдельное решение.
  wipe.extra.addEventListener("click", async () => {
    if (wipe.extra.disabled) {
      return;
    }
    wipe.extra.disabled = true;
    fail("");
    try {
      await exportHistory();
    } catch {
      fail(EXPORT_FAILED);
    } finally {
      wipe.extra.disabled = false;
    }
  });
  wipe.no.addEventListener("click", () => {
    hideWipe();
    submit.focus();
  });
  wipe.yes.addEventListener("click", () => {
    hideWipe();
    run();
  });

  form.addEventListener("submit", async (event) => {
    event.preventDefault();
    if (submit.disabled) {
      return;
    }
    view.nick = nick.input.value.trim().toLowerCase();
    nick.input.value = view.nick;
    const pass = password.input.value;
    fail("");
    hideWipe();

    if (!ctx.validNick(view.nick)) {
      fail("ник: 2–32 символа, a–z, 0–9, _");
      nick.input.focus();
      return;
    }
    // Длина пароля проверяется в обоих режимах: короче минимума пароля
    // нет ни у одного аккаунта, считать по нему PBKDF2 незачем (docs/ui.md).
    if ([...pass].length < ctx.minPassword) {
      fail(`пароль: не короче ${ctx.minPassword} символов`);
      password.input.focus();
      return;
    }
    try {
      await ctx.ensureConfig();
    } catch (err) {
      fail(ctx.errorText(err));
      return;
    }
    // Поле инвайта могло не появиться: конфигурация приехала только что.
    if (register && ctx.config.inviteRequired && invite === null) {
      view.error = "нужен инвайт-код";
      paint();
      return;
    }

    const other = await ctx.storedNick();
    if (other && other !== view.nick) {
      wipe.text.textContent = `на этом устройстве история @${other}. вход под другим ником удалит её.`;
      wipe.root.hidden = false;
      submit.hidden = true;
      // Фокус — на «экспортировать»: с него безопасно начинать.
      wipe.extra.focus();
      return;
    }
    await run();
  });

  inner.append(form);
  return main;
}

// tabs — переключатель «вход / регистрация».
function tabs(view, paint) {
  const wrap = el("div", "tabs");
  MODES.forEach(([mode, text], index) => {
    if (index > 0) {
      wrap.append(el("span", "tabs__sep", "/"));
    }
    const tab = el("button", "tab", text);
    tab.type = "button";
    const on = view.mode === mode;
    if (on) {
      tab.classList.add("is-on");
    }
    tab.setAttribute("aria-pressed", String(on));
    tab.addEventListener("click", () => {
      if (view.mode !== mode) {
        view.mode = mode;
        view.error = "";
        paint();
      }
    });
    wrap.append(tab);
  });
  return wrap;
}
