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

// Tree view of paliad.projects rendered into a container element.
// t-paliad-149 redesign: tree fetches with the orchestrator's chip / search
// state encoded as query params on /api/projects/tree, so the cache invalidates
// when the orchestrator calls refreshProjectTree({ params }).

export interface ProjectTreeNode {
  id: string;
  type: string;
  parent_id?: string | null;
  path: string;
  title: string;
  reference?: string | null;
  status: string;
  client_number?: string | null;
  matter_number?: string | null;
  open_deadlines: number;
  overdue_deadlines: number;
  // t-paliad-149: subtree-aggregated counts populated when ?subtree_counts=true
  // (the new default). Per-node fields above stay populated regardless.
  open_deadlines_subtree?: number;
  overdue_deadlines_subtree?: number;
  // t-paliad-149: pin state on /api/projects/tree response.
  pinned?: boolean;
  // t-paliad-149: greyed-ancestor flag (Scope=Mine / Scope=Pinned).
  inherited_visibility?: boolean;
  // t-paliad-149: search match kind. Empty when no search active.
  match_kind?: "self" | "ancestor" | "descendant" | "";
  children: ProjectTreeNode[];
}

let cache: ProjectTreeNode[] | null = null;
let cacheParams = "";
let useSubtreeCounts = true;
const expandedKey = "paliad.projectTree.expanded";
let expanded = loadExpanded();

function loadExpanded(): Set<string> {
  try {
    const raw = sessionStorage.getItem(expandedKey);
    if (!raw) return new Set();
    const arr = JSON.parse(raw) as string[];
    return new Set(arr);
  } catch {
    return new Set();
  }
}

function saveExpanded() {
  try {
    sessionStorage.setItem(expandedKey, JSON.stringify([...expanded]));
  } catch {
    /* private mode etc. — ignore */
  }
}

function isExpanded(node: ProjectTreeNode, depth: number): boolean {
  if (expanded.has(node.id)) return true;
  if (expanded.has(`!${node.id}`)) return false;
  return depth < 2;
}

function setExpanded(node: ProjectTreeNode, depth: number, open: boolean) {
  expanded.delete(node.id);
  expanded.delete(`!${node.id}`);
  const defaultOpen = depth < 2;
  if (open !== defaultOpen) {
    expanded.add(open ? node.id : `!${node.id}`);
  }
  saveExpanded();
}

const typeIcons: Record<string, string> = {
  // Lucide-style 24x24 icons; recolour via currentColor.
  client:
    `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">` +
    `<rect x="2" y="7" width="20" height="14" rx="2"/>` +
    `<path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"/>` +
    `</svg>`,
  litigation:
    `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">` +
    `<path d="M16 16l3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1z"/>` +
    `<path d="M2 16l3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1z"/>` +
    `<path d="M7 21h10"/>` +
    `<path d="M12 3v18"/>` +
    `<path d="M3 7h18"/>` +
    `</svg>`,
  patent:
    `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">` +
    `<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2V7"/>` +
    `<path d="M3 7v12a2 2 0 0 0 2 2h0"/>` +
    `<path d="M21 3a2 2 0 0 0-2 2v14"/>` +
    `<line x1="9" y1="9" x2="15" y2="9"/>` +
    `<line x1="9" y1="13" x2="15" y2="13"/>` +
    `<line x1="9" y1="17" x2="13" y2="17"/>` +
    `</svg>`,
  case:
    `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">` +
    `<path d="M14 13l-7 7-3-3 7-7"/>` +
    `<path d="M11.5 7.5l5 5"/>` +
    `<path d="M16 3l5 5-3 3-5-5z"/>` +
    `<path d="M5 21h6"/>` +
    `</svg>`,
  project:
    `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">` +
    `<path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>` +
    `</svg>`,
};

const chevron =
  `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">` +
  `<polyline points="9 18 15 12 9 6"/>` +
  `</svg>`;

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

function clientMatter(n: ProjectTreeNode): string {
  if (n.client_number && n.matter_number) {
    return `${n.client_number}.${n.matter_number}`;
  }
  return n.client_number || n.matter_number || "";
}

