{{ define "repo/fragments/diff" }}
  <style>
    #filesToggle:checked ~ div label[for="filesToggle"] .show-text { display: none; }
    #filesToggle:checked ~ div label[for="filesToggle"] .hide-text { display: inline; }
    #filesToggle:not(:checked) ~ div label[for="filesToggle"] .hide-text { display: none; }
    #filesToggle:checked ~ div div#files { width: fit-content; max-width: 15vw; }
    #filesToggle:not(:checked) ~ div div#files { width: 0; display: none; margin-right: 0; }
    #filesToggle:not(:checked) ~ div div#resize-files { display: none; }
  </style>

  <div id="diff-area">
    {{ template "diffTopbar" . }}
    {{ block "diffLayout" . }} {{ end }}
    {{ template "fragments/resizable" }}
    {{ template "activeFileHighlight" }}
    {{ template "fragments/line-quote-button" }}
    {{ template "reviewState" }}
  </div>
{{ end }}

{{ define "diffTopbar" }}
  {{ $diff := index . 0 }}
  {{ $opts := index . 1 }}
  {{ $root := "" }}
  {{ if gt (len .) 2 }}
    {{ $root = index . 2 }}
  {{ end }}

  {{ block "filesCheckbox" $ }} {{ end }}
  {{ block "subsCheckbox" $ }} {{ end }}

  <!-- top bar -->
  <div class="sticky top-0 z-30 bg-slate-100 dark:bg-gray-900 flex items-center gap-2 col-span-full h-12 p-2 {{ if $root }}mt-4{{ end }}">
    <!-- left panel toggle -->
    {{ template "filesToggle" . }}

    <!-- stats -->
    {{ $stat := $diff.Stats }}
    {{ $count := len $diff.ChangedFiles }}
    {{ template "repo/fragments/diffStatPill" $stat }}
    <span id="changed-files-label" class="text-xs text-gray-600 dark:text-gray-400 hidden md:inline-flex" data-total="{{ $count }}">{{ $count }} changed file{{ if ne $count 1 }}s{{ end }}</span>

    {{ if $root }}
      {{ if $root.IsInterdiff }}
        <!-- interdiff indicator -->
        <div class="flex items-center gap-2 before:content-['|'] before:text-gray-300 dark:before:text-gray-600 before:mr-2">
          <span class="text-xs text-gray-600 dark:text-gray-400 uppercase tracking-wide">Interdiff</span>
          <a
            href="/{{ $root.RepoInfo.FullName }}/pulls/{{ $root.Pull.PullId }}/round/{{ sub $root.ActiveRound 1 }}"
            class="px-2 py-0.5 bg-white dark:bg-gray-700 rounded font-mono text-xs hover:bg-gray-50 dark:hover:bg-gray-600 border border-gray-300 dark:border-gray-600"
          >
            #{{ sub $root.ActiveRound 1 }}
          </a>
          <span class="text-gray-400 text-xs">→</span>
          <a
            href="/{{ $root.RepoInfo.FullName }}/pulls/{{ $root.Pull.PullId }}/round/{{ $root.ActiveRound }}"
            class="px-2 py-0.5 bg-white dark:bg-gray-700 rounded font-mono text-xs hover:bg-gray-50 dark:hover:bg-gray-600 border border-gray-300 dark:border-gray-600"
          >
            #{{ $root.ActiveRound }}
          </a>
        </div>
      {{ else if ne $root.ActiveRound nil }}
        <!-- diff round indicator -->
        <div class="flex items-center gap-2 before:content-['|'] before:text-gray-300 dark:before:text-gray-600 before:mr-2">
          <span class="text-xs text-gray-600 dark:text-gray-400 uppercase tracking-wide">Diff</span>
          <span class="px-2 py-0.5 bg-white dark:bg-gray-700 rounded font-mono text-xs border border-gray-300 dark:border-gray-600">
            <span class="hidden md:inline">round </span>#{{ $root.ActiveRound }}
          </span>
        </div>
      {{ end }}
    {{ end }}

    <!-- spacer -->
    <div class="flex-grow"></div>

    <!-- collapse diffs -->
    {{ template "collapseToggle" }}

    <!-- diff options -->
    {{ template "repo/fragments/diffOpts" $opts }}

    <!-- right panel toggle -->
    {{ block "subsToggle" $ }} {{ end }}
  </div>

