// Shared polymorphic notes module. Each detail page (project / deadline /
// appointment) renders an empty <div id="notes-container" data-parent-type=…
// data-parent-id=…> and loads this script. initNotes(container) attaches
// the "Add note" form + list rendering; the API base URL is picked from
// the parent type.
//
// Visibility and edit rights are enforced by the server — the UI only
// hides the edit/delete controls when they wouldn't work anyway (non-
// author, non-partner).

import { t, getLang } from "./i18n";

export type NotesParentType = "project" | "deadline" | "appointment";

export interface Note {
  id: string;
  project_id?: string | null;
  deadline_id?: string | null;
  appointment_id?: string | null;
  project_event_id?: string | null;
  content: string;
  created_by?: string | null;
  created_at: string;
  updated_at: string;
  author_name?: string | null;
  author_email?: string | null;
}

interface Me {
  id: string;
  job_title: string | null;
  global_role: string;
}

interface NotesState {
  parentType: NotesParentType;
  parentId: string;
  me: Me | null;
  notes: Note[];
  listEl: HTMLElement;
  emptyEl: HTMLElement;
  formEl: HTMLFormElement;
  textareaEl: HTMLTextAreaElement;
  submitBtn: HTMLButtonElement;
  msgEl: HTMLElement;
  editingID: string | null;
}

function baseURL(parentType: NotesParentType, parentId: string): string {
  switch (parentType) {
    case "project":
      return `/api/projects/${parentId}/notes`;
    case "deadline":
      return `/api/deadlines/${parentId}/notes`;
    case "appointment":
      return `/api/appointments/${parentId}/notes`;
  }
}

function esc(s: string): string {
  const d = document.createElement("div");
  d.textContent = s;
  return d.innerHTML;
}

function authorLabel(n: Note): string {
  return n.author_name || n.author_email || t("notes.unknown_author");
}

function fmtRelative(iso: string): string {
  const now = Date.now();
  const then = new Date(iso).getTime();
  const diff = Math.max(0, now - then);
  const sec = Math.floor(diff / 1000);
  const min = Math.floor(sec / 60);
  const hr = Math.floor(min / 60);
  const day = Math.floor(hr / 24);
  const lang = getLang();

  if (sec < 45) return t("notes.time.just_now");
  if (min < 2) return lang === "de" ? "vor 1 Minute" : "1 minute ago";
  if (min < 60) return lang === "de" ? `vor ${min} Minuten` : `${min} minutes ago`;
  if (hr < 2) return lang === "de" ? "vor 1 Stunde" : "1 hour ago";
  if (hr < 24) return lang === "de" ? `vor ${hr} Stunden` : `${hr} hours ago`;
  if (day < 2) return lang === "de" ? "gestern" : "yesterday";
  if (day < 30) return lang === "de" ? `vor ${day} Tagen` : `${day} days ago`;

  // > 30 days → absolute date
  const d = new Date(iso);
  return d.toLocaleDateString(lang === "de" ? "de-DE" : "en-GB", {
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
  });
}

function canEdit(state: NotesState, n: Note): boolean {
  if (!state.me) return false;
  return n.created_by === state.me.id;
}

function canDelete(state: NotesState, n: Note): boolean {
  if (!state.me) return false;
  if (n.created_by === state.me.id) return true;
  return state.me.global_role === "global_admin";
}

function render(state: NotesState) {
  state.listEl.innerHTML = "";
  if (state.notes.length === 0) {
    state.emptyEl.style.display = "";
    return;
  }
  state.emptyEl.style.display = "none";
  for (const n of state.notes) {
    state.listEl.appendChild(noteCard(state, n));
  }
}

function noteCard(state: NotesState, n: Note): HTMLElement {
  const card = document.createElement("li");
  card.className = "notiz-card";
  card.dataset.id = n.id;

  const header = document.createElement("div");
  header.className = "notiz-card-header";

  const author = document.createElement("span");
  author.className = "notiz-author";
  author.textContent = authorLabel(n);

  const time = document.createElement("time");
  time.className = "notiz-time";
  time.dateTime = n.created_at;
  time.textContent = fmtRelative(n.created_at);
  time.title = new Date(n.created_at).toLocaleString(getLang() === "de" ? "de-DE" : "en-GB");

  header.appendChild(author);
  header.appendChild(time);

  const actions = document.createElement("div");
  actions.className = "notiz-actions";

  if (canEdit(state, n)) {
    const editBtn = document.createElement("button");
    editBtn.type = "button";
    editBtn.className = "notiz-action-btn";
    editBtn.setAttribute("aria-label", t("notes.edit"));
    editBtn.title = t("notes.edit");
    editBtn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" /><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" /></svg>`;
    editBtn.addEventListener("click", () => startEdit(state, n));
    actions.appendChild(editBtn);
  }
  if (canDelete(state, n)) {
    const delBtn = document.createElement("button");
    delBtn.type = "button";
    delBtn.className = "notiz-action-btn notiz-action-danger";
    delBtn.setAttribute("aria-label", t("notes.delete"));
    delBtn.title = t("notes.delete");
    delBtn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6" /><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /></svg>`;
    delBtn.addEventListener("click", () => deleteNote(state, n));
    actions.appendChild(delBtn);
  }

  if (actions.children.length > 0) {
    header.appendChild(actions);
  }

  const body = document.createElement("div");
  body.className = "notiz-body";
  body.innerHTML = linkify(n.content);

  card.appendChild(header);
  card.appendChild(body);

  if (n.updated_at && n.updated_at !== n.created_at) {
    const edited = document.createElement("span");
    edited.className = "notiz-edited";
    edited.textContent = t("notes.edited");
    edited.title = new Date(n.updated_at).toLocaleString(getLang() === "de" ? "de-DE" : "en-GB");
    card.appendChild(edited);
  }

  return card;
}

