{{ define "repo/pulls/fragments/diff" }}
  <div id="diff-files-content" hx-swap-oob="outerHTML">
    {{ template "repo/fragments/fileTree" .FileTree }}
  </div>
  <div id="diff-stats" hx-swap-oob="outerHTML">
    {{ template "repo/fragments/diffStatPill" .Stats }}
  </div>
  <div id="diff-settings" hx-swap-oob="outerHTML">
    {{ template "repo/pulls/fragments/diffSettings"
      (dict "DiffUrl" .DiffUrl
            "Unified" .Unified) }}
  </div>
  <script>
    (() => {
      if (window.__diffRestrictSelect) return;
      window.__diffRestrictSelect = true;
      document.addEventListener('mousedown', (e) => {
        document.querySelectorAll('.diff[data-restrict-select]')
          .forEach(d => d.removeAttribute('data-restrict-select'));
        const side = e.target.closest('.diff-side');
        if (!side) return;
        const diff = side.closest('.diff');
        if (!diff) return;
        const isLeft = side === side.parentElement.firstElementChild;
        diff.setAttribute('data-restrict-select', isLeft ? 'left' : 'right');
      });
    })();
  </script>
  <div id="diff-list" class="space-y-4">
    {{ if .ErrorMsg }}
      <div class="flex items-center justify-center w-full min-h-32 p-4 rounded text-red-600 dark:text-red-300 bg-red-50 dark:bg-red-900/30 border border-red-300 dark:border-red-700">
        <span>{{ .ErrorMsg }}</span>
      </div>
    {{ else if .Files }}
      {{ range .Files }}
      <div class="drop-shadow-sm rounded overflow-clip">
        <details id="file-{{ .Id }}" class="group w-full" open>
          <summary class="list-none cursor-pointer sticky top-12 z-10 bg-slate-100 dark:bg-gray-900">
            <div class="flex justify-between rounded border bg-white dark:bg-gray-800 group-open:rounded-b-none border-gray-200 dark:border-gray-700">
              <div class="p-2 flex gap-2 items-center">
                <span class="group-open:hidden inline">{{ i "chevron-right" "size-4" }}</span>
                <span class="hidden group-open:inline">{{ i "chevron-down" "size-4" }}</span>
                {{ template "repo/fragments/diffStatPill" .Stats }}
                <span>{{ .Path }}</span>
              </div>
              <div class="px-2 flex gap-2 items-center">
                <label
                  data-review-btn="file-{{ .Id }}"
                  onclick="event.stopPropagation()"
                  class="review-btn hidden p-2 items-center gap-1 text-xs text-gray-400 dark:text-gray-500 hover:text-green-600 dark:hover:text-green-400 transition-colors cursor-pointer"
                  title="Mark as reviewed"
                >
                  <input
                    type="checkbox"
                    class="sr-only peer review-checkbox"
                    data-file-id="file-{{ .Id }}"
                  />
                  <span class="peer-checked:hidden">{{ i "circle" "size-4" }}</span>
                  <span class="hidden peer-checked:inline text-green-600 dark:text-green-400">{{ i "circle-check" "size-4" }}</span>
                  <span class="hidden md:inline">Reviewed</span>
                </label>
              </div>
            </div>
          </summary>

          <div class="rounded-b overflow-clip border-x border-b border-gray-200 dark:border-gray-700">
          {{ if .Note }}
            <div class="p-4 text-center text-gray-400 dark:text-gray-500 text-sm">{{ .Note }}</div>
          {{ else if $.Unified }}
            {{ template "diffUnified" . }}
          {{ else }}
            {{ template "diffSplit" . }}
          {{ end }}
          </div>
        </details>
      </div>
      {{ end }}
    {{ else }}
      <div class="flex items-center justify-center w-full min-h-32 p-4 rounded text-blue-600 dark:text-blue-300 bg-blue-50 dark:bg-blue-900/30 border border-blue-300 dark:border-blue-700">
        <span>No change between two revisions.</span>
      </div>
    {{ end }}
  </div>
  {{ template "activeFileHighlightScript" }}
  {{ template "fragments/line-quote-button" }}
  {{ template "reviewStateScript" }}
{{ end }}