{{ end }}

{{ define "resize-grip" }}
  {{ $id := index . 0 }}
  {{ $target := index . 1 }}
  {{ $direction := index . 2 }}
  <div id="{{ $id }}"
    data-resizer="vertical"
    data-target="{{ $target }}"
    data-direction="{{ $direction }}"
    class="resizer-vertical hidden md:flex w-4 sticky top-12 max-h-screen flex-col items-center justify-center group">
    <div class="w-1 h-16 group-hover:h-24 group-[.resizing]:h-24 transition-all rounded-full bg-gray-400 dark:bg-gray-500 group-hover:bg-gray-500 group-hover:dark:bg-gray-400"></div>
  </div>
{{ end }}

{{ define "diffLayout" }}
  {{ $diff := index . 0 }}
  {{ $opts := index . 1 }}
  {{ $padBottom := gt (len .) 2 }}

  <div class="flex col-span-full flex-grow">
    <!-- left panel -->
    <div id="files" class="w-0 hidden md:block overflow-hidden sticky top-12 max-h-screen overflow-y-auto {{ if $padBottom }}pb-12{{ end }}">
      <section class="overflow-x-auto text-sm px-6 py-2 border border-gray-200 dark:border-gray-700 w-full mx-auto min-h-full rounded bg-white dark:bg-gray-800 drop-shadow-sm">
        {{ template "repo/fragments/fileTree" $diff.FileTree }}
      </section>
    </div>

    {{ template "resize-grip" (list "resize-files" "files" "before") }}

    <!-- main content -->
    <div id="diff-files" class="flex-1 min-w-0 sticky top-12 {{ if $padBottom }}pb-12{{ end }}">
      {{ template "diffFiles" (list $diff $opts) }}
    </div>

  </div>
{{ end }}

{{ define "diffFiles" }}
  {{ $diff := index . 0 }}
  {{ $opts := index . 1 }}
  {{ $files := $diff.ChangedFiles }}
  {{ $isSplit := $opts.Split }}
  <div class="flex flex-col gap-4">
    {{ if eq (len $files) 0 }}
      <div class="text-center text-gray-500 dark:text-gray-400 py-8">
        <p>No differences found between the selected revisions.</p>
      </div>
    {{ else }}
      {{ range $idx, $file := $files }}
        {{ template "diffFile" (list $idx $file $isSplit) }}
      {{ end }}
    {{ end }}
  </div>
{{ end }}

{{ define "diffFile" }}
  {{ $idx := index . 0 }}
  {{ $file := index . 1 }}
  {{ $isSplit := index . 2 }}
  {{ $collapsed := false }}
  {{ if gt (len .) 3 }}{{ $collapsed = index . 3 }}{{ end }}
  {{ $isGenerated := false }}
  {{ $isDeleted := false }}
  {{ with $file }}
    {{ $n := .Names }}
    {{ $isDeleted = and (eq $n.New "") (ne $n.Old "") }}
    {{ if $n.New }}
      {{ $isGenerated = isGenerated $n.New }}
    {{ else if $n.Old }}
      {{ $isGenerated = isGenerated $n.Old }}
    {{ end }}
    <details {{ if and (not $isGenerated) (not $isDeleted) (not $collapsed) }}open{{ end }} id="file-{{ .Id }}" class="group border border-gray-200 dark:border-gray-700 w-full mx-auto rounded bg-white dark:bg-gray-800 drop-shadow-sm" tabindex="{{ add $idx 1 }}">
      <summary class="list-none cursor-pointer {{ if not $collapsed }}sticky top-12{{ end }} group-open:border-b border-gray-200 dark:border-gray-700">
        <div id="diff-file-header" class="rounded cursor-pointer bg-white dark:bg-gray-800 flex justify-between">
          <div id="left-side-items" class="p-2 flex gap-2 items-center overflow-x-auto">
            <span class="group-open:hidden inline">{{ i "chevron-right" "w-4 h-4" }}</span>
            <span class="hidden group-open:inline">{{ i "chevron-down" "w-4 h-4" }}</span>
            {{ template "repo/fragments/diffStatPill" .Stats }}

            <div class="flex gap-2 items-center overflow-x-auto">
              {{ if and $n.New $n.Old (ne $n.New $n.Old)}}
                {{ $n.Old }} {{ i "arrow-right" "w-4 h-4" }} {{ $n.New }}
              {{ else if $n.New }}
                {{ $n.New }}
              {{ else }}
                {{ $n.Old }}
              {{ end }}
              {{ if $isDeleted }}
                <span class="text-gray-400 dark:text-gray-500" title="Deleted files are collapsed by default">
                  {{ i "circle-question-mark" "size-4" }}
                </span>
              {{ else if $isGenerated }}
                <span class="text-gray-400 dark:text-gray-500" title="Generated files are collapsed by default">
                  {{ i "circle-question-mark" "size-4" }}
                </span>
              {{ end }}
            </div>
          </div>
          <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>
      </summary>

      <div class="transition-all duration-700 ease-in-out">
        {{ $reason := .CanRender }}
        {{ if $reason }}
          <p class="text-center text-gray-400 dark:text-gray-500 p-4">{{ $reason }}</p>
        {{ else }}
          {{ if $isSplit }}
            {{- template "repo/fragments/splitDiff" .Split -}}
          {{ else }}
            {{- template "repo/fragments/unifiedDiff" . -}}
          {{ end }}
        {{- end -}}
      </div>
    </details>
  {{ end }}
{{ end }}

