import { initI18n, onLangChange, t, tDyn, getLang } from "./i18n";
import { initSidebar } from "./sidebar";
import { initNotes } from "./notes";
import { projectIndent } from "./project-indent";
import {
  attachEventTypePicker,
  fetchEventTypes,
  eventTypeLabel,
  type EventType,
  type PickerHandle,
} from "./event-types";

interface Deadline {
  id: string;
  project_id: string;
  title: string;
  description?: string;
  due_date: string;
  status: string;
  source: string;
  rule_id?: string;
  rule_code?: string;
  notes?: string;
  created_at: string;
  completed_at?: string;
  event_type_ids?: string[];
  // t-paliad-138 + t-paliad-160. approval_status='pending' means an
  // approval_request is in flight; pending_request_id resolves to it
  // and the controls flip to a withdraw affordance for the requester.
  approval_status?: "approved" | "pending" | "legacy";
  pending_request_id?: string | null;
}

interface PendingApprovalRequest {
  id: string;
  status: string;
  requested_by: string;
  requested_at: string;
  required_role: string;
  requester_name?: string;
}

let eventTypePicker: PickerHandle | null = null;
let eventTypeByID: Map<string, EventType> = new Map();

interface Project {
  id: string;
  reference?: string | null;
  title: string;
  path?: string;
}

interface DeadlineRule {
  id: string;
  code?: string;
  name: string;
  rule_code?: string;
}

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

let deadline: Deadline | null = null;
let project: Project | null = null;
let rule: DeadlineRule | null = null;
let me: Me | null = null;
let allProjects: Project[] = [];
let pendingRequest: PendingApprovalRequest | null = null;

function parseDeadlineID(): string | null {
  const parts = window.location.pathname.split("/").filter(Boolean);
  if (parts[0] !== "deadlines" || !parts[1]) return null;
  return parts[1];
}

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

function fmtDate(iso: string): string {
  try {
    const d = new Date(iso + (iso.length === 10 ? "T00:00:00" : ""));
    return d.toLocaleDateString(getLang() === "de" ? "de-DE" : "en-GB", {
      year: "numeric",
      month: "2-digit",
      day: "2-digit",
    });
  } catch {
    return iso;
  }
}

function fmtDateTime(iso: string): string {
  try {
    const d = new Date(iso);
    return d.toLocaleString(getLang() === "de" ? "de-DE" : "en-GB", {
      year: "numeric",
      month: "2-digit",
      day: "2-digit",
      hour: "2-digit",
      minute: "2-digit",
    });
  } catch {
    return iso;
  }
}

function urgencyClass(due: string, status: string): string {
  if (status === "completed") return "frist-urgency-done";
  const today = new Date();
  today.setHours(0, 0, 0, 0);
  const d = new Date(due.slice(0, 10) + "T00:00:00");
  const diffDays = Math.floor((d.getTime() - today.getTime()) / 86400000);
  if (diffDays < 0) return "frist-urgency-overdue";
  if (diffDays <= 7) return "frist-urgency-soon";
  return "frist-urgency-later";
}

async function loadDeadline(id: string): Promise<boolean> {
  try {
    const resp = await fetch(`/api/deadlines/${id}`);
    if (!resp.ok) return false;
    deadline = await resp.json();
    return true;
  } catch {
    return false;
  }
}

async function loadProject(projectID: string) {
  try {
    const resp = await fetch(`/api/projects/${projectID}`);
    if (resp.ok) project = await resp.json();
  } catch {
    /* non-fatal */
  }
}

async function loadAllProjects() {
  try {
    const resp = await fetch("/api/projects");
    if (resp.ok) allProjects = await resp.json();
  } catch {
    /* non-fatal */
  }
}

function populateProjectPicker() {
  const sel = document.getElementById("deadline-project-edit") as HTMLSelectElement | null;
  if (!sel || !deadline) return;
  const opts: string[] = [];
  for (const p of allProjects) {
    const indent = projectIndent(p.path);
    const ref = p.reference || "";
    opts.push(
      `<option value="${esc(p.id)}">${indent}${esc(ref)} — ${esc(p.title)}</option>`,
    );
  }
  sel.innerHTML = opts.join("");
  sel.value = deadline.project_id;
}