// Plaintext → HTML with URL auto-linking. Escapes first, then linkifies.
function linkify(content: string): string {
  const escaped = esc(content);
  // Match URLs outside existing markup. Simple pattern — escaped content has
  // no tags, so we only need to avoid breaking &amp; etc.
  return escaped.replace(/(https?:\/\/[^\s<]+)/g, (url) => {
    const clean = url.replace(/[.,;:!?)]+$/, "");
    const trailing = url.slice(clean.length);
    return `<a href="${clean}" target="_blank" rel="noopener noreferrer">${clean}</a>${trailing}`;
  }).replace(/\n/g, "<br>");
}

async function loadMe(): Promise<Me | null> {
  try {
    const resp = await fetch("/api/me");
    if (!resp.ok) return null;
    return (await resp.json()) as Me;
  } catch {
    return null;
  }
}

async function loadNotes(state: NotesState) {
  try {
    const resp = await fetch(baseURL(state.parentType, state.parentId));
    if (!resp.ok) {
      state.notes = [];
      render(state);
      return;
    }
    const data = (await resp.json()) as Note[];
    state.notes = Array.isArray(data) ? data : [];
  } catch {
    state.notes = [];
  }
  render(state);
}

function showMsg(state: NotesState, kind: "ok" | "error", text: string) {
  state.msgEl.textContent = text;
  state.msgEl.className = `form-msg ${kind === "ok" ? "form-msg-ok" : "form-msg-error"}`;
}

function clearMsg(state: NotesState) {
  state.msgEl.textContent = "";
  state.msgEl.className = "form-msg";
}

async function submitForm(ev: Event, state: NotesState) {
  ev.preventDefault();
  const content = state.textareaEl.value.trim();
  if (!content) {
    showMsg(state, "error", t("notes.error.empty"));
    return;
  }
  state.submitBtn.disabled = true;
  clearMsg(state);

  if (state.editingID) {
    await saveEdit(state, state.editingID, content);
  } else {
    await addNote(state, content);
  }
  state.submitBtn.disabled = false;
}

async function addNote(state: NotesState, content: string) {
  // Optimistic add: insert a placeholder, replace with server response on success.
  const tempID = `temp-${Date.now()}`;
  const placeholder: Note = {
    id: tempID,
    content,
    created_by: state.me?.id ?? null,
    created_at: new Date().toISOString(),
    updated_at: new Date().toISOString(),
    author_name: null,
    author_email: null,
  };
  state.notes = [placeholder, ...state.notes];
  render(state);
  state.textareaEl.value = "";

  try {
    const resp = await fetch(baseURL(state.parentType, state.parentId), {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ content }),
    });
    if (!resp.ok) {
      // Revert: remove placeholder
      state.notes = state.notes.filter((n) => n.id !== tempID);
      render(state);
      state.textareaEl.value = content;
      const data = await resp.json().catch(() => ({}) as { error?: string });
      showMsg(state, "error", data.error || t("notes.error.generic"));
      return;
    }
    const saved = (await resp.json()) as Note;
    state.notes = state.notes.map((n) => (n.id === tempID ? saved : n));
    render(state);
  } catch {
    state.notes = state.notes.filter((n) => n.id !== tempID);
    render(state);
    state.textareaEl.value = content;
    showMsg(state, "error", t("notes.error.generic"));
  }
}

function startEdit(state: NotesState, n: Note) {
  state.editingID = n.id;
  state.textareaEl.value = n.content;
  state.textareaEl.focus();
  state.submitBtn.textContent = t("notes.save");
  state.formEl.classList.add("notiz-form-editing");
  // Add a cancel affordance inline
  let cancel = state.formEl.querySelector<HTMLButtonElement>(".notiz-cancel-btn");
  if (!cancel) {
    cancel = document.createElement("button");
    cancel.type = "button";
    cancel.className = "btn-cancel notiz-cancel-btn";
    cancel.textContent = t("notes.cancel");
    cancel.addEventListener("click", () => cancelEdit(state));
    state.submitBtn.parentElement!.insertBefore(cancel, state.submitBtn);
  }
}

function cancelEdit(state: NotesState) {
  state.editingID = null;
  state.textareaEl.value = "";
  state.submitBtn.textContent = t("notes.submit");
  state.formEl.classList.remove("notiz-form-editing");
  const cancel = state.formEl.querySelector<HTMLButtonElement>(".notiz-cancel-btn");
  if (cancel) cancel.remove();
  clearMsg(state);
}

