// Участники комнаты — docs/ui.md, «Участники».
//
// Состав, владелец и то, чей ключ мешает rekey, приходят из sync: экран
// не пишет в базу и не ходит в сеть сам.

import * as sync from "../sync.js";
import { button, clear, confirmPanel, el, message, setError, setNote } from "./dom.js";

// renderMembers рисует экран в root и отдаёт отписку.
export function renderMembers(root, ctx, roomId) {
  const view = {
    ctx,
    roomId,
    chatId: sync.roomChatId(roomId),
    me: ctx.me.nick,
    alive: true,
    // Событий «rooms» приходит больше одного подряд; рисует последнее.
    generation: 0,
  };

  view.title = el("span", "title", "#");
  root.append(head(view));

  const body = el("div", "body settings");

  // Полоса «нужен новый ключ комнаты» — единственный акцент экрана
  // (docs/identity/brief.md).
  view.warn = el("p", "bar bar--mark");
  view.warn.hidden = true;
  view.warn.setAttribute("aria-live", "polite");

  const people = el("section", "block block--first");
  view.list = el("ul", "members");
  view.note = message();
  people.append(el("h2", "section", "участники"), view.list, add(view), view.note);

  body.append(view.warn, people, exit(view));
  root.append(body);

  const off = sync.on("rooms", (detail) => {
    if (detail.id === view.roomId) {
      paint(view);
    }
  });
  paint(view);

  return () => {
    view.alive = false;
    off();
  };
}

function head(view) {
  const bar = el("div", "head");
  const back = el("button", "back", "назад");
  back.type = "button";
  back.addEventListener("click", () => view.ctx.go(`#/room/${view.roomId}`));
  bar.append(back, view.title);
  return bar;
}

// --- разметка -----------------------------------------------------------

// add — строка ввода `@ник` и «добавить». Видна только владельцу: состав
// меняет он (ADR-018).
function add(view) {
  const form = el("form", "form form--row");
  form.noValidate = true;
  form.hidden = true;

  const line = el("div", "input");
  const prompt = el("span", "p", ">");
  prompt.setAttribute("aria-hidden", "true");
  const field = el("input", "input__field");
  field.type = "text";
  field.placeholder = "@ник";
  field.autocapitalize = "off";
  field.autocomplete = "off";
  field.spellcheck = false;
  line.append(prompt, field);

  const go = el("button", "button", "добавить");
  go.type = "submit";
  form.append(line, go);

  form.addEventListener("submit", async (event) => {
    event.preventDefault();
    if (go.disabled) {
      return;
    }
    setNote(view.note, "");
    // Ник вводят как в списке: с «@» или без. Ники строчные (ADR-019).
    const nick = field.value.trim().replace(/^@/, "").toLowerCase();
    if (nick === "") {
      field.focus();
      return;
    }
    field.value = nick;
    go.disabled = true;
    try {
      await sync.changeMembers(view.roomId, { add: [nick] });
      field.value = "";
    } catch (err) {
      setError(view.note, failure(view.ctx, err));
      field.focus();
    } finally {
      go.disabled = false;
    }
  });

  view.add = form;
  return form;
}

// exit — «выйти из комнаты» всем, «удалить комнату» владельцу
// (docs/ui.md, «Участники»).
function exit(view) {
  const box = el("section", "block");
  const note = message();

  const leave = button("выйти из комнаты");
  leave.addEventListener("click", async () => {
    leave.disabled = true;
    setNote(note, "");
    try {
      await sync.leaveRoom(view.roomId);
      view.ctx.go("#/");
    } catch (err) {
      setError(note, failure(view.ctx, err));
      leave.disabled = false;
    }
  });

  const drop = button("удалить комнату");
  drop.hidden = true;
  const panel = confirmPanel("комната будет удалена у всех участников.", "удалить");

  drop.addEventListener("click", () => {
    setNote(note, "");
    drop.hidden = true;
    panel.root.hidden = false;
    panel.yes.focus();
  });
  panel.no.addEventListener("click", () => {
    panel.root.hidden = true;
    drop.hidden = false;
    drop.focus();
  });
  panel.yes.addEventListener("click", async () => {
    panel.yes.disabled = true;
    panel.no.disabled = true;
    try {
      await sync.deleteRoom(view.roomId);
      view.ctx.go("#/");
    } catch (err) {
      setError(note, failure(view.ctx, err));
      panel.yes.disabled = false;
      panel.no.disabled = false;
      panel.root.hidden = true;
      drop.hidden = false;
    }
  });

  view.drop = drop;
  view.panel = panel.root;
  box.append(leave, drop, panel.root, note);
  return box;
}

// --- состояние ----------------------------------------------------------

// paint перечитывает комнату и рисует состав. Владение переходит к участнику
// с наименьшим joined_at (ADR-018), поэтому владельческие части экрана
// появляются и исчезают вместе с составом.
async function paint(view) {
  const mine = ++view.generation;
  let record = null;
  try {
    record = await sync.chat(view.chatId);
  } catch {
    // Базы нет — рисуем пустой состав: выйти из комнаты это не мешает.
  }
  if (!view.alive || mine !== view.generation) {
    return;
  }
  const members = record?.members ?? [];
  const owner = record?.owner ?? null;
  const mineRoom = owner === view.me;

  view.title.textContent = `#${record?.title || view.roomId}`;

  clear(view.list);
  for (const nick of members) {
    view.list.append(person(view, nick, owner, mineRoom));
  }

  view.add.hidden = !mineRoom;
  view.drop.hidden = !mineRoom || !view.panel.hidden;
  if (!mineRoom) {
    view.panel.hidden = true;
  }

  const blocked = sync.needsTrust(view.roomId);
  view.warn.textContent = blocked.length > 0 ? trustText(blocked) : "";
  view.warn.hidden = blocked.length === 0;
}

// person — строка участника: ник, пометка «владелец», «убрать» у владельца.
// Владельца убрать нельзя (docs/protocol.md, `400 owner`), поэтому кнопки
// в его строке нет.
function person(view, nick, owner, mineRoom) {
  const row = el("li", "member");
  row.append(el("span", "member__name", `@${nick}`));
  if (nick === owner) {
    row.append(el("span", "tag", "владелец"));
    return row;
  }
  if (!mineRoom) {
    return row;
  }
  const drop = el("button", "link", "убрать");
  drop.type = "button";
  drop.addEventListener("click", async () => {
    drop.disabled = true;
    setNote(view.note, "");
    try {
      await sync.changeMembers(view.roomId, { remove: [nick] });
    } catch (err) {
      setError(view.note, failure(view.ctx, err));
      drop.disabled = false;
    }
  });
  row.append(drop);
  return row;
}

// failure — текст отказа. Неподтверждённый ключ обрывает смену состава
// до запроса, и состояние у него то же, что у несделанного rekey:
// комнате нужен новый ключ, а раздать его некому (ADR-016, ADR-038).
function failure(ctx, err) {
  if (err instanceof sync.TrustNeeded) {
    return trustText(err.nicks);
  }
  return ctx.errorText(err);
}

function trustText(nicks) {
  return `нужен новый ключ комнаты: подтвердите ключ ${nicks.map((nick) => `@${nick}`).join(", ")}`;
}