async function loadRule(ruleID: string) {
  try {
    const resp = await fetch(`/api/deadline-rules`);
    if (!resp.ok) return;
    const all: DeadlineRule[] = await resp.json();
    rule = all.find((r) => r.id === ruleID) || null;
  } catch {
    /* non-fatal */
  }
}

async function loadMe() {
  try {
    const resp = await fetch("/api/me");
    if (resp.ok) me = await resp.json();
  } catch {
    /* non-fatal */
  }
}

// loadPendingRequest hydrates the in-flight approval_request when the
// entity carries approval_status='pending'. Used to populate the badge
// tooltip + decide whether to show the Withdraw button (only the
// requester can withdraw).
async function loadPendingRequest(): Promise<void> {
  pendingRequest = null;
  if (!deadline || deadline.approval_status !== "pending" || !deadline.pending_request_id) {
    return;
  }
  try {
    const resp = await fetch(`/api/approval-requests/${deadline.pending_request_id}`);
    if (resp.ok) pendingRequest = await resp.json();
  } catch {
    /* non-fatal — badge still renders without the tooltip details */
  }
}

function render() {
  if (!deadline) return;
  (document.getElementById("deadline-title-display") as HTMLElement).textContent = deadline.title;
  (document.getElementById("deadline-title-edit") as HTMLInputElement).value = deadline.title;

  const dueChip = document.getElementById("deadline-due-chip")!;
  dueChip.className = `frist-due-chip ${urgencyClass(deadline.due_date, deadline.status)}`;
  dueChip.textContent = fmtDate(deadline.due_date);
  (document.getElementById("deadline-due-display") as HTMLElement).textContent = fmtDate(deadline.due_date);
  (document.getElementById("deadline-due-edit") as HTMLInputElement).value = deadline.due_date.slice(0, 10);

  const statusChip = document.getElementById("deadline-status-chip")!;
  statusChip.className = `entity-status-chip entity-status-${deadline.status}`;
  statusChip.textContent = tDyn(`deadlines.status.${deadline.status}`) || deadline.status;

  const projectLink = document.getElementById("deadline-project-link") as HTMLAnchorElement;
  if (project) {
    projectLink.href = `/projects/${project.id}`;
    projectLink.textContent = `${project.reference || ""} — ${project.title}`;
  } else {
    projectLink.href = `/projects/${deadline.project_id}`;
    projectLink.textContent = "—";
  }

  const ruleEl = document.getElementById("deadline-rule-display")!;
  if (rule) {
    const code = rule.rule_code || rule.code || "";
    ruleEl.textContent = code ? `${code} — ${rule.name}` : rule.name;
  } else if (deadline.rule_code) {
    // Fristenrechner-saved deadlines carry rule_code directly without
    // a rule_id (no rule UUID round-trips through the public API).
    ruleEl.textContent = deadline.rule_code;
  } else {
    ruleEl.textContent = "—";
  }

  (document.getElementById("deadline-source-display") as HTMLElement).textContent =
    tDyn(`deadlines.source.${deadline.source}`) || deadline.source;

  (document.getElementById("deadline-notes-display") as HTMLElement).textContent = deadline.notes || "—";
  (document.getElementById("deadline-notes-edit") as HTMLTextAreaElement).value = deadline.notes || "";

  // Event-Type display & picker (display always, picker only in edit mode).
  const etDisplay = document.getElementById("deadline-event-types-display");
  if (etDisplay) {
    const ids = deadline.event_type_ids ?? [];
    if (ids.length === 0) {
      etDisplay.innerHTML = "&mdash;";
    } else {
      etDisplay.innerHTML = ids
        .map((id) => {
          const et = eventTypeByID.get(id);
          if (!et) return "";
          return `<span class="entity-event-type-pill">${esc(eventTypeLabel(et))}</span>`;
        })
        .filter(Boolean)
        .join(" ");
      if (etDisplay.innerHTML === "") etDisplay.innerHTML = "&mdash;";
    }
  }
  if (eventTypePicker) {
    eventTypePicker.setIDs(deadline.event_type_ids ?? []);
  }

  (document.getElementById("deadline-created-display") as HTMLElement).textContent = fmtDateTime(deadline.created_at);

  const completedLabel = document.getElementById("deadline-completed-row-label")!;
  const completedDD = document.getElementById("deadline-completed-display")!;
  if (deadline.completed_at) {
    completedLabel.style.display = "";
    completedDD.style.display = "";
    completedDD.textContent = fmtDateTime(deadline.completed_at);
  } else {
    completedLabel.style.display = "none";
    completedDD.style.display = "none";
  }

  const completeBtn = document.getElementById("deadline-complete-btn") as HTMLButtonElement;
  const reopenBtn = document.getElementById("deadline-reopen-btn") as HTMLButtonElement;
  const withdrawBtn = document.getElementById("deadline-withdraw-btn") as HTMLButtonElement;
  const editBtn = document.getElementById("deadline-edit-btn") as HTMLButtonElement;
  const badge = document.getElementById("deadline-pending-approval-badge") as HTMLElement | null;

  // t-paliad-160 §C+E — approval_status='pending' freezes the action
  // controls and surfaces the badge + a Withdraw button (visible only to
  // the requester). Other authenticated viewers see only the badge.
  const isPending = deadline.approval_status === "pending";
  const isRequester = !!(me && pendingRequest && me.id === pendingRequest.requested_by);

  if (badge) {
    if (isPending) {
      badge.style.display = "";
      const labelDe = t("approvals.pending.badge") || "Wartet auf Genehmigung";
      badge.textContent = labelDe;
      // Tooltip carries requester + required_role + age (best-effort).
      if (pendingRequest) {
        const role = tDyn(`approvals.required_role.${pendingRequest.required_role}`) || pendingRequest.required_role;
        const requester = pendingRequest.requester_name || pendingRequest.requested_by;
        const when = fmtDateTime(pendingRequest.requested_at);
        badge.title = `${labelDe} · ${role}+ · ${requester} · ${when}`;
      } else {
        badge.title = labelDe;
      }
    } else {
      badge.style.display = "none";
      badge.title = "";
    }
  }

  // Buttons.
  if (deadline.status === "completed") {
    completeBtn.style.display = "none";
    if (me && (me.global_role === "global_admin") && !isPending) {
      reopenBtn.style.display = "";
      reopenBtn.disabled = false;
    } else {
      reopenBtn.style.display = "none";
    }
  } else if (isPending) {
    // Lifecycle frozen — server returns 409 to anyone who tries.
    completeBtn.style.display = "none";
    reopenBtn.style.display = "none";
  } else {
    completeBtn.style.display = "";
    completeBtn.disabled = false;
    completeBtn.textContent = t("deadlines.detail.complete");
    reopenBtn.style.display = "none";
  }

  // Edit button: hidden during pending so users don't fight a 409.
  if (editBtn) editBtn.style.display = isPending ? "none" : "";

  // Withdraw button: visible only when caller is the requester of the
  // in-flight request.
  if (withdrawBtn) {
    if (isPending && isRequester) {
      withdrawBtn.style.display = "";
      withdrawBtn.disabled = false;
    } else {
      withdrawBtn.style.display = "none";
    }
  }

  const deleteWrap = document.getElementById("deadline-delete-wrap")!;
  if (me && (me.global_role === "global_admin") && !isPending) {
    deleteWrap.style.display = "";
  } else {
    deleteWrap.style.display = "none";
  }
}