function renderNode(node: ProjectTreeNode, depth: number): string {
  const hasChildren = node.children.length > 0;
  const open = hasChildren && isExpanded(node, depth);
  const typeLabel = tDyn(`projects.type.${node.type}`) || node.type;
  const statusLabel = tDyn(`projects.filter.status.${node.status}`) || node.status;
  const cm = clientMatter(node);
  const ref = node.reference || "";

  // Subtree-aggregated counts when available, per-node otherwise (the
  // chip-row toolbar can flip the orchestrator's subtree_counts param).
  const useSubtree = useSubtreeCounts && (node.open_deadlines_subtree !== undefined);
  const overdue = useSubtree ? (node.overdue_deadlines_subtree ?? 0) : node.overdue_deadlines;
  const openCount = useSubtree ? (node.open_deadlines_subtree ?? 0) : node.open_deadlines;

  const toggle = hasChildren
    ? `<button class="projekt-tree-toggle${open ? " is-open" : ""}" type="button" aria-label="${esc(t("projects.tree.toggle") || "Aufklappen / Zuklappen")}">${chevron}</button>`
    : `<span class="projekt-tree-toggle is-leaf" aria-hidden="true"></span>`;

  const icon = typeIcons[node.type] || typeIcons.project;

  // Pin star — always-visible (touch-friendly per design §4.6).
  const pinned = !!node.pinned;
  const pinLabel = pinned
    ? (t("projects.tree.unpin") || "Pin entfernen")
    : (t("projects.tree.pin") || "Anpinnen");
  const pinStar = `<button class="projekt-tree-pin${pinned ? " is-pinned" : ""}" type="button" data-action="pin" aria-label="${esc(pinLabel)}" title="${esc(pinLabel)}">${pinned ? starFilled : starOutline}</button>`;

  const subtreeHint = useSubtree
    ? (t("projects.tree.deadlines.subtree.tooltip") || "Inkl. Unterprojekte")
    : (t("projects.tree.deadlines.direct.tooltip") || "Nur direkt");

  let badges = "";
  if (overdue > 0) {
    const label = (t("projects.tree.deadlines.overdue") || "überfällig") + " — " + subtreeHint;
    badges += `<span class="projekt-tree-badge projekt-tree-badge-overdue" title="${esc(label)}">${overdue}</span>`;
  }
  if (openCount > 0 && overdue === 0) {
    const label = (t("projects.tree.deadlines.open") || "offene Fristen") + " — " + subtreeHint;
    badges += `<span class="projekt-tree-badge projekt-tree-badge-open" title="${esc(label)}">${openCount}</span>`;
  }

  const meta: string[] = [];
  if (ref) meta.push(`<span class="projekt-tree-ref">${esc(ref)}</span>`);
  if (cm) meta.push(`<span class="projekt-tree-cm">${esc(cm)}</span>`);

  const childMarkup = hasChildren && open
    ? `<ul class="projekt-tree-children" role="group">${node.children.map((c) => renderNode(c, depth + 1)).join("")}</ul>`
    : "";

  // Modifier classes for the row:
  //  - is-inherited: greyed-ancestor under Scope=Mine / Scope=Pinned (design §3.3)
  //  - is-match-self / is-match-ancestor / is-match-descendant: search highlighting
  const modifiers: string[] = [];
  if (node.inherited_visibility) modifiers.push("is-inherited");
  if (node.match_kind === "self") modifiers.push("is-match-self");
  if (node.match_kind === "ancestor") modifiers.push("is-match-ancestor");
  if (node.match_kind === "descendant") modifiers.push("is-match-descendant");
  const rowClass = ["projekt-tree-row", ...modifiers].join(" ");
  const inheritedHint = node.inherited_visibility
    ? ` title="${esc(t("projects.tree.inherited.context") || "Sichtbar wegen Unterprojekt")}"`
    : "";

  return (
    `<li class="projekt-tree-node" data-id="${esc(node.id)}" data-depth="${depth}" role="treeitem"` +
    (hasChildren ? ` aria-expanded="${open ? "true" : "false"}"` : "") +
    `>` +
    `<div class="${rowClass}" tabindex="0"${inheritedHint}>` +
    toggle +
    `<span class="projekt-tree-icon projekt-tree-icon-${esc(node.type)}">${icon}</span>` +
    `<span class="projekt-tree-title">${esc(node.title)}</span>` +
    `<span class="projekt-tree-type-chip entity-type-chip entity-type-${esc(node.type)}">${esc(typeLabel)}</span>` +
    (meta.length ? `<span class="projekt-tree-meta">${meta.join(" · ")}</span>` : "") +
    `<span class="projekt-tree-spacer"></span>` +
    badges +
    `<span class="projekt-tree-status entity-status-chip entity-status-${esc(node.status)}">${esc(statusLabel)}</span>` +
    pinStar +
    `</div>` +
    childMarkup +
    `</li>`
  );
}

