import { initI18n, onLangChange, t, tDyn } from "./i18n";
import { initSidebar } from "./sidebar";
import { openBroadcastModal, firstName, type BroadcastRecipient } from "./broadcast";

interface User {
  id: string;
  email: string;
  display_name: string;
  office: string;
  additional_offices?: string[];
  job_title?: string | null;
}

interface MembershipEntry {
  user_id: string;
  project_ids: string[];
  lead_project_ids: string[];
  roles: string[];
}

interface ProjectSummary {
  id: string;
  title: string;
  type: string;
  reference?: string | null;
}

interface MeUser {
  id: string;
  global_role: string;
}

interface DepartmentMember {
  user_id: string;
  email: string;
  display_name: string;
  office: string;
  job_title?: string | null;
}

interface Department {
  id: string;
  name: string;
  office: string;
  lead_user_id?: string;
  lead_display_name?: string;
  lead_email?: string;
  members: DepartmentMember[];
}

const OFFICE_ORDER = ["munich", "duesseldorf", "hamburg", "amsterdam", "london", "paris", "milan"];

// ROLE_ORDER drives pill ordering: seniority hierarchy first, then alphabetical
// for anything new the firm starts using. Compared case-insensitively against
// the trimmed job_title — see roleKey().
const ROLE_ORDER = [
  "partner",
  "counsel",
  "counsel knowledge lawyer",
  "senior associate",
  "associate",
  "junior associate",
  "trainee",
  "pa",
  "paralegal",
  "secretary",
];

let users: User[] = [];
let departments: Department[] = [];
let memberships: MembershipEntry[] = [];
let projectsList: ProjectSummary[] = [];
let me: MeUser | null = null;
let groupBy: "office" | "department" = "office";
let activeOffice = "all";
let activeRole = "all";
let activeProjectIDs: Set<string> = new Set();
let searchQuery = "";

const ICON_MAIL = '<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="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>';
const ICON_PIN = '<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="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>';

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

function officeLabel(key: string): string {
  return tDyn("office." + key) || key;
}

// roleKey normalises a free-text job_title for case-insensitive comparison
// (DB has both "Associate" and "associate" today — t-paliad-085).
function roleKey(jobTitle: string | null | undefined): string {
  return (jobTitle ?? "").trim().toLowerCase();
}

// roleLabel looks up a translation under team.role.<slug>; falls back to the
// raw job_title so newly-introduced titles render even without an i18n entry.
function roleLabel(jobTitle: string): string {
  const slug = roleKey(jobTitle).replace(/\s+/g, "_");
  return tDyn("team.role." + slug) || jobTitle;
}

function initials(name: string): string {
  const parts = name.trim().split(/\s+/).filter(Boolean);
  if (parts.length === 0) return "?";
  if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
  return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
}

async function loadAll() {
  const [usersResp, deptsResp, membershipsResp, projectsResp, meResp] = await Promise.all([
    fetch("/api/users"),
    fetch("/api/partner-units?include=members"),
    fetch("/api/team/memberships"),
    fetch("/api/projects"),
    fetch("/api/me"),
  ]);
  if (usersResp.ok) users = (await usersResp.json()) as User[];
  if (deptsResp.ok) departments = (await deptsResp.json()) as Department[];
  if (membershipsResp.ok) memberships = (await membershipsResp.json()) as MembershipEntry[];
  if (projectsResp.ok) {
    const raw = (await projectsResp.json()) as ProjectSummary[];
    projectsList = raw;
  }
  if (meResp.ok) me = (await meResp.json()) as MeUser;
  buildOfficeFilters();
  buildRoleFilters();
  buildProjectFilter();
  render();
  updateBroadcastButton();
}

function presentOffices(): string[] {
  const seen = new Set<string>();
  for (const u of users) seen.add(u.office);
  return OFFICE_ORDER.filter((k) => seen.has(k)).concat(
    Array.from(seen).filter((k) => !OFFICE_ORDER.includes(k)).sort(),
  );
}