async function saveEdit(state: NotesState, id: string, content: string) {
  const prev = state.notes.find((n) => n.id === id);
  if (!prev) return;
  const prevContent = prev.content;
  // Optimistic
  state.notes = state.notes.map((n) => (n.id === id ? { ...n, content, updated_at: new Date().toISOString() } : n));
  render(state);
  cancelEdit(state);

  try {
    const resp = await fetch(`/api/notes/${id}`, {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ content }),
    });
    if (!resp.ok) {
      state.notes = state.notes.map((n) => (n.id === id ? { ...n, content: prevContent } : n));
      render(state);
      const data = await resp.json().catch(() => ({}) as { error?: string });
      showMsg(state, "error", data.error || t("notes.error.generic"));
      return;
    }
    const saved = (await resp.json()) as Note;
    state.notes = state.notes.map((n) => (n.id === id ? saved : n));
    render(state);
  } catch {
    state.notes = state.notes.map((n) => (n.id === id ? { ...n, content: prevContent } : n));
    render(state);
    showMsg(state, "error", t("notes.error.generic"));
  }
}

async function deleteNote(state: NotesState, n: Note) {
  if (!confirm(t("notes.delete.confirm"))) return;
  const prev = state.notes;
  state.notes = state.notes.filter((x) => x.id !== n.id);
  render(state);
  try {
    const resp = await fetch(`/api/notes/${n.id}`, { method: "DELETE" });
    if (!resp.ok && resp.status !== 204) {
      state.notes = prev;
      render(state);
      const data = await resp.json().catch(() => ({}) as { error?: string });
      alert(data.error || t("notes.error.generic"));
    }
  } catch {
    state.notes = prev;
    render(state);
    alert(t("notes.error.generic"));
  }
}

function buildUI(container: HTMLElement, state: NotesState) {
  container.innerHTML = "";

  const form = document.createElement("form");
  form.className = "notiz-form";
  form.autocomplete = "off";

  const textarea = document.createElement("textarea");
  textarea.className = "notiz-textarea";
  textarea.rows = 3;
  textarea.placeholder = t("notes.placeholder");
  textarea.setAttribute("data-i18n-placeholder", "notes.placeholder");

  const actions = document.createElement("div");
  actions.className = "notiz-form-actions";

  const submit = document.createElement("button");
  submit.type = "submit";
  submit.className = "btn-primary btn-cta-lime btn-small";
  submit.textContent = t("notes.submit");

  // Keyboard-shortcut hint mirrors the listener at line 426 below — Strg+Enter
  // on Linux/Windows, Cmd+Enter on macOS. The hint string carries both forms
  // so we don't need to sniff the platform; it's a tooltip-grade affordance.
  const hint = document.createElement("p");
  hint.className = "notiz-form-hint";
  hint.textContent = t("notes.shortcut.hint");
  hint.setAttribute("data-i18n", "notes.shortcut.hint");

  const msg = document.createElement("p");
  msg.className = "form-msg";

  actions.appendChild(submit);
  form.appendChild(textarea);
  form.appendChild(hint);
  form.appendChild(actions);
  form.appendChild(msg);

  const list = document.createElement("ul");
  list.className = "notiz-list";

  const empty = document.createElement("p");
  empty.className = "notiz-empty";
  empty.textContent = t("notes.empty");
  empty.setAttribute("data-i18n", "notes.empty");
  empty.style.display = "none";

  container.appendChild(form);
  container.appendChild(list);
  container.appendChild(empty);

  state.formEl = form;
  state.textareaEl = textarea;
  state.submitBtn = submit;
  state.msgEl = msg;
  state.listEl = list;
  state.emptyEl = empty;

  form.addEventListener("submit", (ev) => submitForm(ev, state));
  textarea.addEventListener("keydown", (ev) => {
    if ((ev.ctrlKey || ev.metaKey) && ev.key === "Enter") {
      ev.preventDefault();
      form.requestSubmit();
    }
  });
}

export async function initNotes(
  container: HTMLElement,
  parentType: NotesParentType,
  parentId: string,
): Promise<void> {
  const state: NotesState = {
    parentType,
    parentId,
    me: null,
    notes: [],
    listEl: document.createElement("ul"),
    emptyEl: document.createElement("p"),
    formEl: document.createElement("form"),
    textareaEl: document.createElement("textarea"),
    submitBtn: document.createElement("button"),
    msgEl: document.createElement("p"),
    editingID: null,
  };

  buildUI(container, state);
  state.me = await loadMe();
  await loadNotes(state);
}

// Auto-init if a container element with data attributes is present. Any
// page that embeds a <div id="notes-container" data-parent-type=…
// data-parent-id=…> gets the component for free — no per-page wiring.
export function autoInitNotes() {
  const el = document.getElementById("notes-container");
  if (!el) return;
  const parentType = el.getAttribute("data-parent-type") as NotesParentType | null;
  const parentId = el.getAttribute("data-parent-id");
  if (!parentType || !parentId) return;
  void initNotes(el, parentType, parentId);
}
