{% extends "base.html" %}

{% block title %}Ontology Graph — Ontoref{% endblock title %}
{% block nav_graph %}active{% endblock nav_graph %}

{% block head %}
<script src="https://cdn.jsdelivr.net/npm/cytoscape@3.30.2/dist/cytoscape.min.js"></script>
<style>
  #graph-root {
    display: flex;
    height: calc(100vh - 148px);
    min-height: 400px;
    gap: 0;
    user-select: none;
  }
  #cy-wrapper {
    flex: 1 1 auto;
    min-width: 220px;
    overflow: hidden;
    border-radius: 0.5rem;
  }
  #cy { width: 100%; height: 100%; }

  #resize-handle {
    flex: 0 0 6px;
    cursor: col-resize;
    background: transparent;
    position: relative;
    z-index: 20;
    transition: background 0.15s;
  }
  #resize-handle:hover,
  #resize-handle.dragging { background: oklch(var(--p) / 0.5); }
  #resize-handle::after {
    content: "";
    position: absolute;
    left: 2px;
    top: 50%;
    transform: translateY(-50%);
    width: 2px;
    height: 40px;
    border-radius: 99px;
    background: oklch(var(--bc) / 0.25);
  }

  #detail-panel {
    flex: 0 0 300px;
    min-width: 160px;
    max-width: 60%;
    overflow-y: auto;
    overflow-x: hidden;
    border-radius: 0.5rem;
  }
</style>
{% endblock head %}

{% block content %}
<!-- Toolbar -->
<div class="mb-2 flex flex-wrap items-center justify-between gap-2 text-sm">
  <h1 class="text-xl font-bold">Ontology Graph</h1>
  <div class="flex flex-wrap gap-1 items-center">
    <!-- Level filters (toggle = hide that level) -->
    <button class="filter-btn btn btn-xs btn-warning"   data-level="Axiom">◆ Axiom</button>
    <button class="filter-btn btn btn-xs btn-error"     data-level="Tension">● Tension</button>
    <button class="filter-btn btn btn-xs btn-success"   data-level="Practice">▪ Practice</button>
    <div class="w-px h-4 bg-base-content/20 mx-1"></div>
    <!-- Pole filters -->
    <button class="filter-btn btn btn-xs" style="background:#f59e0b;color:#111;border-color:#f59e0b" data-pole="Yang">Yang</button>
    <button class="filter-btn btn btn-xs" style="background:#3b82f6;color:#fff;border-color:#3b82f6" data-pole="Yin">Yin</button>
    <button class="filter-btn btn btn-xs" style="background:#8b5cf6;color:#fff;border-color:#8b5cf6" data-pole="Spiral">Spiral</button>
    <div class="w-px h-4 bg-base-content/20 mx-1"></div>
    <!-- Layout -->
    <div class="join">
      <button id="btn-bfs"  class="join-item btn btn-xs btn-primary">Hierarchy</button>
      <button id="btn-cose" class="join-item btn btn-xs btn-ghost">Force</button>
    </div>
    <button id="btn-reset" class="btn btn-xs btn-ghost">Reset</button>
  </div>
</div>

<!-- Split: graph | drag handle | detail -->
<div id="graph-root">
  <div id="cy-wrapper" class="bg-base-200">
    <div id="cy"></div>
  </div>

  <div id="resize-handle"></div>

  <div id="detail-panel" class="bg-base-200 p-4 hidden">
    <div class="flex justify-between items-start mb-2">
      <h3 class="font-bold text-base leading-tight" id="d-name"></h3>
      <button id="btn-close-panel" class="btn btn-xs btn-ghost ml-2 flex-shrink-0">✕</button>
    </div>
    <div class="flex flex-wrap gap-1 mb-2" id="d-badges"></div>
    <p class="text-xs text-base-content/70 mb-3 leading-relaxed" id="d-description"></p>
    <div id="d-artifacts" class="hidden mb-3">
      <p class="text-xs font-semibold text-base-content/40 uppercase tracking-wider mb-1">Artifacts</p>
      <ul id="d-artifact-list" class="text-xs font-mono text-base-content/60 space-y-1 break-all"></ul>
    </div>
    <div id="d-edges" class="hidden">
      <p class="text-xs font-semibold text-base-content/40 uppercase tracking-wider mb-1">Connections</p>
      <ul id="d-edge-list" class="text-xs text-base-content/60 space-y-1"></ul>
    </div>
  </div>