{{ define "filesCheckbox" }}
  <input type="checkbox" id="filesToggle" class="peer/files hidden" {{ if gt (len .) 2 }}checked{{ end }}/>
{{ end }}

{{ define "filesToggle" }}
  <label title="Toggle filetree panel" for="filesToggle" class="hidden md:inline-flex items-center justify-center rounded cursor-pointer text-normal font-normal normalcase">
    <span class="show-text">{{ i "panel-left-open" "size-4" }}</span>
    <span class="hide-text">{{ i "panel-left-close" "size-4" }}</span>
  </label>
{{ end }}

{{ define "collapseToggle" }}
  <label
    title="Expand/Collapse diffs"
    for="collapseToggle"
    class="btn font-normal normal-case p-2"
  >
    <input type="checkbox" id="collapseToggle" class="peer/collapse hidden" checked/>
    <span class="peer-checked/collapse:hidden inline-flex items-center gap-2">
      {{ i "unfold-vertical" "w-4 h-4" }}
      <span class="hidden md:inline">expand all</span>
    </span>
    <span class="peer-checked/collapse:inline-flex hidden flex items-center gap-2">
      {{ i "fold-vertical" "w-4 h-4" }}
      <span class="hidden md:inline">collapse all</span>
    </span>
  </label>
  <script>
    (() => {
      const checkbox = document.getElementById('collapseToggle');
      const diffArea = document.getElementById('diff-area');

      checkbox.addEventListener('change', () => {
        document.querySelectorAll('details[id^="file-"]').forEach(detail => {
          detail.open = checkbox.checked;
        });
      });

      if (window.__collapseToggleHandler) {
        diffArea.removeEventListener('toggle', window.__collapseToggleHandler, true);
      }

      const handler = (e) => {
        if (!e.target.matches('details[id^="file-"]')) return;
        const details = document.querySelectorAll('details[id^="file-"]');
        const allOpen = Array.from(details).every(d => d.open);
        const allClosed = Array.from(details).every(d => !d.open);

        if (allOpen) checkbox.checked = true;
        else if (allClosed) checkbox.checked = false;
      };

      window.__collapseToggleHandler = handler;
      diffArea.addEventListener('toggle', handler, true);
    })();
  </script>
{{ end }}

{{ define "activeFileHighlight" }}
  <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-30');
      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 "reviewState" }}
  <script>
    (() => {
      const linkBase = document.getElementById('round-link-base');
      if (!linkBase) return;

      const isInterdiff = !!document.getElementById('is-interdiff');
      const basePath = linkBase.value.replace(/^\//, '');
      const storageKey = 'reviewed:' + basePath + (isInterdiff ? '/interdiff' : '');

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

      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-area').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 }}