function buildOfficeFilters() {
  const container = document.getElementById("team-office-filters")!;
  const offices = presentOffices();
  const allBtn = `<button class="filter-pill${activeOffice === "all" ? " active" : ""}" data-office="all" type="button">${esc(t("team.filter.all"))}</button>`;
  const pills = offices
    .map((k) => `<button class="filter-pill${activeOffice === k ? " active" : ""}" data-office="${esc(k)}" type="button">${esc(officeLabel(k))}</button>`)
    .join("");
  container.innerHTML = allBtn + pills;
  container.querySelectorAll<HTMLButtonElement>(".filter-pill").forEach((btn) => {
    btn.addEventListener("click", () => {
      activeOffice = btn.dataset.office ?? "all";
      container.querySelectorAll(".filter-pill").forEach((p) => p.classList.remove("active"));
      btn.classList.add("active");
      render();
    });
  });
}

// presentRoles returns the distinct job_titles in the loaded users, ordered by
// ROLE_ORDER (seniority) with anything unrecognised tacked on alphabetically.
// Values are case-folded for grouping; the first-seen casing wins for display.
function presentRoles(): string[] {
  const seen = new Map<string, string>();
  for (const u of users) {
    const jt = (u.job_title ?? "").trim();
    if (!jt) continue;
    const key = jt.toLowerCase();
    if (!seen.has(key)) seen.set(key, jt);
  }
  return [...seen.values()].sort((a, b) => {
    const ai = ROLE_ORDER.indexOf(a.toLowerCase());
    const bi = ROLE_ORDER.indexOf(b.toLowerCase());
    if (ai !== -1 && bi !== -1) return ai - bi;
    if (ai !== -1) return -1;
    if (bi !== -1) return 1;
    return a.localeCompare(b);
  });
}

function buildRoleFilters() {
  const container = document.getElementById("team-role-filters")!;
  const roles = presentRoles();
  const activeKey = activeRole.toLowerCase();
  const allBtn = `<button class="filter-pill${activeRole === "all" ? " active" : ""}" data-role="all" type="button">${esc(t("team.filter.all"))}</button>`;
  const pills = roles
    .map((r) => `<button class="filter-pill${activeKey === r.toLowerCase() ? " active" : ""}" data-role="${esc(r)}" type="button">${esc(roleLabel(r))}</button>`)
    .join("");
  container.innerHTML = allBtn + pills;
  container.querySelectorAll<HTMLButtonElement>(".filter-pill").forEach((btn) => {
    btn.addEventListener("click", () => {
      activeRole = btn.dataset.role ?? "all";
      container.querySelectorAll(".filter-pill").forEach((p) => p.classList.remove("active"));
      btn.classList.add("active");
      render();
    });
  });
}

function userMatchesSearch(u: User): boolean {
  if (!searchQuery) return true;
  const q = searchQuery.toLowerCase();
  const hay = [
    u.display_name,
    u.email,
    u.job_title ?? "",
    u.office,
    officeLabel(u.office),
    ...(u.additional_offices ?? []).map(officeLabel),
  ]
    .join(" ")
    .toLowerCase();
  return hay.includes(q);
}

function userMatchesOffice(u: User): boolean {
  if (activeOffice === "all") return true;
  if (u.office === activeOffice) return true;
  return (u.additional_offices ?? []).includes(activeOffice);
}

function userMatchesRole(u: User): boolean {
  if (activeRole === "all") return true;
  return roleKey(u.job_title) === activeRole.toLowerCase();
}

// userMatchesProject returns true when the project filter is empty or
// when the user is a direct member of at least one selected project.
// Inherited memberships intentionally don't qualify here — users want
// "people I can mail on this matter", which means direct membership.
function userMatchesProject(u: User): boolean {
  if (activeProjectIDs.size === 0) return true;
  const m = memberships.find((m) => m.user_id === u.id);
  if (!m) return false;
  for (const pid of m.project_ids) {
    if (activeProjectIDs.has(pid)) return true;
  }
  return false;
}