{{ define "diffUnified" }}
<div class="diff w-full">
  {{ $name := .Id }}
  {{- range .Hunks -}}
    {{- if not .AtFileStart -}}<div class="diff-splitter">&middot;&middot;&middot;</div>{{- end -}}
    {{- range $i, $line := .Lines -}}
      {{- $lineId := "" -}}
      {{- if ge .New 0 -}}
        {{- $lineId = printf "%s-N%d" $name .New -}}
      {{- else -}}
        {{- $lineId = printf "%s-O%d" $name .Old -}}
      {{- end -}}
      {{- $cls := "" -}}
      {{- if eq .Op "+" -}}
        {{- $cls = "add" -}}
      {{- else if eq .Op "-" -}}
        {{- $cls = "del" -}}
      {{- end -}}
      <div id="{{ $lineId }}" class="diff-line {{ $cls }}">
        <a href="#{{ $lineId }}" class="diff-num">
          <span>{{ if gt .Old 0 }}{{ .Old }}{{ end }}</span>
          <span>{{ if gt .New 0 }}{{ .New }}{{ end }}</span>
        </a>
        <span class="diff-indicator">{{ .Op }}</span>
        <div class="diff-content">{{ .Content }}</div>
      </div>
    {{- end -}}
  {{- end -}}
</div>
{{ end }}

{{ define "diffSplit" }}
<div class="diff w-full">
  {{ $name := .Id }}
  {{- range .Hunks -}}
    {{- if not .AtFileStart -}}<div class="diff-splitter">&middot;&middot;&middot;</div>{{- end -}}
    {{- range $i, $row := .Rows -}}
      <div class="diff-line">
        {{- template "diffSplitSide" (list $name "O" $row.Left) -}}
        {{- template "diffSplitSide" (list $name "N" $row.Right) -}}
      </div>
    {{- end -}}
  {{- end -}}
</div>
{{ end }}

{{ define "diffSplitSide" }}
  {{- $name   := index . 0 -}}
  {{- $side   := index . 1 -}}
  {{- $line   := index . 2 -}}
  {{- $lineId := printf "%s-%s%d" $name $side $line.Num -}}
  {{- $cls    := "" -}}
  {{- $mark   := "" -}}
  {{-      if eq $line.Kind "del"   }}{{ $cls = "del" }}{{ $mark = "-" -}}
  {{- else if eq $line.Kind "add"   }}{{ $cls = "add" }}{{ $mark = "+" -}}
  {{- else if eq $line.Kind "empty" }}{{ $cls = "empty" -}}
  {{- end -}}
  <div {{ if ge $line.Num 0 }}id="{{ $lineId }}"{{ end }} class="diff-side {{ $cls }}">
    {{ if gt $line.Num 0 -}}
      <a href="#{{ $lineId }}" class="diff-num"><span>{{ $line.Num }}</span></a>
    {{- else -}}
      <span aria-hidden="true" class="diff-num"><span></span></span>
    {{- end }}
    <span class="diff-indicator">{{ $mark }}</span>
    <div class="diff-content">{{ $line.Content }}</div>
  </div>
{{ end }}

{{ define "activeFileHighlightScript" }}
  <script>
    (() => {
      if (window.__activeFileScrollHandler) {
        document.removeEventListener('scroll', window.__activeFileScrollHandler);
      }

      const filetreeLinks = document.querySelectorAll('.filetree-link');
      if (filetreeLinks.length === 0) return;

      const linkMap = new Map();
      filetreeLinks.forEach(link => {
        const path = link.getAttribute('data-path');
        if (path) linkMap.set('file-' + path, link);
      });

      let currentActive = null;
      const setActive = (link) => {
        if (link && link !== currentActive) {
          if (currentActive) currentActive.classList.remove('font-bold');
          link.classList.add('font-bold');
          currentActive = link;
        }
      };

      filetreeLinks.forEach(link => {
        link.addEventListener('click', () => setActive(link));
      });

      const topbar = document.querySelector('.sticky.top-0.z-20');
      const headerHeight = topbar ? topbar.offsetHeight : 0;

      const updateActiveFile = () => {
        const diffFiles = document.querySelectorAll('details[id^="file-"]');
        Array.from(diffFiles).some(file => {
          const rect = file.getBoundingClientRect();
          if (rect.top <= headerHeight && rect.bottom > headerHeight) {
            setActive(linkMap.get(file.id));
            return true;
          }
          return false;
        });
      };

      window.__activeFileScrollHandler = updateActiveFile;
      document.addEventListener('scroll', updateActiveFile);
      updateActiveFile();
    })();
  </script>
{{ end }}

