<script lang="ts">
  import { createEventDispatcher, onDestroy, onMount } from "svelte";
  import { PUBLIC_API_URL } from "$env/static/public";
  import { io, type Socket } from "socket.io-client";
  import {
    sales,
    type CommittedQuote,
    type QuoteDownloadLog,
  } from "$lib/optima-api/modules/sales";
  import type { PermissionMap } from "$lib/permissions";
  import PdfViewer from "../../../../../components/PdfViewer.svelte";

  export let accessToken: string | null = null;
  export let opportunityId: string = "";
  export let quotePreviewPdfUrl: string | null = null;
  export let initialQuotes: CommittedQuote[] = [];
  export let initialQuoteId: string | null = null;
  export let permissions: PermissionMap = {} as PermissionMap;
  export let isClosedOpportunity: boolean = false;
  /** True when the opportunity is in an Optima workflow state other than New or Active. */
  export let isBackGenerateState: boolean = false;

  const dispatch = createEventDispatcher<{ quotesChanged: CommittedQuote[] }>();

  // ── Permission helpers ──
  $: canFetchQuotes = permissions["sales.opportunity.quote.fetch"] !== false;
  $: canBackGenerate =
    permissions["sales.opportunity.quote.commit.backgenerate"] === true;
  $: canCommitQuote =
    !isClosedOpportunity &&
    permissions["sales.opportunity.quote.commit"] === true &&
    (!isBackGenerateState || canBackGenerate);
  $: backGenerateBlockedReason =
    !isClosedOpportunity &&
    permissions["sales.opportunity.quote.commit"] === true &&
    isBackGenerateState &&
    !canBackGenerate
      ? "Generating a quote in this workflow state requires additional permissions."
      : null;
  $: canPreviewQuote = permissions["sales.opportunity.quote.preview"] === true;
  $: canDownloadQuote =
    permissions["sales.opportunity.quote.download"] === true;
  $: canFetchDownloads =
    permissions["sales.opportunity.quote.fetch_downloads"] === true;

  // ── Quotes list state (basic data from server) ──
  let quotes: CommittedQuote[] = initialQuotes;
  let quotesLoading = false;
  let quotesError = "";

  // Determine initial selection: prefer initialQuoteId match, fall back to first quote
  const initialMatch = initialQuoteId
    ? initialQuotes.find(
        (q) => q.id === initialQuoteId || q.quoteFileName === initialQuoteId
      )
    : null;
  console.log(
    "[QuotesTab] initialQuoteId:",
    initialQuoteId,
    "quotes:",
    initialQuotes.map((q) => ({ id: q.id, fileName: q.quoteFileName })),
    "match:",
    initialMatch?.id
  );
  let selectedQuote: CommittedQuote | null =
    initialMatch ?? (initialQuotes.length > 0 ? initialQuotes[0] : null);

  // Auto-select quote by ID when navigating from activity tab (for post-mount updates)
  $: if (initialQuoteId && quotes.length > 0) {
    const match = quotes.find(
      (q) => q.id === initialQuoteId || q.quoteFileName === initialQuoteId
    );
    if (match) {
      selectedQuote = match;
      viewMode = "list";
      loadQuotePreview(match.id);
    }
  }

  // ── Detail data (lazy-loaded with regen data & params) ──
  let detailQuotes: Map<string, CommittedQuote> = new Map();
  let detailLoading = false;
  let detailLoaded = false;

  // ── Quote PDF preview (per-quote, from /quote/:id/preview) ──
  let quotePreviewLoading = false;
  let quotePreviewError = "";
  let quotePreviewObjectUrl: string | null = null;
  let quotePreviewQuoteId: string | null = null;

  // ── View mode ──
  type ViewMode = "list" | "preview" | "logs" | "narrative";
  let viewMode: ViewMode = initialQuotes.length > 0 ? "list" : "preview";

  // Show the quotes sub-tabs whenever this section is open.
  $: showQuotesSubTab = true;

  $: canEditQuoteNarrative = permissions["sales.opportunity.update"] !== false;

  let quoteNarrative = "";
  let quoteNarrativeSaved = ""; // Track the last saved value
  let quoteNarrativeLoading = false;
  let quoteNarrativeSaving = false;
  let quoteNarrativeLoaded = false;
  let quoteNarrativeError = "";
  let quoteNarrativeSuccess = "";
  let quoteNarrativeAutoSaveTimeout: ReturnType<typeof setTimeout> | null =
    null;

  // ── Live preview state ──
  let quotePreviewOptions = {
    lineItemPricing: false,
    includeQuoteNarrative: true,
    includeItemNarratives: true,
    separateRecurringServices: true,
  };

  let isCommitting = false;
  let commitError = "";
  let commitSuccess = "";

  let socket: Socket | null = null;
  let liveEventName = "";
  let previewEventName = "";
  let isConnected = false;
  let connectionError = "";

  // Tracks the token currently used for the socket (may be refreshed independently of the prop)
  let socketAccessToken: string | null = null;
  let isRefreshingToken = false;

  let livePreviewPdfUrl: string | null = null;
  let livePreviewObjectUrl: string | null = null;

  $: effectivePdfUrl = livePreviewPdfUrl ?? quotePreviewPdfUrl;

  // ── Selected quote with detail data merged ──
  $: selectedQuoteDetail = selectedQuote
    ? (detailQuotes.get(selectedQuote.id) ?? selectedQuote)
    : null;

  // ── Product splits & totals (from quote regen data) ──
  $: allProducts = selectedQuoteDetail?.quoteRegenData?.products ?? [];
  $: activeProducts = allProducts.filter((p) => p.cancellationType !== "full");
  $: cancelledProducts = allProducts.filter(
    (p) => p.cancellationType === "full"
  );

  $: totalRevenue = activeProducts.reduce(
    (sum, p) => sum + (p.revenue ?? 0),
    0
  );
  $: totalCost = activeProducts.reduce((sum, p) => sum + (p.cost ?? 0), 0);
  $: totalMargin = totalRevenue - totalCost;
  $: totalItems = activeProducts.length;
  $: totalQty = activeProducts.reduce(
    (sum, p) => sum + (p.effectiveQuantity ?? p.quantity ?? 0),
    0
  );

  let cancelledExpanded = false;

  // ── Download / Print state ──
  let quoteDownloading = false;
  let quotePrinting = false;

  // ── Logs state ──
  let downloadLogs: QuoteDownloadLog[] = [];
  let logsLoading = false;
  let logsLoaded = false;
  let logsError = "";

  // ── Helpers ──
  function formatDate(iso: string): string {
    try {
      return new Date(iso).toLocaleDateString("en-US", {
        month: "short",
        day: "numeric",
        year: "numeric",
        hour: "numeric",
        minute: "2-digit",
      });
    } catch {
      return iso;
    }
  }

  function shortFileName(name: string): string {
    return name.replace(/\.pdf$/i, "");
  }

  function formatCurrency(value: unknown): string {
    const num = Number(value);
    if (isNaN(num)) return "—";
    return num.toLocaleString("en-US", {
      style: "currency",
      currency: "USD",
      minimumFractionDigits: 2,
    });
  }

  /** Fetch basic quote list (lightweight, no regen data) */
  async function loadQuotes() {
    if (!accessToken || !opportunityId) return;
    quotesLoading = true;
    quotesError = "";
    try {
      const result = await sales.fetchQuotes(accessToken, opportunityId);
      quotes = result.data ?? [];
      dispatch("quotesChanged", quotes);
      if (quotes.length > 0 && !selectedQuote) {
        selectedQuote = quotes[0];
      }
    } catch (err: unknown) {
      quotesError =
        err instanceof Error ? err.message : "Failed to load quotes";
    } finally {
      quotesLoading = false;
    }
  }

  /** Fetch full detail (regen data + params) — called once when tab is opened */
  async function loadQuoteDetails() {
    if (!accessToken || !opportunityId || detailLoaded || detailLoading) return;
    detailLoading = true;
    try {
      const result = await sales.fetchQuotes(accessToken, opportunityId, {
        includeRegenData: true,
        includeRegenParams: true,
      });
      const map = new Map<string, CommittedQuote>();
      for (const q of result.data ?? []) {
        map.set(q.id, q);
      }
      detailQuotes = map;
      detailLoaded = true;
    } catch {
      // Silently fail — detail is optional enhancement
    } finally {
      detailLoading = false;
    }
  }

  async function loadQuoteNarrative() {
    if (
      !accessToken ||
      !opportunityId ||
      quoteNarrativeLoaded ||
      quoteNarrativeLoading
    )
      return;
    quoteNarrativeLoading = true;
    quoteNarrativeError = "";
    try {
      const result = await sales.fetchQuoteNarrative(
        accessToken,
        opportunityId
      );
      quoteNarrative = result.data?.quoteNarrative ?? "";
      quoteNarrativeSaved = quoteNarrative;
      quoteNarrativeLoaded = true;
    } catch (err: unknown) {
      quoteNarrativeLoaded = true;
      quoteNarrativeError =
        err instanceof Error ? err.message : "Failed to load quote narrative";
    } finally {
      quoteNarrativeLoading = false;
    }
  }

  function selectQuote(q: CommittedQuote) {
    selectedQuote = q;
    loadQuotePreview(q.id);
  }

  $: hasNarrativeChanges = quoteNarrative !== quoteNarrativeSaved;

  function scheduleAutoSave() {
    if (!canEditQuoteNarrative) return;
    if (quoteNarrativeAutoSaveTimeout)
      clearTimeout(quoteNarrativeAutoSaveTimeout);
    quoteNarrativeAutoSaveTimeout = setTimeout(() => {
      saveQuoteNarrative(true);
    }, 2000);
  }

  async function saveQuoteNarrative(isAutoSave = false) {
    if (
      !accessToken ||
      !opportunityId ||
      quoteNarrativeSaving ||
      !canEditQuoteNarrative
    )
      return;
    quoteNarrativeSaving = true;
    quoteNarrativeError = "";
    if (!isAutoSave) quoteNarrativeSuccess = "";
    try {
      const result = await sales.updateQuoteNarrative(
        accessToken,
        opportunityId,
        quoteNarrative.trim().length > 0 ? quoteNarrative : null
      );
      quoteNarrative = result.data?.quoteNarrative ?? quoteNarrative;
      quoteNarrativeSaved = quoteNarrative;
      quoteNarrativeLoaded = true;
      if (!isAutoSave) {
        quoteNarrativeSuccess = "Quote narrative saved to CW.";
        setTimeout(() => (quoteNarrativeSuccess = ""), 3000);
      }
      publishPreviewPayload();
    } catch (err: unknown) {
      if (!isAutoSave) {
        quoteNarrativeError =
          err instanceof Error ? err.message : "Failed to save quote narrative";
      }
    } finally {
      quoteNarrativeSaving = false;
    }
  }

  function handleNarrativeKeydown(event: KeyboardEvent) {
    // Ctrl+S or Cmd+S to save
    if ((event.ctrlKey || event.metaKey) && event.key === "s") {
      event.preventDefault();
      if (hasNarrativeChanges) {
        saveQuoteNarrative();
      }
    }
  }

  function getCharCount(text: string): { chars: number; words: number } {
    const trimmed = text.trim();
    const chars = trimmed.length;
    const words = trimmed.length === 0 ? 0 : trimmed.split(/\s+/).length;
    return { chars, words };
  }

  $: narrativeStats = getCharCount(quoteNarrative);

  function cleanupQuotePreview() {
    if (quotePreviewObjectUrl) {
      URL.revokeObjectURL(quotePreviewObjectUrl);
      quotePreviewObjectUrl = null;
    }
    quotePreviewQuoteId = null;
  }

  async function loadQuotePreview(quoteId: string) {
    if (!accessToken || !opportunityId || !canPreviewQuote) return;
    if (quotePreviewQuoteId === quoteId && quotePreviewObjectUrl) return;

    cleanupQuotePreview();
    quotePreviewLoading = true;
    quotePreviewError = "";
    try {
      const result = await sales.previewQuote(
        accessToken,
        opportunityId,
        quoteId
      );
      const base64 = result.data?.contentBase64;
      if (!base64) {
        quotePreviewError = "No preview data returned";
        return;
      }
      const cleaned = base64.replace(/^data:application\/pdf;base64,/, "");
      const binary = atob(cleaned);
      const bytes = new Uint8Array(binary.length);
      for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
      const blob = new Blob([bytes], { type: "application/pdf" });
      quotePreviewObjectUrl = URL.createObjectURL(blob);
      quotePreviewQuoteId = quoteId;
    } catch (err: unknown) {
      quotePreviewError =
        err instanceof Error ? err.message : "Failed to load preview";
    } finally {
      quotePreviewLoading = false;
    }
  }

  /** Download quote PDF as a file */
  async function handleDownloadQuote() {
    if (
      !accessToken ||
      !opportunityId ||
      !selectedQuoteDetail ||
      !canDownloadQuote
    )
      return;
    quoteDownloading = true;
    try {
      const result = await sales.downloadQuote(
        accessToken,
        opportunityId,
        selectedQuoteDetail.id,
        "download"
      );
      const { contentBase64, quoteFileName, mimeType } = result.data;
      const cleaned = contentBase64.replace(
        /^data:application\/pdf;base64,/,
        ""
      );
      const binary = atob(cleaned);
      const bytes = new Uint8Array(binary.length);
      for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
      const blob = new Blob([bytes], { type: mimeType || "application/pdf" });
      const url = URL.createObjectURL(blob);
      const a = document.createElement("a");
      a.href = url;
      a.download = quoteFileName || "quote.pdf";
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      URL.revokeObjectURL(url);
    } catch (err: unknown) {
      console.error("Download failed:", err);
    } finally {
      quoteDownloading = false;
    }
  }

  /** Print quote PDF via hidden iframe */
  async function handlePrintQuote() {
    if (
      !accessToken ||
      !opportunityId ||
      !selectedQuoteDetail ||
      !canDownloadQuote
    )
      return;
    quotePrinting = true;
    try {
      const result = await sales.downloadQuote(
        accessToken,
        opportunityId,
        selectedQuoteDetail.id,
        "print"
      );
      const { contentBase64 } = result.data;
      const cleaned = contentBase64.replace(
        /^data:application\/pdf;base64,/,
        ""
      );
      const binary = atob(cleaned);
      const bytes = new Uint8Array(binary.length);
      for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
      const blob = new Blob([bytes], { type: "application/pdf" });
      const url = URL.createObjectURL(blob);
      const printFrame = document.createElement("iframe");
      printFrame.style.position = "fixed";
      printFrame.style.left = "-9999px";
      printFrame.style.top = "-9999px";
      printFrame.style.width = "0";
      printFrame.style.height = "0";
      printFrame.src = url;
      document.body.appendChild(printFrame);
      printFrame.onload = () => {
        try {
          printFrame.contentWindow?.print();
        } catch {
          window.open(url, "_blank");
        }
        setTimeout(() => {
          document.body.removeChild(printFrame);
          URL.revokeObjectURL(url);
        }, 1000);
      };
    } catch (err: unknown) {
      console.error("Print failed:", err);
    } finally {
      quotePrinting = false;
    }
  }

  function switchToPreview() {
    viewMode = "preview";
  }

  function switchToList() {
    viewMode = "list";
  }

  function switchToNarrative() {
    viewMode = "narrative";
    loadQuoteNarrative();
  }

  function switchToLogs() {
    viewMode = "logs";
    loadDownloadLogs();
  }

  async function loadDownloadLogs() {
    if (!accessToken || !opportunityId || !canFetchDownloads) return;
    logsLoading = true;
    logsError = "";
    try {
      const result = await sales.fetchQuoteDownloads(
        accessToken,
        opportunityId
      );
      downloadLogs = result.data ?? [];
      logsLoaded = true;
    } catch (err: unknown) {
      logsError =
        err instanceof Error ? err.message : "Failed to load download logs";
    } finally {
      logsLoading = false;
    }
  }

  // ── Live preview helpers ──
  function cleanupObjectUrl() {
    if (livePreviewObjectUrl) {
      URL.revokeObjectURL(livePreviewObjectUrl);
      livePreviewObjectUrl = null;
    }
  }

  function buildPayload() {
    return {
      lineItemPricing: quotePreviewOptions.lineItemPricing,
      includeQuoteNarrative: quotePreviewOptions.includeQuoteNarrative,
      includeItemNarratives: quotePreviewOptions.includeItemNarratives,
      separateRecurringServices: quotePreviewOptions.separateRecurringServices,
    };
  }

  function decodeBase64PdfToObjectUrl(base64Value: string): string | null {
    try {
      const cleaned = base64Value.replace(/^data:application\/pdf;base64,/, "");
      const binary = atob(cleaned);
      const len = binary.length;
      const bytes = new Uint8Array(len);
      for (let i = 0; i < len; i++) bytes[i] = binary.charCodeAt(i);
      const blob = new Blob([bytes], { type: "application/pdf" });
      return URL.createObjectURL(blob);
    } catch {
      return null;
    }
  }

  function applyIncomingPayload(payload: unknown) {
    const data = payload as Record<string, unknown> | null;
    if (!data || typeof data !== "object") return;

    const options =
      (data.options as Record<string, unknown> | undefined) ??
      (data as Record<string, unknown>);
    if (options) {
      if (typeof options.lineItemPricing === "boolean") {
        quotePreviewOptions.lineItemPricing = options.lineItemPricing;
      }
      if (typeof options.includeQuoteNarrative === "boolean") {
        quotePreviewOptions.includeQuoteNarrative =
          options.includeQuoteNarrative;
      }
      if (typeof options.includeItemNarratives === "boolean") {
        quotePreviewOptions.includeItemNarratives =
          options.includeItemNarratives;
      }
      if (typeof options.separateRecurringServices === "boolean") {
        quotePreviewOptions.separateRecurringServices =
          options.separateRecurringServices;
      }
    }

    const nestedData = data.data as Record<string, unknown> | undefined;
    const pdfUrlCandidate =
      (typeof data.pdfUrl === "string" && data.pdfUrl) ||
      (typeof data.url === "string" && data.url) ||
      (typeof nestedData?.pdfUrl === "string" && nestedData.pdfUrl) ||
      (typeof nestedData?.url === "string" && nestedData.url) ||
      null;

    if (pdfUrlCandidate) {
      cleanupObjectUrl();
      livePreviewPdfUrl = pdfUrlCandidate;
      return;
    }

    const pdfBase64Candidate =
      (typeof data.contentBase64 === "string" && data.contentBase64) ||
      (typeof data.pdfBase64 === "string" && data.pdfBase64) ||
      (typeof data.quoteFile === "string" && data.quoteFile) ||
      (typeof nestedData?.contentBase64 === "string" &&
        nestedData.contentBase64) ||
      (typeof nestedData?.pdfBase64 === "string" && nestedData.pdfBase64) ||
      (typeof nestedData?.quoteFile === "string" && nestedData.quoteFile) ||
      null;

    if (pdfBase64Candidate) {
      const objectUrl = decodeBase64PdfToObjectUrl(pdfBase64Candidate);
      if (objectUrl) {
        cleanupObjectUrl();
        livePreviewObjectUrl = objectUrl;
        livePreviewPdfUrl = objectUrl;
      }
    }
  }

  function publishPreviewPayload() {
    if (!socket || !liveEventName) return;
    socket.emit(liveEventName, buildPayload());
  }

  function teardownSocket() {
    if (socket) {
      try {
        socket.disconnect();
      } catch {}
    }
    socket = null;
    liveEventName = "";
    previewEventName = "";
    isConnected = false;
  }

  function setupSocket() {
    if (!socketAccessToken || !opportunityId || !canPreviewQuote) return;

    const base = PUBLIC_API_URL || "";
    connectionError = "";

    socket = io(`${base}/secure`, {
      transports: ["websocket"],
      reconnection: false, // We manage reconnection manually to avoid retrying with a stale token
      auth: {
        authorization: `Bearer ${socketAccessToken}`,
      },
      rejectUnauthorized: false,
    });

    socket.on("connect", () => {
      isConnected = true;
      connectionError = "";

      socket?.emit(
        "opp:live_quote_preview",
        { id: opportunityId },
        (ack: { ok: boolean; event?: string; error?: string }) => {
          if (!ack?.ok || !ack?.event) {
            connectionError =
              ack?.error || "Failed to register live preview channel";
            return;
          }

          liveEventName = ack.event;
          previewEventName = `opp:live_quote_preview:${opportunityId}:preview`;

          socket?.off(liveEventName);
          socket?.on(liveEventName, (payload: unknown) => {
            applyIncomingPayload(payload);
          });

          socket?.off(previewEventName);
          socket?.on(previewEventName, (payload: unknown) => {
            applyIncomingPayload(payload);
          });

          publishPreviewPayload();
        }
      );
    });

    socket.on("connect_error", (err: unknown) => {
      const msg = err instanceof Error ? err.message : String(err);
      if (msg.toLowerCase().includes("unauthorized")) {
        attemptTokenRefreshAndReconnect();
        return;
      }
      isConnected = false;
      connectionError = msg || "Socket connection error";
    });

    // Server emits this before disconnecting an expired/invalidated session
    socket.on("secure:session:expired", () => {
      attemptTokenRefreshAndReconnect();
    });

    socket.on("disconnect", () => {
      isConnected = false;
    });

    socket.on("error", (err: unknown) => {
      connectionError =
        err instanceof Error
          ? err.message
          : typeof err === "string"
            ? err
            : "Live quote preview socket error";
    });

    socket.on(
      "opp:live_quote_preview:error",
      (payload: { message?: string }) => {
        connectionError = payload?.message || "Live quote preview error";
      }
    );
  }

  async function attemptTokenRefreshAndReconnect() {
    if (isRefreshingToken) return;
    isRefreshingToken = true;
    connectionError = "Session expired — refreshing…";
    try {
      const res = await fetch("/api/auth/refresh");
      if (res.ok) {
        const body = (await res.json()) as { accessToken?: string };
        if (body.accessToken) {
          socketAccessToken = body.accessToken;
          teardownSocket();
          setupSocket();
          return;
        }
      }
      // Refresh failed — kick to login
      window.location.assign("/logout");
    } catch {
      window.location.assign("/logout");
    } finally {
      isRefreshingToken = false;
    }
  }

  onMount(() => {
    // Lazy-load detailed quote data (regen data & params) now that the tab is active
    loadQuoteDetails();
    loadQuoteNarrative();

    // Set up live preview socket (only if user has preview permission)
    if (!accessToken || !opportunityId || !canPreviewQuote) return;

    socketAccessToken = accessToken;
    setupSocket();

    // Load preview for initially selected quote
    if (selectedQuote) {
      loadQuotePreview(selectedQuote.id);
    }

    return () => {
      teardownSocket();
      cleanupObjectUrl();
      cleanupQuotePreview();
    };
  });

  async function handleCommitQuote() {
    if (!accessToken || !opportunityId || isCommitting) return;
    isCommitting = true;
    commitError = "";
    commitSuccess = "";
    try {
      const result = await sales.commitQuote(accessToken, opportunityId, {
        lineItemPricing: quotePreviewOptions.lineItemPricing,
        includeQuoteNarrative: quotePreviewOptions.includeQuoteNarrative,
        includeItemNarratives: quotePreviewOptions.includeItemNarratives,
        separateRecurringServices: quotePreviewOptions.separateRecurringServices,
      });
      commitSuccess = result.message || "Quote created successfully!";
      // Reload quotes (basic + detail) and switch to list view
      detailLoaded = false;
      await loadQuotes();
      await loadQuoteDetails();
      if (result.data) {
        selectedQuote =
          quotes.find((q) => q.id === result.data.id) ?? quotes[0] ?? null;
      }
      viewMode = "list";
      if (selectedQuote) {
        loadQuotePreview(selectedQuote.id);
      }
      setTimeout(() => (commitSuccess = ""), 5000);
    } catch (err: unknown) {
      commitError =
        err instanceof Error ? err.message : "Failed to create quote";
      setTimeout(() => (commitError = ""), 5000);
    } finally {
      isCommitting = false;
    }
  }

  onDestroy(() => {
    teardownSocket();
    cleanupObjectUrl();
    cleanupQuotePreview();
  });

  $: if (isConnected && liveEventName) {
    publishPreviewPayload();
  }

  $: if (viewMode === "narrative") {
    loadQuoteNarrative();
  }