// canBroadcast reports whether the current user is allowed to send a
// broadcast given the active project filter. global_admin always wins.
// Otherwise the user must be a 'lead' on every project they have
// selected (or, when no project is selected, on at least one of their
// own projects).
function canBroadcast(): boolean {
  if (!me) return false;
  if (me.global_role === "global_admin") return true;
  const myMembership = memberships.find((m) => m.user_id === me?.id);
  if (!myMembership || !myMembership.lead_project_ids.length) return false;
  if (activeProjectIDs.size === 0) {
    // No project filter — allow when caller leads at least one project.
    // Server-side check still runs per-broadcast so a non-lead can never
    // actually send.
    return true;
  }
  for (const pid of activeProjectIDs) {
    if (!myMembership.lead_project_ids.includes(pid)) return false;
  }
  return true;
}

function buildProjectFilter() {
  const container = document.getElementById("team-project-filter");
  if (!container) return;
  // Show only projects the caller can see — projectsList already does
  // that via the visibility-gated /api/projects endpoint.
  const sortedProjects = [...projectsList].sort((a, b) =>
    (a.title || "").localeCompare(b.title || ""),
  );
  const options = sortedProjects
    .map(
      (p) =>
        `<label class="filter-checkbox"><input type="checkbox" data-project-id="${esc(p.id)}" ${
          activeProjectIDs.has(p.id) ? "checked" : ""
        } /> <span>${esc(p.title)}</span></label>`,
    )
    .join("");
  const summary = activeProjectIDs.size === 0
    ? (t("team.filter.project.all") || "Alle Projekte")
    : `${activeProjectIDs.size} ${t("team.filter.project.selected") || "ausgewählt"}`;
  container.innerHTML = `
    <button type="button" class="filter-pill team-project-trigger" data-project-trigger>
      <span class="team-project-summary">${esc(t("team.filter.project") || "Projekt")}: ${esc(summary)}</span>
    </button>
    <div class="team-project-panel hidden" data-project-panel>
      <div class="team-project-actions">
        <button type="button" class="link-button" data-project-clear>${esc(t("team.filter.project.clear") || "Alle abwählen")}</button>
      </div>
      <div class="team-project-options">${options}</div>
    </div>
  `;
  const trigger = container.querySelector<HTMLButtonElement>("[data-project-trigger]");
  const panel = container.querySelector<HTMLDivElement>("[data-project-panel]");
  trigger?.addEventListener("click", (e) => {
    e.stopPropagation();
    panel?.classList.toggle("hidden");
  });
  document.addEventListener("click", (e) => {
    if (!container.contains(e.target as Node)) panel?.classList.add("hidden");
  });
  container.querySelectorAll<HTMLInputElement>("input[data-project-id]").forEach((cb) => {
    cb.addEventListener("change", () => {
      const pid = cb.dataset.projectId!;
      if (cb.checked) activeProjectIDs.add(pid);
      else activeProjectIDs.delete(pid);
      buildProjectFilter();
      render();
      updateBroadcastButton();
    });
  });
  container.querySelector<HTMLButtonElement>("[data-project-clear]")?.addEventListener("click", () => {
    activeProjectIDs.clear();
    buildProjectFilter();
    render();
    updateBroadcastButton();
  });
}

function buildBroadcastButton() {
  const wrap = document.getElementById("team-broadcast-wrap");
  if (!wrap) return;
  if (!canBroadcast()) {
    wrap.innerHTML = "";
    wrap.style.display = "none";
    return;
  }
  wrap.style.display = "";
  wrap.innerHTML = `
    <button type="button" class="btn btn-primary" id="team-broadcast-btn">
      ${esc(t("team.broadcast.button") || "E-Mail an Auswahl")} <span class="team-broadcast-count" id="team-broadcast-count">0</span>
    </button>
  `;
  document.getElementById("team-broadcast-btn")?.addEventListener("click", () => onBroadcastClick());
}