{{ define "reviewStateScript" }}
  <script>
    (() => {
      const targetRepoDid = document.getElementById('pull-target-repo-did')?.value;
      const pullId = document.getElementById('pull-id')?.value;
      const activeVersionId = document.getElementById('pull-active-version-id')?.value;
      if (!targetRepoDid || !pullId || !activeVersionId) return;

      const REVIEWED_PREFIX = 'reviewed:';
      const MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000;

      const storageKey = REVIEWED_PREFIX + ':' + targetRepoDid + ':' + pullId + ':' + activeVersionId;

      const load = () => {
        try {
          const entry = JSON.parse(localStorage.getItem(storageKey) || '{}');
          return new Set(Array.isArray(entry) ? entry : (entry.files || []));
        }
        catch { return new Set(); }
      };

      const save = (reviewed) => {
        const liveIds = new Set(Array.from(allFiles()).map(d => d.id));
        localStorage.setItem(storageKey, JSON.stringify({
          files: Array.from(reviewed).filter(id => liveIds.has(id)),
          ts: Date.now(),
        }));
      };

      const pruneStale = () => {
        const now = Date.now();
        Object.keys(localStorage)
          .filter(k => k.startsWith(REVIEWED_PREFIX) && k !== storageKey)
          .forEach(k => {
            try {
              const entry = JSON.parse(localStorage.getItem(k));
              if (!entry.ts || now - entry.ts > MAX_AGE_MS) localStorage.removeItem(k);
            } catch { localStorage.removeItem(k); }
          });
      };
      if (Math.random() < 0.1) pruneStale();

      const allFiles = () =>
        document.querySelectorAll('details[id^="file-"]');

      const applyOne = (fileId, isReviewed) => {
        const detail = document.getElementById(fileId);
        if (!detail) return;

        const btn = detail.querySelector('[data-review-btn]');
        const checkbox = btn?.querySelector('input[type="checkbox"]');
        const path = CSS.escape(fileId.replace('file-', ''));
        const treeLink = document.querySelector(`.filetree-link[data-path="${path}"]`);

        detail.classList.toggle('opacity-60', isReviewed);

        if (checkbox) checkbox.checked = isReviewed;

        if (treeLink) {
          const existing = treeLink.parentElement.querySelector('.review-indicator');
          if (isReviewed && !existing) {
            const indicator = document.createElement('span');
            indicator.className = 'review-indicator text-green-600 dark:text-green-400 flex-shrink-0';
            indicator.innerHTML = '&#10003;';
            treeLink.parentElement.appendChild(indicator);
          } else if (!isReviewed && existing) {
            existing.remove();
          }
        }
      };

      const updateProgress = (reviewed) => {
        const el = document.getElementById('changed-files-label');
        if (!el) return;
        const total = parseInt(el.dataset.total, 10);
        const files = allFiles();
        const count = Array.from(files).filter(d => reviewed.has(d.id)).length;
        const suffix = total === 1 ? 'file' : 'files';
        const allDone = count === total;
        el.classList.toggle('text-green-600', allDone);
        el.classList.toggle('dark:text-green-400', allDone);
        el.classList.toggle('text-gray-600', !allDone);
        el.classList.toggle('dark:text-gray-400', !allDone);
        el.textContent = count > 0
          ? `${count}/${total} ${suffix} reviewed`
          : `${total} changed ${suffix}`;
      };

      const reviewed = load();

      const toggleReview = (fileId) => {
        const detail = document.getElementById(fileId);
        if (!detail) return;
        const isNowReviewed = !reviewed.has(fileId);
        if (isNowReviewed) {
          reviewed.add(fileId);
          detail.open = false;
        } else {
          reviewed.delete(fileId);
        }
        save(reviewed);
        applyOne(fileId, isNowReviewed);
        updateProgress(reviewed);
      };

      document.getElementById('diff-list').addEventListener('change', (e) => {
        const checkbox = e.target.closest('.review-checkbox');
        if (!checkbox) return;
        const fileId = checkbox.dataset.fileId;
        if (fileId) toggleReview(fileId);
      });

      document.querySelectorAll('.review-btn').forEach(btn => {
        btn.classList.remove('hidden');
        btn.classList.add('flex');
      });

      allFiles().forEach(detail => {
        if (reviewed.has(detail.id)) {
          applyOne(detail.id, true);
          detail.open = false;
        }
      });
      updateProgress(reviewed);
    })();
  </script>
{{ end }}