function initEdit() {
  const titleDisplay = document.getElementById("deadline-title-display")!;
  const titleEdit = document.getElementById("deadline-title-edit") as HTMLInputElement;
  const dueDisplay = document.getElementById("deadline-due-display")!;
  const dueEdit = document.getElementById("deadline-due-edit") as HTMLInputElement;
  const notesDisplay = document.getElementById("deadline-notes-display")!;
  const notesEdit = document.getElementById("deadline-notes-edit") as HTMLTextAreaElement;
  const editBtn = document.getElementById("deadline-edit-btn") as HTMLButtonElement;
  const saveBtn = document.getElementById("deadline-save-btn") as HTMLButtonElement;
  const etDisplay = document.getElementById("deadline-event-types-display");
  const etEdit = document.getElementById("deadline-event-types-edit");
  const projectLink = document.getElementById("deadline-project-link") as HTMLAnchorElement;
  const projectEdit = document.getElementById("deadline-project-edit") as HTMLSelectElement | null;

  function enterEdit() {
    titleDisplay.style.display = "none";
    titleEdit.style.display = "";
    dueDisplay.style.display = "none";
    dueEdit.style.display = "";
    notesDisplay.style.display = "none";
    notesEdit.style.display = "";
    if (etDisplay) etDisplay.style.display = "none";
    if (etEdit) etEdit.style.display = "";
    if (projectEdit && deadline) {
      projectLink.style.display = "none";
      projectEdit.style.display = "";
      projectEdit.value = deadline.project_id;
    }
    saveBtn.style.display = "";
    editBtn.style.display = "none";
    titleEdit.focus();
    titleEdit.select();
  }
  function exitEdit() {
    titleDisplay.style.display = "";
    titleEdit.style.display = "none";
    dueDisplay.style.display = "";
    dueEdit.style.display = "none";
    notesDisplay.style.display = "";
    notesEdit.style.display = "none";
    if (etDisplay) etDisplay.style.display = "";
    if (etEdit) etEdit.style.display = "none";
    if (projectEdit) {
      projectEdit.style.display = "none";
      projectLink.style.display = "";
    }
    saveBtn.style.display = "none";
    editBtn.style.display = "";
  }

  editBtn.addEventListener("click", enterEdit);

  saveBtn.addEventListener("click", async () => {
    if (!deadline) return;
    const newTitle = titleEdit.value.trim();
    const newDue = dueEdit.value;
    const newNotes = notesEdit.value;
    if (!newTitle || !newDue) return;
    saveBtn.disabled = true;
    try {
      const payload: Record<string, unknown> = {
        title: newTitle,
        due_date: newDue,
        notes: newNotes,
      };
      if (eventTypePicker) {
        payload.event_type_ids = eventTypePicker.getIDs();
      }
      if (projectEdit && projectEdit.value && projectEdit.value !== deadline.project_id) {
        payload.project_id = projectEdit.value;
      }
      const resp = await fetch(`/api/deadlines/${deadline.id}`, {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(payload),
      });
      if (resp.ok) {
        const prevProjectID = deadline.project_id;
        deadline = await resp.json();
        if (deadline && deadline.project_id !== prevProjectID) {
          await loadProject(deadline.project_id);
        }
        render();
      }
    } finally {
      saveBtn.disabled = false;
      exitEdit();
    }
  });
}