function updateBroadcastButton() {
  buildBroadcastButton();
  const countEl = document.getElementById("team-broadcast-count");
  if (countEl) {
    const n = displayedRecipients().length;
    countEl.textContent = String(n);
    const btn = document.getElementById("team-broadcast-btn") as HTMLButtonElement | null;
    if (btn) btn.disabled = n === 0;
  }
}

// displayedRecipients returns the currently visible users as broadcast
// recipients. Personal placeholder fields are sourced from each user
// (display_name / first_name) and from the membership index when a
// project filter is set (role_on_project = the role on the selected
// project; falls back to first available role).
function displayedRecipients(): BroadcastRecipient[] {
  const filtered = users.filter(
    (u) => userMatchesOffice(u) && userMatchesRole(u) && userMatchesProject(u) && userMatchesSearch(u),
  );
  return filtered.map((u) => {
    const m = memberships.find((m) => m.user_id === u.id);
    let role = "";
    if (m) {
      if (activeProjectIDs.size > 0) {
        const idx = m.project_ids.findIndex((pid) => activeProjectIDs.has(pid));
        if (idx >= 0) role = m.roles[idx];
      } else if (m.roles.length > 0) {
        role = m.roles[0];
      }
    }
    return {
      user_id: u.id,
      email: u.email,
      display_name: u.display_name,
      first_name: firstName(u.display_name),
      role_on_project: role,
    };
  });
}

function onBroadcastClick() {
  const recipients = displayedRecipients();
  const selectedProjectIDs = Array.from(activeProjectIDs);
  // When exactly one project is selected we pass it as project_id so
  // the backend can verify lead-ship on that project. With multi-
  // select we leave project_id null and rely on global_admin (the
  // service rejects non-admin senders without a project_id).
  const projectID = selectedProjectIDs.length === 1 ? selectedProjectIDs[0] : null;
  const offices = activeOffice === "all" ? [] : [activeOffice];
  const roles = activeRole === "all" ? [] : [activeRole];
  openBroadcastModal({
    recipients,
    projectID,
    projectIDs: selectedProjectIDs,
    offices,
    roles,
  });
}

function memberAsUser(m: DepartmentMember): User | undefined {
  return users.find((u) => u.id === m.user_id);
}

function renderUserCard(u: User): string {
  const additional = (u.additional_offices ?? []).filter((o) => o !== u.office);
  const jobTitle = (u.job_title ?? "").trim();
  return `
  <article class="team-card">
    <div class="team-avatar" aria-hidden="true">${esc(initials(u.display_name))}</div>
    <div class="team-card-body">
      <div class="team-card-name">${esc(u.display_name)}</div>
      ${jobTitle ? `<div class="team-card-role">${esc(roleLabel(jobTitle))}</div>` : ""}
      <div class="team-card-meta">
        <span class="team-office-badge">${ICON_PIN}<span>${esc(officeLabel(u.office))}</span></span>
        ${additional.length ? `<span class="team-office-extra">+ ${additional.map((o) => esc(officeLabel(o))).join(", ")}</span>` : ""}
      </div>
      <a class="team-card-email" href="mailto:${esc(u.email)}">${ICON_MAIL}<span>${esc(u.email)}</span></a>
    </div>
  </article>`;
}

function renderGroupByOffice(filtered: User[]): string {
  const present = presentOffices();
  const sections = present
    .map((officeKey) => {
      const inOffice = filtered.filter((u) => u.office === officeKey || (u.additional_offices ?? []).includes(officeKey));
      if (!inOffice.length) return "";
      return `
        <section class="team-group">
          <header class="team-group-header">
            <h2>${esc(officeLabel(officeKey))}</h2>
            <span class="team-group-count">${inOffice.length}</span>
          </header>
          <div class="team-grid">${inOffice.map(renderUserCard).join("")}</div>
        </section>`;
    })
    .filter(Boolean);
  // Catch users whose office key isn't in OFFICE_ORDER and isn't present-listed.
  const placedKeys = new Set(present);
  const orphans = filtered.filter((u) => !placedKeys.has(u.office));
  if (orphans.length) {
    sections.push(`
      <section class="team-group">
        <header class="team-group-header">
          <h2>${esc(t("team.group.other") || "Sonstige")}</h2>
          <span class="team-group-count">${orphans.length}</span>
        </header>
        <div class="team-grid">${orphans.map(renderUserCard).join("")}</div>
      </section>`);
  }
  return sections.join("");
}

