<script>
  import { onMount } from "svelte";

  let fileInput;
  let backtraceData = "";
  let threads = [];
  let filteredThreads = [];
  let filterText = "";
  let visibleThreads = new Set();

  // Parse the backtrace file format
  function parseBacktraceFile(content) {
    const lines = content.split("\n");
    const parsedThreads = [];
    let currentThread = null;
    let currentBacktrace = [];

    for (let i = 0; i < lines.length; i++) {
      const line = lines[i];

      // Simpler approach: look for lines containing "Thread_" with numbers at the start
      if (line.includes("Thread_") && /^\s*\d+\s+Thread_\d+/.test(line)) {
        // Save previous thread if exists
        if (currentThread) {
          currentThread.backtrace = currentBacktrace;
          currentThread.collapsibleBacktrace =
            createCollapsibleBacktrace(currentBacktrace);
          parsedThreads.push(currentThread);
        }

        // Extract sample count and thread ID
        const sampleMatch = line.match(/^\s*(\d+)\s+(Thread_\d+)/);
        const sampleCount = sampleMatch[1];
        const threadId = sampleMatch[2];

        // Extract thread name (everything after the first colon, before parentheses)
        let threadName = "";
        const colonIndex = line.indexOf(":");
        if (colonIndex !== -1) {
          const afterColon = line.substring(colonIndex + 1);
          const parenIndex = afterColon.indexOf("(");
          threadName = (
            parenIndex !== -1 ? afterColon.substring(0, parenIndex) : afterColon
          ).trim();
        }

        currentThread = {
          id: threadId,
          name: threadName,
          fullName: threadName || threadId,
          sampleCount: parseInt(sampleCount),
          rawHeader: line.trim(),
          backtrace: [],
          expanded: false,
          collapsibleBacktrace: null, // Will be created after backtrace is complete
        };
        currentBacktrace = [];
      } else if (currentThread && line.trim()) {
        // This is part of the backtrace
        currentBacktrace.push(line);
      }
      // Skip empty lines
    }

    // Don't forget the last thread
    if (currentThread) {
      currentThread.backtrace = currentBacktrace;
      currentThread.collapsibleBacktrace =
        createCollapsibleBacktrace(currentBacktrace);
      parsedThreads.push(currentThread);
    }

    return parsedThreads;
  }

  // Toggle thread visibility
  function toggleThread(threadId) {
    console.log("🔄 Toggling thread:", threadId);
    // Create a new array with updated thread to trigger Svelte reactivity
    threads = threads.map((thread) => {
      if (thread.id === threadId) {
        const newThread = { ...thread, expanded: !thread.expanded };
        console.log(
          "📊 Thread expanded:",
          newThread.expanded,
          "backtrace lines:",
          newThread.backtrace.length
        );
        return newThread;
      }
      return thread;
    });
  }

  // Handle file upload
  function handleFileUpload(event) {
    const file = event.target.files[0];
    if (file) {
      const reader = new FileReader();
      reader.onload = (e) => {
        backtraceData = e.target.result;
        threads = parseBacktraceFile(backtraceData);
      };
      reader.readAsText(file);
    }
  }

  // Load sample file on mount
  onMount(async () => {
    try {
      const response = await fetch("/sample.txt");
      if (response.ok) {
        backtraceData = await response.text();
        threads = parseBacktraceFile(backtraceData);
        console.log("✅ Loaded", threads.length, "threads successfully");
      }
    } catch (error) {
      console.error("Could not load sample.txt:", error);
    }
  });

  // Parse individual backtrace lines for better display
  function parseBacktraceLine(line) {
    // Extract indentation
    const indentMatch = line.match(/^(\s*[\+\!\:\|]*\s*)/);
    const indent = indentMatch ? indentMatch[1] : "";
    const content = line.substring(indent.length);

    // Try to extract sample count, function name, and address
    const parts = {
      indent,
      content,
      original: line,
    };

    // Extract sample count if at beginning
    const sampleMatch = content.match(/^(\d+)\s+(.+)/);
    if (sampleMatch) {
      parts.sampleCount = sampleMatch[1];
      parts.content = sampleMatch[2];
    }

    return parts;
  }

  // Parse indentation level from a backtrace line
  function getIndentationLevel(line) {
    const match = line.match(/^(\s*[\+\!\:\|]*\s*)/);
    return match ? match[1].length : 0;
  }

  // Find the end of a section (lines with greater indentation)
  function findSectionEnd(backtrace, startIndex) {
    const startIndentation = getIndentationLevel(backtrace[startIndex]);

    for (let i = startIndex + 1; i < backtrace.length; i++) {
      const currentIndentation = getIndentationLevel(backtrace[i]);
      // If we find a line with same or lesser indentation, that's where the section ends
      if (currentIndentation <= startIndentation) {
        return i - 1; // Return the last line of the section
      }
    }
    // If we reach the end, the section goes to the last line
    return backtrace.length - 1;
  }

  // Create collapsible backtrace structure with section awareness
  function createCollapsibleBacktrace(backtrace) {
    const result = backtrace.map((line, index) => {
      const indentationLevel = getIndentationLevel(line);
      const sectionEnd = findSectionEnd(backtrace, index);
      const hasSection = sectionEnd > index; // This line has child lines with greater indentation

      return {
        id: index,
        line,
        parsed: parseBacktraceLine(line),
        indentationLevel,
        hasSection,
        sectionEnd,
        sectionCollapsed: false,
        hidden: false, // Will be set based on parent section state
      };
    });

    // Initialize visibility based on any collapsed sections
    result.forEach((_, index) => {
      result[index].hidden = isLineHiddenByParentSection(result, index);
    });

    return result;
  }

  // Toggle section collapse for a specific line
  function toggleSection(threadId, lineIndex) {
    console.log(
      "🔄 Toggling section for thread:",
      threadId,
      "line:",
      lineIndex
    );
    threads = threads.map((thread) => {
      if (thread.id === threadId && thread.collapsibleBacktrace) {
        const updatedBacktrace = [...thread.collapsibleBacktrace];
        const line = updatedBacktrace[lineIndex];

        if (line.hasSection) {
          // Toggle the section collapsed state
          line.sectionCollapsed = !line.sectionCollapsed;
          console.log(
            "📊 Section",
            lineIndex,
            "collapsed:",
            line.sectionCollapsed,
            "affects lines",
            lineIndex + 1,
            "to",
            line.sectionEnd
          );

          // Update hidden state for all lines in this section
          updateSectionVisibility(updatedBacktrace, lineIndex);
        } else {
          console.log("⚠️ Line", lineIndex, "has no section to toggle");
        }

        return { ...thread, collapsibleBacktrace: updatedBacktrace };
      }
      return thread;
    });
  }

  // Update visibility of lines based on section collapse states
  function updateSectionVisibility(backtrace, changedLineIndex) {
    const changedLine = backtrace[changedLineIndex];

    // Update visibility for lines in the changed section
    for (let i = changedLineIndex + 1; i <= changedLine.sectionEnd; i++) {
      if (changedLine.sectionCollapsed) {
        backtrace[i].hidden = true;
      } else {
        // Only show if not hidden by a parent section
        backtrace[i].hidden = isLineHiddenByParentSection(backtrace, i);
      }
    }
  }

  // Check if a line should be hidden by any parent section
  function isLineHiddenByParentSection(backtrace, lineIndex) {
    const currentIndentation = backtrace[lineIndex].indentationLevel;

    // Look backwards for any parent sections that are collapsed
    for (let i = lineIndex - 1; i >= 0; i--) {
      const parentLine = backtrace[i];
      if (
        parentLine.indentationLevel < currentIndentation &&
        parentLine.hasSection &&
        parentLine.sectionCollapsed &&
        i + 1 <= lineIndex &&
        lineIndex <= parentLine.sectionEnd
      ) {
        return true;
      }
    }
    return false;
  }

  // Reactive updates - run filter when threads OR filterText changes
  $: filteredThreads = threads.filter(
    (thread) =>
      !filterText.trim() ||
      thread.fullName.toLowerCase().startsWith(filterText.toLowerCase())
  );