function initComplete() {
  const btn = document.getElementById("deadline-complete-btn") as HTMLButtonElement;
  btn.addEventListener("click", async () => {
    if (!deadline || deadline.status === "completed") return;
    btn.disabled = true;
    try {
      const resp = await fetch(`/api/deadlines/${deadline.id}/complete`, { method: "PATCH" });
      if (resp.ok) {
        deadline = await resp.json();
        // The complete may have created an approval_request rather than
        // completed the deadline outright (4-eye-required). Re-fetch the
        // entity + pending request to surface the right state.
        const fresh = await fetch(`/api/deadlines/${deadline.id}`);
        if (fresh.ok) deadline = await fresh.json();
        await loadPendingRequest();
        render();
      } else if (resp.status === 409) {
        // The handler returns the t-paliad-160 §B body shape. Surface
        // the human message and refresh state — likely a concurrent
        // request was already in flight.
        const body = await resp.json().catch(() => null);
        const msg = (body && body.message) || t("approvals.error.awaiting_approval") || "Diese Anforderung wartet auf Genehmigung.";
        window.alert(msg);
        const fresh = await fetch(`/api/deadlines/${deadline.id}`);
        if (fresh.ok) {
          deadline = await fresh.json();
          await loadPendingRequest();
        }
        render();
      } else {
        btn.disabled = false;
      }
    } catch {
      btn.disabled = false;
    }
  });
}

function initReopen() {
  const btn = document.getElementById("deadline-reopen-btn") as HTMLButtonElement;
  btn.addEventListener("click", async () => {
    if (!deadline || deadline.status !== "completed") return;
    btn.disabled = true;
    try {
      const resp = await fetch(`/api/deadlines/${deadline.id}/reopen`, { method: "PATCH" });
      if (resp.ok) {
        deadline = await resp.json();
        render();
      } else {
        btn.disabled = false;
      }
    } catch {
      btn.disabled = false;
    }
  });
}