</div>
{% endblock content %}

{% block scripts %}
<script>
const GRAPH = {{ graph_json | safe }};

const POLE_COLOR = { Yang: "#f59e0b", Yin: "#3b82f6", Spiral: "#8b5cf6" };
const LEVEL_SHAPE = {
  Axiom:    "diamond",
  Tension:  "ellipse",
  Practice: "round-rectangle",
  Project:  "hexagon",
  Moment:   "triangle",
};
const EDGE_STYLE = {
  ManifestsIn: { color: "#6b7280" },
  DependsOn:   { color: "#ef4444" },
  Resolves:    { color: "#22c55e" },
  Implies:     { color: "#f59e0b" },
  Complements: { color: "#3b82f6" },
  Contradicts: { color: "#ec4899", dashed: true },
  TensionWith: { color: "#f97316", dashed: true },
  Contains:    { color: "#a3a3a3" },
  FlowsTo:     { color: "#06b6d4" },
  SpiralsWith: { color: "#8b5cf6" },
  LimitedBy:   { color: "#f43f5e" },
  ValidatedBy: { color: "#84cc16" },
};

const nodes = (GRAPH.nodes || []).map(n => ({
  data: {
    id:             n.id,
    label:          n.name || n.id,
    pole:           n.pole || "",
    level:          n.level || "",
    description:    n.description || "",
    invariant:      !!n.invariant,
    artifact_paths: n.artifact_paths || [],
    color:          POLE_COLOR[n.pole] || "#6b7280",
    shape:          LEVEL_SHAPE[n.level] || "ellipse",
  }
}));

const edges = (GRAPH.edges || []).map(e => {
  const s = EDGE_STYLE[e.kind] || {};
  return {
    data: {
      id:     `${e.from}__${e.to}__${e.kind}`,
      source: e.from,
      target: e.to,
      kind:   e.kind || "",
      note:   e.note || "",
      color:  s.color || "#6b7280",
      dashed: !!s.dashed,
    }
  };
});

// ── Cytoscape ────────────────────────────────────────────────

const cy = cytoscape({
  container: document.getElementById("cy"),
  elements: { nodes, edges },
  style: [
    {
      selector: "node",
      style: {
        "background-color":        "data(color)",
        "shape":                   "data(shape)",
        "width":                   32,
        "height":                  32,
        "border-width":            2,
        "border-color":            "#111827",
        "label":                   "data(label)",
        "color":                   "#f9fafb",
        "font-size":               "10px",
        "font-family":             "ui-sans-serif, system-ui, sans-serif",
        "font-weight":             500,
        "text-valign":             "bottom",
        "text-halign":             "center",
        "text-margin-y":           6,
        "text-wrap":               "wrap",
        "text-max-width":          "100px",
        "text-background-color":   "#0f172a",
        "text-background-opacity": 0.85,
        "text-background-shape":   "round-rectangle",
        "text-background-padding": "3px",
        "text-outline-width":      0,
      }
    },
    {
      selector: "node[?invariant]",
      style: { "border-color": "#f59e0b", "border-width": 3 }
    },
    {
      selector: "node:selected",
      style: { "border-color": "#ffffff", "border-width": 3 }
    },
    {
      selector: "node.faded",
      style: { "opacity": 0.2, "text-opacity": 0.2 }
    },
    {
      selector: "edge",
      style: {
        "line-color":         "data(color)",
        "target-arrow-color": "data(color)",
        "target-arrow-shape": "triangle",
        "curve-style":        "bezier",
        "width":              1.5,
        "arrow-scale":        0.8,
        "opacity":            0.6,
      }
    },
    {
      selector: "edge[?dashed]",
      style: { "line-style": "dashed", "line-dash-pattern": [6, 4] }
    },
    {
      selector: "edge.faded",
      style: { "opacity": 0.05 }
    },
    {
      selector: "edge.highlighted",
      style: {
        "opacity": 1,
        "width":   2.5,
        "label":   "data(kind)",
        "font-size": "9px",
        "color":   "#e5e7eb",
        "text-background-color":   "#0f172a",
        "text-background-opacity": 0.85,
        "text-background-padding": "2px",
        "text-background-shape":   "round-rectangle",
      }
    },
  ],
  layout: buildBfsLayout(),
  wheelSensitivity: 0.3,
  boxSelectionEnabled: false,
  minZoom: 0.1,
  maxZoom: 4,
});