// Lucide-style filled / outline star for the pin toggle.
const starFilled =
  `<svg viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round" aria-hidden="true">` +
  `<polygon points="12 2 15 9 22 10 17 15 18 22 12 18 6 22 7 15 2 10 9 9"/>` +
  `</svg>`;
const starOutline =
  `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round" aria-hidden="true">` +
  `<polygon points="12 2 15 9 22 10 17 15 18 22 12 18 6 22 7 15 2 10 9 9"/>` +
  `</svg>`;

// fetchTree calls /api/projects/tree with the orchestrator's query params.
// The cache key is the params string — any change reloads. Pass an empty
// URLSearchParams for the legacy "all visible projects" call.
async function fetchTree(container: HTMLElement, params: URLSearchParams): Promise<ProjectTreeNode[] | null> {
  const key = params.toString();
  if (cache && cacheParams === key) return cache;
  const url = key ? `/api/projects/tree?${key}` : "/api/projects/tree";
  try {
    const resp = await fetch(url);
    if (resp.status === 503) {
      container.innerHTML = `<div class="projekt-tree-unavailable" data-i18n="projects.unavailable">${esc(t("projects.unavailable") || "Projektverwaltung zurzeit nicht verfügbar")}</div>`;
      return null;
    }
    if (!resp.ok) {
      container.innerHTML = `<div class="projekt-tree-error">${esc(t("projects.tree.error") || "Baumansicht konnte nicht geladen werden.")}</div>`;
      return null;
    }
    cache = (await resp.json()) as ProjectTreeNode[];
    cacheParams = key;
    return cache;
  } catch {
    container.innerHTML = `<div class="projekt-tree-error">${esc(t("projects.tree.error") || "Baumansicht konnte nicht geladen werden.")}</div>`;
    return null;
  }
}

function attachHandlers(container: HTMLElement) {
  container.querySelectorAll<HTMLLIElement>(".projekt-tree-node").forEach((node) => {
    const row = node.querySelector<HTMLElement>(":scope > .projekt-tree-row");
    if (!row) return;
    const toggle = row.querySelector<HTMLElement>(".projekt-tree-toggle");
    const pinBtn = row.querySelector<HTMLElement>(".projekt-tree-pin");
    const id = node.dataset.id!;
    const depth = Number(node.dataset.depth || "0");

    const navigate = () => {
      window.location.href = `/projects/${id}`;
    };

    // Pin toggle — POST/DELETE, optimistic update on the row, hard refresh
    // of the cache afterwards so subtree counts (if visible) stay coherent.
    if (pinBtn) {
      pinBtn.addEventListener("click", (e) => {
        e.stopPropagation();
        e.preventDefault();
        const data = cache && findById(cache, id);
        if (!data) return;
        void togglePin(data, pinBtn);
      });
    }

    if (toggle && toggle.classList.contains("is-leaf")) {
      row.addEventListener("click", (e) => {
        if ((e.target as HTMLElement).closest(".projekt-tree-pin")) return;
        navigate();
      });
      row.addEventListener("keydown", (e) => {
        if (e.key === "Enter" || e.key === " ") {
          e.preventDefault();
          navigate();
        }
      });
      return;
    }

    if (toggle) {
      toggle.addEventListener("click", (e) => {
        e.stopPropagation();
        const data = cache && findById(cache, id);
        if (!data) return;
        const isOpen = node.getAttribute("aria-expanded") === "true";
        setExpanded(data, depth, !isOpen);
        rerender();
      });
    }

    row.addEventListener("click", (e) => {
      // Clicking the row (but not the toggle / pin) navigates.
      const target = e.target as HTMLElement;
      if (target.closest(".projekt-tree-toggle")) return;
      if (target.closest(".projekt-tree-pin")) return;
      navigate();
    });
    row.addEventListener("keydown", (e) => {
      if (e.key === "Enter") {
        e.preventDefault();
        navigate();
      } else if (e.key === " ") {
        e.preventDefault();
        const data = cache && findById(cache, id);
        if (!data) return;
        const isOpen = node.getAttribute("aria-expanded") === "true";
        setExpanded(data, depth, !isOpen);
        rerender();
      }
    });
  });
}