// initWithdraw — t-paliad-160 §C+E. Reuses the existing
// /api/approval-requests/{id}/revoke endpoint (no new server route
// needed). After the revoke lands, the entity goes back to
// approval_status='approved' and the page reloads to refresh the
// in-memory state cleanly.
function initWithdraw() {
  const btn = document.getElementById("deadline-withdraw-btn") as HTMLButtonElement | null;
  if (!btn) return;
  btn.addEventListener("click", async () => {
    if (!deadline || !pendingRequest) return;
    if (!window.confirm(t("approvals.withdraw.confirm") || "Anfrage wirklich zurückziehen?")) return;
    btn.disabled = true;
    try {
      const resp = await fetch(`/api/approval-requests/${pendingRequest.id}/revoke`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({}),
      });
      if (resp.ok) {
        // Re-fetch the entity so approval_status flips back to 'approved'
        // and the badge / buttons rerender accordingly.
        const r = await fetch(`/api/deadlines/${deadline.id}`);
        if (r.ok) {
          deadline = await r.json();
          await loadPendingRequest();
          render();
        } else {
          window.location.reload();
        }
      } else {
        btn.disabled = false;
        const body = await resp.json().catch(() => null);
        const msg = (body && (body.message || body.error)) || (t("approvals.withdraw.error") || "Fehler beim Zurückziehen");
        window.alert(msg);
      }
    } catch (e) {
      btn.disabled = false;
      window.alert((t("approvals.withdraw.error") || "Fehler beim Zurückziehen") + ": " + e);
    }
  });
}

function initDelete() {
  const btn = document.getElementById("deadline-delete-btn")!;
  const modal = document.getElementById("deadline-delete-modal")!;
  const close = document.getElementById("deadline-delete-modal-close")!;
  const cancel = document.getElementById("deadline-delete-modal-cancel")!;
  const confirmBtn = document.getElementById("deadline-delete-modal-confirm") as HTMLButtonElement;

  btn.addEventListener("click", () => {
    modal.style.display = "flex";
  });
  const closeModal = () => {
    modal.style.display = "none";
  };
  close.addEventListener("click", closeModal);
  cancel.addEventListener("click", closeModal);
  modal.addEventListener("click", (e) => {
    if (e.target === e.currentTarget) closeModal();
  });
  confirmBtn.addEventListener("click", async () => {
    if (!deadline) return;
    confirmBtn.disabled = true;
    const resp = await fetch(`/api/deadlines/${deadline.id}`, { method: "DELETE" });
    if (resp.ok) {
      const target = project ? `/projects/${project.id}/deadlines` : "/events?type=deadline";
      window.location.href = target;
    } else {
      confirmBtn.disabled = false;
      closeModal();
    }
  });
}

async function main() {
  const id = parseDeadlineID();
  const loading = document.getElementById("deadline-loading")!;
  const notfound = document.getElementById("deadline-notfound")!;
  const body = document.getElementById("deadline-body")!;
  if (!id) {
    loading.style.display = "none";
    notfound.style.display = "block";
    return;
  }
  await loadMe();
  const ok = await loadDeadline(id);
  if (!ok || !deadline) {
    loading.style.display = "none";
    notfound.style.display = "block";
    return;
  }
  await Promise.all([loadProject(deadline.project_id), loadAllProjects(), loadPendingRequest()]);
  if (deadline.rule_id) await loadRule(deadline.rule_id);

  // Load event types in parallel; render once ready (the picker re-renders
  // chips off the cached map, and the display element re-renders on the
  // next render() call after data lands).
  try {
    const types = await fetchEventTypes();
    eventTypeByID = new Map(types.map((et) => [et.id, et]));
  } catch {
    /* non-fatal */
  }

  loading.style.display = "none";
  body.style.display = "";

  // Mount the picker (hidden until enterEdit()).
  const pickerHost = document.getElementById("deadline-event-types-edit");
  if (pickerHost) {
    eventTypePicker = attachEventTypePicker(pickerHost, {
      initialIDs: deadline.event_type_ids ?? [],
      currentUserAdmin: me?.global_role === "global_admin",
    });
  }

  populateProjectPicker();
  render();
  initEdit();
  initComplete();
  initReopen();
  initWithdraw();
  initDelete();

  const notes = document.getElementById("notes-container");
  if (notes) {
    notes.setAttribute("data-parent-id", id);
    void initNotes(notes as HTMLElement, "deadline", id);
  }
}

document.addEventListener("DOMContentLoaded", () => {
  initI18n();
  initSidebar();
  onLangChange(render);
  main();
});