function buildBfsLayout() {
  return {
    name: "breadthfirst",
    directed: true,
    animate: false,
    spacingFactor: 2.2,
    padding: 48,
    fit: true,
    avoidOverlap: true,
    nodeDimensionsIncludeLabels: true,
  };
}

function buildCoseLayout() {
  return {
    name:             "cose",
    animate:          false,
    randomize:        true,
    componentSpacing: 120,
    nodeRepulsion:    800000,
    nodeOverlap:      80,
    idealEdgeLength:  220,
    edgeElasticity:   20,
    nestingFactor:    1.0,
    gravity:          2,
    numIter:          3000,
    initialTemp:      2000,
    coolingFactor:    0.99,
    minTemp:          1.0,
    fit:              true,
    padding:          60,
  };
}

// ── Filters ──────────────────────────────────────────────────

// Track which levels/poles are hidden
const hiddenLevels = new Set();
const hiddenPoles  = new Set();

function applyFilters() {
  cy.nodes().forEach(n => {
    const hide = hiddenLevels.has(n.data("level")) || hiddenPoles.has(n.data("pole"));
    if (hide) n.hide(); else n.show();
  });
  cy.edges().forEach(e => {
    if (e.source().hidden() || e.target().hidden()) e.hide();
    else e.show();
  });
}

document.querySelectorAll(".filter-btn[data-level]").forEach(btn => {
  btn.addEventListener("click", () => {
    const level = btn.dataset.level;
    if (hiddenLevels.has(level)) {
      hiddenLevels.delete(level);
      btn.style.opacity = "1";
      btn.style.textDecoration = "";
    } else {
      hiddenLevels.add(level);
      btn.style.opacity = "0.4";
      btn.style.textDecoration = "line-through";
    }
    applyFilters();
  });
});

document.querySelectorAll(".filter-btn[data-pole]").forEach(btn => {
  btn.addEventListener("click", () => {
    const pole = btn.dataset.pole;
    if (hiddenPoles.has(pole)) {
      hiddenPoles.delete(pole);
      btn.style.opacity = "1";
      btn.style.textDecoration = "";
    } else {
      hiddenPoles.add(pole);
      btn.style.opacity = "0.4";
      btn.style.textDecoration = "line-through";
    }
    applyFilters();
  });
});

// ── Layout buttons ───────────────────────────────────────────

const btnBfs  = document.getElementById("btn-bfs");
const btnCose = document.getElementById("btn-cose");

btnBfs.addEventListener("click", () => {
  cy.layout(buildBfsLayout()).run();
  btnBfs.classList.add("btn-primary");   btnBfs.classList.remove("btn-ghost");
  btnCose.classList.add("btn-ghost");    btnCose.classList.remove("btn-primary");
});

btnCose.addEventListener("click", () => {
  cy.layout(buildCoseLayout()).run();
  btnCose.classList.add("btn-primary"); btnCose.classList.remove("btn-ghost");
  btnBfs.classList.add("btn-ghost");    btnBfs.classList.remove("btn-primary");
});