async function togglePin(node: ProjectTreeNode, btn: HTMLElement) {
  const wasPinned = !!node.pinned;
  // Optimistic flip — visually toggle the star immediately so the user
  // sees feedback even on slow networks.
  node.pinned = !wasPinned;
  btn.classList.toggle("is-pinned", node.pinned);
  btn.innerHTML = node.pinned ? starFilled : starOutline;
  const newLabel = node.pinned
    ? (t("projects.tree.unpin") || "Pin entfernen")
    : (t("projects.tree.pin") || "Anpinnen");
  btn.setAttribute("aria-label", newLabel);
  btn.setAttribute("title", newLabel);

  try {
    const method = wasPinned ? "DELETE" : "POST";
    const resp = await fetch(`/api/projects/${encodeURIComponent(node.id)}/pin`, { method });
    if (!resp.ok && resp.status !== 201 && resp.status !== 204) {
      // Revert on failure.
      node.pinned = wasPinned;
      btn.classList.toggle("is-pinned", node.pinned);
      btn.innerHTML = node.pinned ? starFilled : starOutline;
      return;
    }
    // Success — invalidate cache so the next chip-driven refresh
    // (e.g. user clicks "Angepinnt") gets fresh server state.
    cache = null;
    cacheParams = "";
  } catch {
    // Revert on network error.
    node.pinned = wasPinned;
    btn.classList.toggle("is-pinned", node.pinned);
    btn.innerHTML = node.pinned ? starFilled : starOutline;
  }
}

function findById(nodes: ProjectTreeNode[], id: string): ProjectTreeNode | null {
  for (const n of nodes) {
    if (n.id === id) return n;
    const found = findById(n.children, id);
    if (found) return found;
  }
  return null;
}

let mountContainer: HTMLElement | null = null;
let mountParams: URLSearchParams = new URLSearchParams();

function rerender() {
  if (!mountContainer || !cache) return;
  if (cache.length === 0) {
    mountContainer.innerHTML = `<div class="projekt-tree-empty">${esc(t("projects.empty.filtered") || "Keine Treffer.")}</div>`;
    return;
  }
  mountContainer.innerHTML =
    `<ul class="projekt-tree-root" role="tree">${cache.map((n) => renderNode(n, 0)).join("")}</ul>`;
  attachHandlers(mountContainer);
}

// initProjectTree mounts the tree at `container`. The optional params encode
// the orchestrator's chip / search state — see /api/projects/tree handler.
// Empty params → legacy "every visible project" behaviour.
export async function initProjectTree(container: HTMLElement, params?: URLSearchParams) {
  mountContainer = container;
  mountParams = params ? new URLSearchParams(params) : new URLSearchParams();
  // Honour the orchestrator's subtree_counts param when the tree renders
  // its badges. Default true.
  const sc = mountParams.get("subtree_counts");
  useSubtreeCounts = sc === null ? true : sc === "true";
  // If params changed, the cache is stale.
  if (cache && cacheParams !== mountParams.toString()) {
    cache = null;
  }
  if (!cache) {
    container.innerHTML = `<div class="projekt-tree-loading">${esc(t("projects.tree.loading") || "Baum wird geladen…")}</div>`;
    const data = await fetchTree(container, mountParams);
    if (!data) return;
  }
  rerender();
}

// refreshProjectTree forces a fresh fetch — used by the orchestrator
// after a chip change or after a pin toggle invalidates the cache.
export function refreshProjectTree(params?: URLSearchParams) {
  cache = null;
  cacheParams = "";
  if (mountContainer) {
    void initProjectTree(mountContainer, params || mountParams);
  }
}

// Re-render the visible tree on language change so labels follow.
export function rerenderProjectTree() {
  rerender();
}