function renderGroupByDepartment(filtered: User[]): string {
  const filteredIDs = new Set(filtered.map((u) => u.id));
  const sections: string[] = [];

  for (const d of departments) {
    if (activeOffice !== "all" && d.office !== activeOffice) continue;
    const visibleMembers = d.members
      .map(memberAsUser)
      .filter((u): u is User => !!u && filteredIDs.has(u.id));
    if (!visibleMembers.length && !d.lead_display_name) continue;
    const leadHTML = d.lead_display_name
      ? `<div class="team-dept-lead">${esc(t("team.dept.lead") || "Lead")}: <strong>${esc(d.lead_display_name)}</strong>${d.lead_email ? ` <a href="mailto:${esc(d.lead_email)}">${esc(d.lead_email)}</a>` : ""}</div>`
      : "";
    sections.push(`
      <section class="team-group">
        <header class="team-group-header">
          <div>
            <h2>${esc(d.name)}</h2>
            <div class="team-group-sub">${esc(officeLabel(d.office))}</div>
            ${leadHTML}
          </div>
          <span class="team-group-count">${visibleMembers.length}</span>
        </header>
        <div class="team-grid">${visibleMembers.map(renderUserCard).join("")}</div>
      </section>`);
  }

  // Users not in any partner unit get a single "ohne Partner Unit" bucket so
  // they're still discoverable in the directory.
  const inAnyDept = new Set<string>();
  for (const d of departments) for (const m of d.members) inAnyDept.add(m.user_id);
  const orphans = filtered.filter((u) => !inAnyDept.has(u.id));
  if (orphans.length) {
    const heading = t("team.partner_unit.unassigned") || "Ohne Partner Unit";
    sections.push(`
      <section class="team-group team-group-loose">
        <header class="team-group-header">
          <h2>${esc(heading)}</h2>
          <span class="team-group-count">${orphans.length}</span>
        </header>
        <div class="team-grid">${orphans.map(renderUserCard).join("")}</div>
      </section>`);
  }

  return sections.join("");
}

function render() {
  const list = document.getElementById("team-list")!;
  const empty = document.getElementById("team-empty")!;
  const count = document.getElementById("team-count")!;

  const filtered = users.filter(
    (u) => userMatchesOffice(u) && userMatchesRole(u) && userMatchesProject(u) && userMatchesSearch(u),
  );
  count.textContent = `${filtered.length} / ${users.length}`;
  updateBroadcastButton();

  if (filtered.length === 0) {
    list.innerHTML = "";
    empty.style.display = "block";
    return;
  }
  empty.style.display = "none";

  list.innerHTML = groupBy === "office"
    ? renderGroupByOffice(filtered)
    : renderGroupByDepartment(filtered);
}

function initToggle() {
  const container = document.querySelector<HTMLElement>(".team-toggle")!;
  container.addEventListener("click", (e) => {
    const btn = (e.target as HTMLElement).closest<HTMLButtonElement>(".filter-pill");
    if (!btn) return;
    const next = btn.dataset.group as "office" | "department" | undefined;
    if (!next || next === groupBy) return;
    groupBy = next;
    container.querySelectorAll(".filter-pill").forEach((p) => p.classList.remove("active"));
    btn.classList.add("active");
    render();
  });
}

function initSearch() {
  const input = document.getElementById("team-search") as HTMLInputElement;
  input.addEventListener("input", () => {
    searchQuery = input.value;
    render();
  });
}

document.addEventListener("DOMContentLoaded", () => {
  initI18n();
  initSidebar();
  initSearch();
  initToggle();
  onLangChange(() => {
    buildOfficeFilters();
    buildRoleFilters();
    render();
  });
  loadAll();
});