document.getElementById("btn-reset").addEventListener("click", () => {
  cy.elements().removeClass("faded highlighted");
  cy.fit(undefined, 48);
  closePanel();
});

// ── Node detail panel ────────────────────────────────────────

const panel     = document.getElementById("detail-panel");
const dName     = document.getElementById("d-name");
const dBadges   = document.getElementById("d-badges");
const dDesc     = document.getElementById("d-description");
const dArtifacts = document.getElementById("d-artifacts");
const dList     = document.getElementById("d-artifact-list");
const dEdges    = document.getElementById("d-edges");
const dEdgeList = document.getElementById("d-edge-list");

function closePanel() {
  panel.classList.add("hidden");
  cy.elements().removeClass("faded highlighted");
}

cy.on("tap", "node", evt => {
  const d = evt.target.data();
  panel.classList.remove("hidden");

  dName.textContent = d.label;
  dBadges.innerHTML =
    `<span class="badge badge-xs badge-outline">${d.level}</span>` +
    `<span class="badge badge-xs" style="background:${d.color};color:#111;border:none">${d.pole}</span>` +
    (d.invariant ? `<span class="badge badge-xs badge-warning">invariant</span>` : "");

  dDesc.textContent = d.description;

  if (d.artifact_paths.length) {
    dArtifacts.classList.remove("hidden");
    dList.innerHTML = d.artifact_paths.map(p =>
      `<li class="break-all"><code>${p}</code></li>`
    ).join("");
  } else {
    dArtifacts.classList.add("hidden");
  }

  const conn = evt.target.connectedEdges();
  if (conn.length) {
    dEdges.classList.remove("hidden");
    dEdgeList.innerHTML = conn.map(e => {
      const isSrc  = e.data("source") === d.id;
      const other  = isSrc
        ? cy.getElementById(e.data("target")).data("label")
        : cy.getElementById(e.data("source")).data("label");
      const arrow  = isSrc ? "→" : "←";
      return `<li class="flex gap-1"><span class="opacity-40 flex-shrink-0">${arrow}</span>` +
             `<span class="text-base-content/80 flex-shrink-0">${e.data("kind")}</span>` +
             `<span class="opacity-60 break-all">${other}</span></li>`;
    }).join("");
  } else {
    dEdges.classList.add("hidden");
  }

  // Dim non-neighbours
  cy.elements().addClass("faded").removeClass("highlighted");
  evt.target.closedNeighborhood().removeClass("faded").addClass("highlighted");
});

cy.on("tap", evt => {
  if (evt.target === cy) closePanel();
});

document.getElementById("btn-close-panel").addEventListener("click", closePanel);

// ── Resizable split ───────────────────────────────────────────

const handle     = document.getElementById("resize-handle");
const cyWrapper  = document.getElementById("cy-wrapper");
const graphRoot  = document.getElementById("graph-root");

let resizing = false;

handle.addEventListener("mousedown", e => {
  resizing = true;
  handle.classList.add("dragging");
  document.body.style.cursor = "col-resize";
  e.preventDefault();
});

document.addEventListener("mousemove", e => {
  if (!resizing) return;
  const rect  = graphRoot.getBoundingClientRect();
  const hW    = handle.offsetWidth;
  const cyW   = Math.max(220, e.clientX - rect.left);
  const panW  = Math.max(160, rect.width - cyW - hW);
  if (cyW + panW + hW <= rect.width + 2) {
    cyWrapper.style.flex = `0 0 ${cyW}px`;
    if (!panel.classList.contains("hidden")) {
      panel.style.flex = `0 0 ${panW}px`;
    }
    cy.resize();
  }
});

document.addEventListener("mouseup", () => {
  if (!resizing) return;
  resizing = false;
  handle.classList.remove("dragging");
  document.body.style.cursor = "";
});
</script>
{% endblock scripts %}