</script>

<div class="quotes-tab">
  <!-- ── View toggle bar ── -->
  <div class="quotes-view-bar">
    <div class="quotes-view-tabs">
      {#if showQuotesSubTab}
        <button
          class="quotes-view-tab"
          class:active={viewMode === "list"}
          type="button"
          on:click={switchToList}
        >
          <svg
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            stroke-width="2"
            width="15"
            height="15"
          >
            <path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" />
            <polyline points="14 2 14 8 20 8" />
          </svg>
          Quotes
          <span class="quotes-view-badge">{quotes.length}</span>
        </button>
      {/if}
      <button
        class="quotes-view-tab"
        class:active={viewMode === "preview"}
        type="button"
        on:click={switchToPreview}
        disabled={!canPreviewQuote}
        title={canPreviewQuote
          ? ""
          : "You do not have permission to preview quotes"}
      >
        <svg
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
          width="15"
          height="15"
        >
          <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
          <circle cx="12" cy="12" r="3" />
        </svg>
        Live Preview
      </button>
      <button
        class="quotes-view-tab"
        class:active={viewMode === "narrative"}
        type="button"
        on:click={switchToNarrative}
        disabled={!canEditQuoteNarrative}
        title={canEditQuoteNarrative
          ? "Edit the quote narrative stored in CW"
          : "You do not have permission to edit quote narrative"}
      >
        <svg
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
          width="15"
          height="15"
        >
          <path d="M4 5h16M4 12h16M4 19h10" />
        </svg>
        Narrative
      </button>
      {#if canFetchDownloads}
        <button
          class="quotes-view-tab"
          class:active={viewMode === "logs"}
          type="button"
          on:click={switchToLogs}
        >
          <svg
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            stroke-width="2"
            width="15"
            height="15"
          >
            <path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" />
            <polyline points="14 2 14 8 20 8" />
            <line x1="16" y1="13" x2="8" y2="13" />
            <line x1="16" y1="17" x2="8" y2="17" />
            <polyline points="10 9 9 9 8 9" />
          </svg>
          Logs
        </button>
      {/if}
    </div>
  </div>

  <!-- ═══ LIST VIEW ═══ -->
  {#if viewMode === "list"}
    <div class="quotes-list-layout">
      <!-- Sidebar: quote list -->
      <div class="quotes-list-sidebar">
        <div class="quotes-list-header">
          <span class="quotes-field-label">Committed Quotes</span>
          {#if canCommitQuote}
            <button
              class="quotes-new-btn"
              type="button"
              on:click={switchToPreview}
              title="Create new quote"
            >
              <svg
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                stroke-width="2"
                width="14"
                height="14"
              >
                <line x1="12" y1="5" x2="12" y2="19" />
                <line x1="5" y1="12" x2="19" y2="12" />
              </svg>
            </button>
          {:else if backGenerateBlockedReason}
            <button
              class="quotes-new-btn"
              type="button"
              disabled
              title={backGenerateBlockedReason}
            >
              <svg
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                stroke-width="2"
                width="14"
                height="14"
              >
                <line x1="12" y1="5" x2="12" y2="19" />
                <line x1="5" y1="12" x2="19" y2="12" />
              </svg>
            </button>
          {/if}
        </div>

        {#if quotesLoading}
          <div class="quotes-list-empty">Loading quotes...</div>
        {:else if quotesError}
          <div class="quotes-list-empty quotes-list-error">{quotesError}</div>
        {:else if quotes.length === 0}
          <div class="quotes-list-empty">
            No quotes yet.
            {#if canCommitQuote}
              <button
                class="quotes-list-empty-link"
                type="button"
                on:click={switchToPreview}
              >
                Create one
              </button>
            {/if}
          </div>
        {:else}
          <div class="quotes-list-items">
            {#each quotes as quote (quote.id)}
              <button
                class="quotes-list-item"
                class:active={selectedQuote?.id === quote.id}
                type="button"
                on:click={() => selectQuote(quote)}
              >
                <div class="quotes-list-item-name">
                  {shortFileName(quote.quoteFileName)}
                </div>
                <div class="quotes-list-item-date">
                  {formatDate(quote.createdAt)}
                </div>
              </button>
            {/each}
          </div>
        {/if}
      </div>

      <!-- Main area: quote detail + PDF preview -->
      <div class="quotes-detail-area">
        {#if selectedQuoteDetail}
          <div class="quotes-detail-content">
            <!-- Left: info + line items -->
            <div class="quotes-detail-info">
              <!-- Compact header -->
              <div class="quotes-detail-header">
                <div class="quotes-detail-header-top">
                  <h3 class="quotes-detail-title">
                    {shortFileName(selectedQuoteDetail.quoteFileName)}
                  </h3>
                  <div class="quotes-detail-header-right">
                    <span class="quotes-detail-date">
                      {formatDate(selectedQuoteDetail.createdAt)}
                    </span>
                    <div class="quotes-detail-actions">
                      <button
                        class="quotes-action-btn"
                        title="Print quote"
                        disabled={quotePrinting || !canDownloadQuote}
                        on:click={handlePrintQuote}
                      >
                        <svg
                          viewBox="0 0 24 24"
                          fill="none"
                          stroke="currentColor"
                          stroke-width="2"
                          width="14"
                          height="14"
                        >
                          <polyline points="6 9 6 2 18 2 18 9" />
                          <path
                            d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"
                          />
                          <rect x="6" y="14" width="12" height="8" />
                        </svg>
                      </button>
                      <button
                        class="quotes-action-btn"
                        title="Download quote"
                        disabled={quoteDownloading || !canDownloadQuote}
                        on:click={handleDownloadQuote}
                      >
                        <svg
                          viewBox="0 0 24 24"
                          fill="none"
                          stroke="currentColor"
                          stroke-width="2"
                          width="14"
                          height="14"
                        >
                          <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
                          <polyline points="7 10 12 15 17 10" />
                          <line x1="12" y1="15" x2="12" y2="3" />
                        </svg>
                      </button>
                    </div>
                  </div>
                </div>

                {#if detailLoading}
                  <div class="quotes-detail-meta">
                    <span class="quotes-detail-meta-loading"
                      >Loading details...</span
                    >
                  </div>
                {:else if selectedQuoteDetail.quoteRegenData?.options}
                  <div class="quotes-detail-meta">
                    <span
                      class="quotes-detail-badge"
                      class:active={selectedQuoteDetail.quoteRegenData.options
                        .lineItemPricing}
                    >
                      Line pricing
                    </span>
                    <span
                      class="quotes-detail-badge"
                      class:active={selectedQuoteDetail.quoteRegenData.options
                        .includeQuoteNarrative}
                    >
                      Quote narrative
                    </span>
                    <span
                      class="quotes-detail-badge"
                      class:active={selectedQuoteDetail.quoteRegenData.options
                        .includeItemNarratives}
                    >
                      Item narratives
                    </span>
                    <span
                      class="quotes-detail-badge"
                      class:active={selectedQuoteDetail.quoteRegenData.options
                        .separateRecurringServices}
                    >
                      Separate recurring
                    </span>
                  </div>
                {/if}
              </div>

              <!-- Summary numbers -->
              {#if !detailLoading && allProducts.length > 0}
                <div class="quotes-summary-bar">
                  <div class="quotes-summary-stat">
                    <span class="quotes-summary-label">Items</span>
                    <span class="quotes-summary-value">{totalItems}</span>
                  </div>
                  <div class="quotes-summary-stat">
                    <span class="quotes-summary-label">Revenue</span>
                    <span class="quotes-summary-value"
                      >{formatCurrency(totalRevenue)}</span
                    >
                  </div>
                  <div class="quotes-summary-stat">
                    <span class="quotes-summary-label">Cost</span>
                    <span class="quotes-summary-value"
                      >{formatCurrency(totalCost)}</span
                    >
                  </div>
                  <div class="quotes-summary-stat">
                    <span class="quotes-summary-label">Margin</span>
                    <span
                      class="quotes-summary-value"
                      class:positive={totalMargin >= 0}
                      class:negative={totalMargin < 0}
                    >
                      {formatCurrency(totalMargin)}
                    </span>
                  </div>
                </div>
              {/if}

              <!-- Line items table -->
              <div class="quotes-line-items">
                <div class="quotes-line-items-header">
                  <span class="quotes-li-col quotes-li-num">#</span>
                  <span class="quotes-li-col quotes-li-desc">Description</span>
                  <span class="quotes-li-col quotes-li-qty">Qty</span>
                  <span class="quotes-li-col quotes-li-price">Revenue</span>
                </div>
                <div class="quotes-line-items-body">
                  {#if detailLoading}
                    <div class="quotes-line-items-empty">
                      Loading line items...
                    </div>
                  {:else if activeProducts.length > 0}
                    {#each activeProducts as product, i}
                      <div
                        class="quotes-line-item"
                        class:partial-cancelled={product.cancellationType ===
                          "partial"}
                      >
                        <span class="quotes-li-col quotes-li-num">{i + 1}</span>
                        <span
                          class="quotes-li-col quotes-li-desc"
                          title={product.productDescription ?? ""}
                        >
                          {product.productDescription ?? "—"}
                          {#if product.productClass}
                            <span class="quotes-li-class"
                              >{product.productClass}</span
                            >
                          {/if}
                          {#if product.cancellationType === "partial"}
                            <span class="quotes-li-partial-tag">
                              {product.quantityCancelled ?? 0} cancelled
                            </span>
                          {/if}
                        </span>
                        <span class="quotes-li-col quotes-li-qty"
                          >{product.effectiveQuantity ??
                            product.quantity ??
                            0}</span
                        >
                        <span class="quotes-li-col quotes-li-price"
                          >{formatCurrency(product.revenue)}</span
                        >
                      </div>
                    {/each}
                  {:else if allProducts.length === 0}
                    <div class="quotes-line-items-empty">No line items</div>
                  {:else}
                    <div class="quotes-line-items-empty">
                      All items cancelled
                    </div>
                  {/if}

                  <!-- Cancelled items accordion -->
                  {#if cancelledProducts.length > 0}
                    <!-- svelte-ignore a11y-no-static-element-interactions -->
                    <div
                      class="quotes-cancelled-accordion"
                      on:click={() => (cancelledExpanded = !cancelledExpanded)}
                      on:keydown={(e) => {
                        if (e.key === "Enter" || e.key === " ")
                          cancelledExpanded = !cancelledExpanded;
                      }}
                      role="button"
                      tabindex="0"
                    >
                      <svg
                        class="quotes-accordion-chevron"
                        class:expanded={cancelledExpanded}
                        viewBox="0 0 24 24"
                        fill="none"
                        stroke="currentColor"
                        stroke-width="2"
                        width="12"
                        height="12"
                      >
                        <polyline points="9 18 15 12 9 6" />
                      </svg>
                      <span>Cancelled ({cancelledProducts.length})</span>
                    </div>
                    {#if cancelledExpanded}
                      {#each cancelledProducts as product, i}
                        <div class="quotes-line-item cancelled">
                          <span class="quotes-li-col quotes-li-num"
                            >{activeProducts.length + i + 1}</span
                          >
                          <span
                            class="quotes-li-col quotes-li-desc"
                            title={product.productDescription ?? ""}
                          >
                            {product.productDescription ?? "—"}
                            {#if product.productClass}
                              <span class="quotes-li-class"
                                >{product.productClass}</span
                              >
                            {/if}
                          </span>
                          <span class="quotes-li-col quotes-li-qty"
                            >{product.quantity ?? 0}</span
                          >
                          <span class="quotes-li-col quotes-li-price"
                            >{formatCurrency(product.revenue)}</span
                          >
                        </div>
                      {/each}
                    {/if}
                  {/if}
                </div>
              </div>

              <!-- Subtle quote ID at bottom right -->
              <div class="quotes-detail-footer">
                <span class="quotes-detail-quote-id"
                  >{selectedQuoteDetail.id}</span
                >
              </div>
            </div>

            <!-- Right: PDF preview -->
            <div class="quotes-detail-preview">
              {#if quotePreviewLoading}
                <div class="quotes-detail-preview-frame quotes-preview-loading">
                  <div class="quotes-pdf-page quotes-pdf-page-mini">
                    <p style="color: var(--text-muted); font-style: italic;">
                      Loading preview...
                    </p>
                  </div>
                </div>
              {:else if quotePreviewError}
                <div
                  class="quotes-detail-preview-frame quotes-preview-error-frame"
                >
                  <div class="quotes-pdf-page quotes-pdf-page-mini">
                    <p style="color: var(--text-muted);">{quotePreviewError}</p>
                  </div>
                </div>
              {:else if quotePreviewObjectUrl}
                <div class="quotes-detail-preview-frame">
                  <PdfViewer src={quotePreviewObjectUrl} />
                </div>
              {:else}
                <div class="quotes-detail-preview-frame">
                  <div class="quotes-pdf-page quotes-pdf-page-mini">
                    <p class="quotes-pdf-page-subtitle">No preview available</p>
                  </div>
                </div>
              {/if}
            </div>
          </div>
        {:else}
          <div class="quotes-detail-empty">
            <p>Select a quote from the list to view details.</p>
          </div>
        {/if}
      </div>
    </div>

    <!-- ═══ NARRATIVE VIEW ═══ -->
  {:else if viewMode === "narrative"}
    <div class="quotes-preview-layout">
      <div class="quotes-preview-sidebar">
        <div class="quotes-sidebar-header">
          <svg
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            stroke-width="2"
            width="18"
            height="18"
          >
            <path d="M4 5h16M4 12h16M4 19h10" />
          </svg>
          <span>Quote Narrative</span>
        </div>

        <div class="quotes-sidebar-divider"></div>

        <div class="quotes-preview-options">
          <div class="quotes-narrative-header">
            <span class="quotes-field-label">Edit narrative</span>
            <div class="quotes-narrative-unsaved-indicator">
              {#if hasNarrativeChanges}
                <span class="unsaved-badge">Unsaved changes</span>
              {/if}
            </div>
          </div>

          {#if quoteNarrativeLoading}
            <div class="quotes-list-empty">Loading narrative...</div>
          {:else}
            <div class="quotes-narrative-editor-container">
              <textarea
                class="quotes-narrative-textarea"
                bind:value={quoteNarrative}
                on:input={scheduleAutoSave}
                on:keydown={handleNarrativeKeydown}
                placeholder="Enter the quote narrative that will appear in generated quotes. This text is stored in CW opportunity custom field 35. You can use markdown-style formatting..."
                rows="14"
                disabled={!canEditQuoteNarrative}
              ></textarea>
              <div class="quotes-narrative-stats">
                <span class="quotes-stat">{narrativeStats.words} words</span>
                <span class="quotes-stat"
                  >{narrativeStats.chars} characters</span
                >
              </div>
            </div>
          {/if}
        </div>

        <div class="quotes-narrative-actions">
          <button
            class="quotes-create-btn"
            type="button"
            disabled={!hasNarrativeChanges ||
              !accessToken ||
              !opportunityId ||
              quoteNarrativeSaving ||
              !canEditQuoteNarrative}
            on:click={() => saveQuoteNarrative()}
          >
            {#if quoteNarrativeSaving}
              <span class="saving-indicator">⌛ Saving...</span>
            {:else if hasNarrativeChanges}
              <span class="save-indicator">↓ Save Narrative</span>
            {:else}
              <span class="saved-indicator">✓ Saved</span>
            {/if}
          </button>

          {#if quoteNarrativeError}
            <div class="quotes-commit-msg quotes-commit-error">
              {quoteNarrativeError}
            </div>
          {/if}
          {#if quoteNarrativeSuccess}
            <div class="quotes-commit-msg quotes-commit-success">
              {quoteNarrativeSuccess}
            </div>
          {/if}
        </div>
      </div>

      <div class="quotes-detail-preview">
        <div class="quotes-detail-preview-frame">
          <div class="quotes-pdf-page quotes-pdf-page-mini">
            <div class="quotes-narrative-preview-header">
              <h4>Preview as it will appear in quotes</h4>
            </div>
            <div class="quotes-narrative-preview">
              {#if quoteNarrative.trim()}
                <div class="preview-content">
                  {quoteNarrative}
                </div>
              {:else}
                <div class="preview-empty">No narrative entered yet</div>
              {/if}
            </div>
          </div>
        </div>
      </div>
    </div>
  {:else if viewMode === "preview"}
    <div class="quotes-preview-layout">
      <div class="quotes-preview-sidebar">
        <div class="quotes-sidebar-header">
          <svg
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            stroke-width="2"
            width="18"
            height="18"
          >
            <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
            <circle cx="12" cy="12" r="3" />
          </svg>
          <span>Live Preview</span>
        </div>

        <div class="quotes-sidebar-divider"></div>

        <div
          class="quotes-preview-options"
          aria-label="Quote generation options"
        >
          <span class="quotes-field-label">Generation options</span>
          <label class="quotes-flag-row">
            <input
              type="checkbox"
              checked={quotePreviewOptions.lineItemPricing}
              on:change={() => {
                quotePreviewOptions.lineItemPricing =
                  !quotePreviewOptions.lineItemPricing;
                publishPreviewPayload();
              }}
            />
            <span>Show line item pricing</span>
          </label>
          <label class="quotes-flag-row">
            <input
              type="checkbox"
              checked={quotePreviewOptions.includeQuoteNarrative}
              on:change={() => {
                quotePreviewOptions.includeQuoteNarrative =
                  !quotePreviewOptions.includeQuoteNarrative;
                publishPreviewPayload();
              }}
            />
            <span>Include quote narrative</span>
          </label>
          <label class="quotes-flag-row">
            <input
              type="checkbox"
              checked={quotePreviewOptions.includeItemNarratives}
              on:change={() => {
                quotePreviewOptions.includeItemNarratives =
                  !quotePreviewOptions.includeItemNarratives;
                publishPreviewPayload();
              }}
            />
            <span>Include item narratives</span>
          </label>
          <label class="quotes-flag-row">
            <input
              type="checkbox"
              checked={quotePreviewOptions.separateRecurringServices}
              on:change={() => {
                quotePreviewOptions.separateRecurringServices =
                  !quotePreviewOptions.separateRecurringServices;
                publishPreviewPayload();
              }}
            />
            <span>Separate recurring services</span>
          </label>
        </div>

        <button
          class="quotes-create-btn"
          type="button"
          disabled={isCommitting ||
            !accessToken ||
            !opportunityId ||
            !canCommitQuote}
          title={backGenerateBlockedReason ?? undefined}
          on:click={handleCommitQuote}
        >
          {#if isCommitting}
            Creating...
          {:else}
            <svg
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              width="14"
              height="14"
            >
              <line x1="12" y1="5" x2="12" y2="19" />
              <line x1="5" y1="12" x2="19" y2="12" />
            </svg>
            Create Quote
          {/if}
        </button>

        {#if commitError}
          <div class="quotes-commit-msg quotes-commit-error">{commitError}</div>
        {/if}
        {#if commitSuccess}
          <div class="quotes-commit-msg quotes-commit-success">
            {commitSuccess}
          </div>
        {/if}

        {#if quotes.length > 0}
          <div class="quotes-sidebar-divider"></div>
          <button
            class="quotes-back-to-list"
            type="button"
            on:click={switchToList}
          >
            <svg
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              width="14"
              height="14"
            >
              <path d="M19 12H5M12 19l-7-7 7-7" />
            </svg>
            Back to quotes ({quotes.length})
          </button>
        {/if}
      </div>

      <div class="quotes-preview-canvas" aria-label="Quote PDF preview area">
        {#if effectivePdfUrl}
          <PdfViewer src={effectivePdfUrl} />
        {:else}
          <div class="quotes-pdf-frame quotes-pdf-placeholder">
            <div class="quotes-pdf-page">
              <h4>Quote PDF Preview</h4>
              <p>
                {#if connectionError}
                  Live preview error: {connectionError}
                {:else if !accessToken || !opportunityId}
                  Missing authentication or opportunity id for live preview.
                {:else if !isConnected}
                  Connecting to live preview channel...
                {:else}
                  Connected. Waiting for live quote preview PDF payload.
                {/if}
              </p>
            </div>
          </div>
        {/if}
      </div>
    </div>
  {:else if viewMode === "logs"}
    <!-- ═══ LOGS VIEW ═══ -->
    <div class="quotes-logs-view">
      <div class="quotes-logs-header">
        <span class="quotes-field-label">Download & Print History</span>
        <button
          class="quotes-logs-refresh"
          type="button"
          title="Refresh logs"
          disabled={logsLoading}
          on:click={loadDownloadLogs}
        >
          <svg
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            stroke-width="2"
            width="13"
            height="13"
          >
            <polyline points="23 4 23 10 17 10" />
            <polyline points="1 20 1 14 7 14" />
            <path
              d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15"
            />
          </svg>
        </button>
      </div>

      {#if logsLoading}
        <div class="quotes-logs-empty">
          <p>Loading logs...</p>
        </div>
      {:else if logsError}
        <div class="quotes-logs-empty">
          <p style="color: var(--error, #ef4444);">{logsError}</p>
        </div>
      {:else if downloadLogs.length === 0}
        <div class="quotes-logs-empty">
          <p>No download or print activity yet.</p>
        </div>
      {:else}
        <div class="quotes-logs-list">
          {#each downloadLogs as log}
            <div class="quotes-log-group">
              <div class="quotes-log-group-header">
                <span class="quotes-log-filename"
                  >{log.quoteFileName.replace(/\.pdf$/i, "")}</span
                >
                {#if log.downloads.length > 0}
                  <span class="quotes-log-count"
                    >{log.downloads.length}
                    {log.downloads.length === 1 ? "entry" : "entries"}</span
                  >
                {:else}
                  <span class="quotes-log-count"
                    >Created {formatDate(log.createdAt)}</span
                  >
                {/if}
              </div>
              {#if log.downloads.length > 0}
                <div class="quotes-log-entries">
                  {#each log.downloads as dl}
                    <div class="quotes-log-entry">
                      <span
                        class="quotes-log-action"
                        class:download={dl.fetchAction === "download"}
                        class:print={dl.fetchAction === "print"}
                      >
                        {#if dl.fetchAction === "download"}
                          <svg
                            viewBox="0 0 24 24"
                            fill="none"
                            stroke="currentColor"
                            stroke-width="2"
                            width="11"
                            height="11"
                          >
                            <path
                              d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"
                            />
                            <polyline points="7 10 12 15 17 10" />
                            <line x1="12" y1="15" x2="12" y2="3" />
                          </svg>
                        {:else}
                          <svg
                            viewBox="0 0 24 24"
                            fill="none"
                            stroke="currentColor"
                            stroke-width="2"
                            width="11"
                            height="11"
                          >
                            <polyline points="6 9 6 2 18 2 18 9" />
                            <path
                              d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"
                            />
                            <rect x="6" y="14" width="12" height="8" />
                          </svg>
                        {/if}
                        {dl.fetchAction}
                      </span>
                      <span class="quotes-log-user">{dl.userName}</span>
                      <span class="quotes-log-date"
                        >{formatDate(dl.downloadedAt)}</span
                      >
                    </div>
                  {/each}
                </div>
              {:else}
                <div class="quotes-log-entries">
                  <div class="quotes-log-entry">
                    <span
                      class="quotes-log-action"
                      style="opacity: 0.5; width: auto; white-space: nowrap;"
                      >No downloads yet</span
                    >
                  </div>
                </div>
              {/if}
            </div>
          {/each}
        </div>
      {/if}
    </div>
  {/if}
</div>

<style>
  .quotes-narrative-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 0.5rem;
  }

  .quotes-narrative-unsaved-indicator {
    display: flex;
    align-items: center;
  }

  .unsaved-badge {
    font-size: 0.75rem;
    background-color: var(--status-pending-bg);
    color: var(--status-pending-color);
    padding: 0.25rem 0.5rem;
    border-radius: 0.25rem;
    font-weight: 500;
    animation: pulse 2s infinite;
  }

  @keyframes pulse {
    0%,
    100% {
      opacity: 1;
    }
    50% {
      opacity: 0.6;
    }
  }

  .quotes-narrative-editor-container {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin-bottom: 0.75rem;
  }

  .quotes-narrative-textarea {
    padding: 0.75rem;
    border: 1px solid var(--input-border);
    border-radius: 0.375rem;
    font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace;
    font-size: 0.875rem;
    line-height: 1.5;
    resize: vertical;
    transition: all 0.2s;
    background-color: var(--input-bg);
    color: var(--input-text);
  }

  .quotes-narrative-textarea:focus {
    outline: none;
    border-color: var(--input-focus-border);
    background-color: var(--input-focus-bg);
    box-shadow: 0 0 0 3px var(--input-focus-ring);
  }

  .quotes-narrative-textarea:disabled {
    background-color: var(--bg-inset);
    color: var(--text-muted);
    cursor: not-allowed;
  }

  .quotes-narrative-stats {
    display: flex;
    gap: 1rem;
    font-size: 0.75rem;
    color: var(--text-muted);
    padding: 0 0.25rem;
  }

  .quotes-stat {
    display: flex;
    align-items: center;
  }

  .quotes-narrative-actions {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
  }

  .saving-indicator,
  .save-indicator,
  .saved-indicator {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
  }

  .quotes-narrative-preview-header {
    margin-bottom: 1rem;
  }

  .quotes-narrative-preview-header h4 {
    margin: 0;
    font-size: 0.875rem;
    color: var(--text-muted);
    font-weight: 500;
  }

  .quotes-narrative-preview {
    font-size: 0.875rem;
    line-height: 1.6;
    color: var(--text-primary);
    white-space: pre-wrap;
    word-break: break-word;
    padding: 1rem;
    background-color: var(--input-bg);
    border-radius: 0.375rem;
    min-height: 200px;
    max-height: 500px;
    overflow-y: auto;
  }

  .preview-content {
    font-family: inherit;
  }

  .preview-empty {
    color: var(--text-muted);
    font-style: italic;
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
  }
</style>