</script>

<div class="header">
  <h1>🔍 Backtrace Viewer</h1>
  <div class="controls">
    <label for="fileInput" class="file-label"> 📁 Load Backtrace File </label>
    <input
      type="file"
      id="fileInput"
      class="file-input"
      accept=".txt"
      on:change={handleFileUpload}
      bind:this={fileInput}
    />

    <input
      type="text"
      class="filter-input"
      placeholder="Filter threads by name prefix..."
      bind:value={filterText}
    />

    <div class="stats">
      {filteredThreads.length} / {threads.length} threads
    </div>
  </div>
</div>

{#if threads.length === 0}
  <div class="no-threads">
    <p>No backtrace data loaded. Upload a file to get started.</p>
  </div>
{:else}
  {#each filteredThreads as thread (thread.id)}
    <div class="thread-container">
      <div
        class="thread-header"
        on:click={() => toggleThread(thread.id)}
        on:keydown={(e) => e.key === "Enter" && toggleThread(thread.id)}
        role="button"
        tabindex="0"
      >
        <div class="thread-title">
          <strong>{thread.sampleCount}</strong>
          {thread.id}
          {#if thread.name}
            : <span style="color: #3498db;">{thread.name}</span>
          {/if}
        </div>
        <div class="toggle-indicator" class:expanded={thread.expanded}>▶</div>
      </div>

      {#if thread.expanded}
        <div class="thread-content">
          <div class="backtrace">
            {#each thread.collapsibleBacktrace as backtraceLine (backtraceLine.id)}
              {#if !backtraceLine.hidden}
                <div
                  class="backtrace-line"
                  class:has-section={backtraceLine.hasSection}
                  class:section-collapsed={backtraceLine.sectionCollapsed}
                  on:click={backtraceLine.hasSection
                    ? () => toggleSection(thread.id, backtraceLine.id)
                    : undefined}
                  on:keydown={backtraceLine.hasSection
                    ? (e) =>
                        e.key === "Enter" &&
                        toggleSection(thread.id, backtraceLine.id)
                    : undefined}
                  role={backtraceLine.hasSection ? "button" : undefined}
                  tabindex={backtraceLine.hasSection ? "0" : undefined}
                >
                  <span class="indent">{backtraceLine.parsed.indent}</span
                  >{#if backtraceLine.hasSection}
                    <span class="collapse-indicator">
                      {backtraceLine.sectionCollapsed ? "▶" : "▼"}
                    </span>
                  {/if}{#if backtraceLine.parsed.sampleCount}<span
                      class="sample-count"
                      >{backtraceLine.parsed.sampleCount}</span
                    >
                  {/if}<span class="content"
                    >{backtraceLine.parsed.content}</span
                  >
                </div>
              {/if}
            {/each}
          </div>
        </div>
      {/if}
    </div>
  {/each}
{/if}
