"use strict";
var import_LogViewer = require("../../LogViewer.css");
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
const jsxRuntime = require("react/jsx-runtime");
const React = require("react");
const reactVirtuoso = require("react-virtuoso");
const screenfull = require("screenfull");
const sprintfJs = require("sprintf-js");
const throttleDebounce = require("throttle-debounce");
const kit_Button = require("../Button.cjs");
const kit_ClipboardButton = require("../ClipboardButton.cjs");
const kit_Column = require("../Column.cjs");
const kit_Icon = require("../Icon.cjs");
const kit_internal_functions = require("../internal/functions.cjs");
const kit_internal_services = require("../internal/services.cjs");
const kit_internal_types = require("../internal/types.cjs");
const kit_Message = require("../Message.cjs");
const kit_Row = require("../Row.cjs");
const kit_Section = require("../Section.cjs");
const kit_Spinner = require("../Spinner.cjs");
const _ = require("lodash");
const kit_LogViewer_LogViewerEntry = require("./LogViewerEntry.cjs");
const pluralizer = (count, inputString, plural) => {
  if (count === 1 || _.isArray(count) && count.length === 1)
    return inputString;
  if (plural)
    return plural;
  return inputString + "s";
};
const options = "LogViewer_options_fe5a1";
const base = "LogViewer_base_fe5a1";
const logs = "LogViewer_logs_fe5a1";
const buttons = "LogViewer_buttons_fe5a1";
const css = {
  options,
  base,
  logs,
  buttons
};
const FetchType = {
  Initial: "Initial",
  Newer: "Newer",
  Older: "Older",
  Stream: "Stream"
};
const FetchDirection = {
  Newer: "Newer",
  Older: "Older"
};
const ARIA_LABEL_ENABLE_TAILING = "Enable Tailing";
const ARIA_LABEL_SCROLL_TO_OLDEST = "Scroll to Oldest";
const PAGE_LIMIT = 100;
const THROTTLE_TIME = 50;
const formatLogEntry = (log) => {
  const formattedTime = log.time ? kit_internal_functions.formatDatetime(log.time, { format: kit_LogViewer_LogViewerEntry.DATETIME_FORMAT }) : "";
  return { ...log, formattedTime };
};
const formatClipboardHeader = (log) => {
  const logEntry = formatLogEntry(log);
  const format = `%${kit_LogViewer_LogViewerEntry.MAX_DATETIME_LENGTH - 1}s `;
  const level = `<${logEntry.level || ""}>`;
  return sprintfJs.sprintf(`%-9s ${format}`, level, logEntry.formattedTime);
};
const logSorter = (key) => (a, b) => {
  const aValue = a[key];
  const bValue = b[key];
  if (key === "id")
    return kit_internal_functions.numericSorter(aValue, bValue);
  if (key === "time")
    return kit_internal_functions.dateTimeStringSorter(aValue, bValue);
  return 0;
};
const START_INDEX = 2e9;
function LogViewer({
  decoder,
  height = "stretch",
  initialLogs,
  onDownload,
  onFetch,
  onError,
  serverAddress,
  sortKey = "time",
  handleCloseLogs,
  title
}) {
  const componentId = React.useId();
  const logsRef = React.useRef(null);
  const virtuosoRef = React.useRef(null);
  const [isFetching, setIsFetching] = React.useState(false);
  const local = React.useRef({
    idSet: /* @__PURE__ */ new Set([]),
    isScrollReady: false
  });
  const [canceler] = React.useState(new AbortController());
  const [fetchDirection, setFetchDirection] = React.useState(FetchDirection.Older);
  const [isTailing, setIsTailing] = React.useState(true);
  const [showButtons, setShowButtons] = React.useState(false);
  const [logs2, setLogs] = React.useState([]);
  const [firstItemIndex, setFirstItemIndex] = React.useState(START_INDEX);
  const processLogs = React.useCallback(
    (newLogs) => {
      return newLogs.filter((log) => {
        const isDuplicate = local.current.idSet.has(log.id);
        const isTqdm = log.message.includes("\r");
        local.current.idSet.add(log.id);
        return !isDuplicate && !isTqdm;
      }).map((log) => formatLogEntry(log)).sort(logSorter(sortKey));
    },
    [sortKey]
  );
  const addLogs = React.useCallback((newLogs = [], prepend = false) => {
    if (newLogs.length === 0)
      return;
    setLogs((prevLogs) => prepend ? newLogs.concat(prevLogs) : prevLogs.concat(newLogs));
    if (prepend)
      setFirstItemIndex((prev) => prev - newLogs.length);
  }, []);
  const fetchLogs = React.useCallback(
    async (config, type) => {
      if (!onFetch)
        return [];
      const buffer = [];
      setIsFetching(true);
      await kit_internal_services.readLogStream(
        serverAddress,
        onFetch({ limit: PAGE_LIMIT, ...config }, type),
        onError,
        (event) => {
          const logEntry = decoder(event);
          fetchDirection === FetchDirection.Older ? buffer.unshift(logEntry) : buffer.push(logEntry);
        }
      );
      setIsFetching(false);
      return processLogs(buffer);
    },
    [decoder, fetchDirection, onFetch, onError, processLogs, serverAddress]
  );
  const handleFetchMoreLogs = React.useCallback(
    async (positionReached) => {
      if (!local.current.isScrollReady)
        return;
      if (isFetching)
        return;
      const shouldFetchNewLogs = positionReached === "end" && fetchDirection === FetchDirection.Newer;
      const shouldFetchOldLogs = positionReached === "start" && fetchDirection === FetchDirection.Older;
      if (shouldFetchNewLogs || shouldFetchOldLogs) {
        const newLogs = await fetchLogs(
          {
            canceler,
            fetchDirection,
            offsetLog: shouldFetchNewLogs ? logs2[logs2.length - 1] : logs2[0]
          },
          shouldFetchNewLogs ? FetchType.Newer : FetchType.Older
        );
        const prevLogs = kit_internal_functions.clone(logs2);
        addLogs(newLogs, shouldFetchOldLogs);
        if (newLogs.length > 0 && shouldFetchNewLogs) {
          setTimeout(() => {
            var _a;
            (_a = virtuosoRef.current) == null ? void 0 : _a.scrollToIndex({
              align: "end",
              index: prevLogs.length - 1
            });
          }, 100);
        }
        return newLogs;
      }
    },
    [addLogs, canceler, fetchDirection, fetchLogs, isFetching, logs2]
  );
  const handleScrollToOldest = React.useCallback(() => {
    var _a;
    setIsTailing(false);
    if (fetchDirection === FetchDirection.Newer) {
      (_a = virtuosoRef.current) == null ? void 0 : _a.scrollToIndex({ index: firstItemIndex });
    } else {
      local.current = {
        idSet: /* @__PURE__ */ new Set([]),
        isScrollReady: false
      };
      setLogs([]);
      setFetchDirection(FetchDirection.Newer);
      setFirstItemIndex(0);
    }
  }, [fetchDirection, firstItemIndex]);
  const handleEnableTailing = React.useCallback(() => {
    var _a;
    setIsTailing(true);
    if (fetchDirection === FetchDirection.Older) {
      (_a = virtuosoRef.current) == null ? void 0 : _a.scrollToIndex({ index: "LAST" });
    } else {
      local.current = {
        idSet: /* @__PURE__ */ new Set([]),
        isScrollReady: false
      };
      setLogs([]);
      setFetchDirection(FetchDirection.Older);
      setFirstItemIndex(START_INDEX);
    }
  }, [fetchDirection]);
  const clipboardCopiedMessage = React.useMemo(() => {
    const linesLabel = pluralizer(logs2, "entry", "entries");
    return `Copied ${logs2.length} ${linesLabel}!`;
  }, [logs2]);
  const getClipboardContent = React.useCallback(() => {
    return logs2.map((log) => `${formatClipboardHeader(log)}${log.message || ""}`).join("\n");
  }, [logs2]);
  const handleFullScreen = React.useCallback(() => {
    if (logsRef.current && screenfull.isEnabled)
      screenfull.toggle();
  }, []);
  const handleDownload = React.useCallback(() => {
    onDownload == null ? void 0 : onDownload();
  }, [onDownload]);
  React.useEffect(() => {
    local.current = {
      idSet: /* @__PURE__ */ new Set([]),
      isScrollReady: false
    };
    setLogs([]);
    setIsTailing(true);
    setFetchDirection(FetchDirection.Older);
    setFirstItemIndex(START_INDEX);
  }, [onFetch]);
  React.useEffect(() => {
    if (!initialLogs)
      return;
    addLogs(processLogs(initialLogs.map((log) => decoder(log))));
  }, [addLogs, decoder, initialLogs, processLogs]);
  React.useEffect(() => {
    fetchLogs({ canceler, fetchDirection }, FetchType.Initial).then((logs22) => {
      addLogs(logs22, false);
      setTimeout(() => {
        var _a;
        (_a = virtuosoRef.current) == null ? void 0 : _a.scrollToIndex({
          index: fetchDirection === FetchDirection.Older ? "LAST" : 0
        });
        local.current.isScrollReady = true;
      }, 200);
    });
  }, [addLogs, canceler, fetchDirection, fetchLogs]);
  React.useEffect(() => {
    const canceler2 = new AbortController();
    let buffer = [];
    const processBuffer = () => {
      const logs22 = processLogs(buffer);
      buffer = [];
      addLogs(logs22);
    };
    const throttledProcessBuffer = throttleDebounce.throttle(THROTTLE_TIME, processBuffer);
    if (fetchDirection === FetchDirection.Older && onFetch) {
      kit_internal_services.readLogStream(
        serverAddress,
        onFetch({ canceler: canceler2, fetchDirection, limit: PAGE_LIMIT }, FetchType.Stream),
        onError,
        (event) => {
          buffer.push(decoder(event));
          throttledProcessBuffer();
        }
      );
    }
    return () => {
      canceler2.abort();
      throttledProcessBuffer.cancel();
    };
  }, [addLogs, decoder, fetchDirection, onError, serverAddress, onFetch, processLogs]);
  React.useEffect(() => {
    return () => {
      canceler.abort();
    };
  }, [canceler]);
  const handleItemsRendered = React.useCallback(
    (renderedLogs) => {
      setShowButtons(renderedLogs.length < logs2.length);
    },
    [logs2.length]
  );
  const handleReachedBottom = React.useCallback(
    async (atBottom) => {
      if (isFetching || !local.current.isScrollReady)
        return;
      if (atBottom && !isTailing) {
        const newLogs = await handleFetchMoreLogs("end");
        if ((newLogs == null ? void 0 : newLogs.length) === 0)
          setIsTailing(true);
      } else if (!atBottom)
        setIsTailing(false);
    },
    [handleFetchMoreLogs, isFetching, isTailing]
  );
  const handleReachedTop = React.useCallback(
    (atTop) => {
      if (atTop)
        handleFetchMoreLogs("start");
    },
    [handleFetchMoreLogs]
  );
  React.useLayoutEffect(() => {
    if (!logsRef.current)
      return;
    const target = logsRef.current;
    const handleCopy = (e) => {
      var _a, _b, _c;
      const clipboardFormat = "text/plain";
      const levelValues = Object.values(kit_internal_types.LogLevel).join("|");
      const levelRegex = new RegExp(`<\\[(${levelValues})\\]>
`, "gim");
      const selection = (((_a = window.getSelection()) == null ? void 0 : _a.toString()) || "").replace(levelRegex, "<$1> ");
      const lines = selection == null ? void 0 : selection.split("\n");
      if ((lines == null ? void 0 : lines.length) <= 1) {
        (_b = e.clipboardData) == null ? void 0 : _b.setData(clipboardFormat, selection);
      } else {
        const oddOrEven = lines.map((line) => /^\[/.test(line) || /\]$/.test(line)).reduce(
          (acc, isTimestamp, index) => {
            if (isTimestamp)
              acc[index % 2 === 0 ? "even" : "odd"]++;
            return acc;
          },
          { even: 0, odd: 0 }
        );
        const isEven = oddOrEven.even > oddOrEven.odd;
        const content = lines.reduce((acc, line, index) => {
          const skipNewline = isEven && index % 2 === 0 || !isEven && index % 2 === 1;
          return acc + line + (skipNewline ? " " : "\n");
        }, "");
        (_c = e.clipboardData) == null ? void 0 : _c.setData(clipboardFormat, content);
      }
      e.preventDefault();
    };
    target.addEventListener("copy", handleCopy);
    return () => target == null ? void 0 : target.removeEventListener("copy", handleCopy);
  }, [logsRef]);
  return /* @__PURE__ */ jsxRuntime.jsxs(kit_Section, { fullHeight: true, children: [
    /* @__PURE__ */ jsxRuntime.jsx("div", { className: css.options, children: /* @__PURE__ */ jsxRuntime.jsxs(kit_Row.Row, { children: [
      /* @__PURE__ */ jsxRuntime.jsx(kit_Column, { width: "shrink", children: title }),
      /* @__PURE__ */ jsxRuntime.jsx(kit_Column, { align: "right", children: /* @__PURE__ */ jsxRuntime.jsxs(kit_Row.Row, { children: [
        /* @__PURE__ */ jsxRuntime.jsx(
          kit_ClipboardButton.default,
          {
            copiedMessage: clipboardCopiedMessage,
            getContent: getClipboardContent
          }
        ),
        /* @__PURE__ */ jsxRuntime.jsx(
          kit_Button,
          {
            "aria-label": "Toggle Fullscreen Mode",
            icon: /* @__PURE__ */ jsxRuntime.jsx(kit_Icon.default, { name: "fullscreen", showTooltip: true, title: "Toggle Fullscreen Mode" }),
            onClick: handleFullScreen
          }
        ),
        handleCloseLogs && /* @__PURE__ */ jsxRuntime.jsx(
          kit_Button,
          {
            "aria-label": "Close Logs",
            icon: /* @__PURE__ */ jsxRuntime.jsx(kit_Icon.default, { name: "close", title: "Close Logs" }),
            onClick: handleCloseLogs
          }
        ),
        onDownload && /* @__PURE__ */ jsxRuntime.jsx(
          kit_Button,
          {
            "aria-label": "Download Logs",
            icon: /* @__PURE__ */ jsxRuntime.jsx(kit_Icon.default, { name: "download", showTooltip: true, title: "Download Logs" }),
            onClick: handleDownload
          }
        )
      ] }) })
    ] }) }),
    /* @__PURE__ */ jsxRuntime.jsx(kit_Spinner, { center: true, spinning: isFetching, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: css.base, style: { height: height === "stretch" ? "100%" : `${height}px` }, children: [
      /* @__PURE__ */ jsxRuntime.jsx("div", { className: css.logs, ref: logsRef, children: logs2.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
        reactVirtuoso.Virtuoso,
        {
          atBottomStateChange: handleReachedBottom,
          atBottomThreshold: 12,
          atTopStateChange: handleReachedTop,
          customScrollParent: logsRef.current || void 0,
          data: logs2,
          firstItemIndex,
          followOutput: "smooth",
          initialTopMostItemIndex: fetchDirection === FetchDirection.Older ? ((initialLogs == null ? void 0 : initialLogs.length) ?? PAGE_LIMIT) - 1 : 0,
          itemContent: (_index, logEntry) => /* @__PURE__ */ jsxRuntime.jsx(
            kit_LogViewer_LogViewerEntry.default,
            {
              formattedTime: logEntry.formattedTime,
              level: logEntry.level,
              message: logEntry.message
            },
            logEntry.id
          ),
          itemsRendered: handleItemsRendered,
          ref: virtuosoRef
        },
        (logs2.length === 0 ? "Loading" : fetchDirection) + componentId
      ) : /* @__PURE__ */ jsxRuntime.jsx(kit_Message, { icon: "warning", title: "No logs to show. " }) }),
      /* @__PURE__ */ jsxRuntime.jsxs("div", { className: css.buttons, style: { display: showButtons ? "flex" : "none" }, children: [
        /* @__PURE__ */ jsxRuntime.jsx(
          kit_Button,
          {
            "aria-label": ARIA_LABEL_SCROLL_TO_OLDEST,
            icon: /* @__PURE__ */ jsxRuntime.jsx(kit_Icon.default, { name: "arrow-up", showTooltip: true, title: ARIA_LABEL_SCROLL_TO_OLDEST }),
            onClick: handleScrollToOldest
          }
        ),
        /* @__PURE__ */ jsxRuntime.jsx(
          kit_Button,
          {
            "aria-label": ARIA_LABEL_ENABLE_TAILING,
            icon: /* @__PURE__ */ jsxRuntime.jsx(
              kit_Icon.default,
              {
                name: "arrow-down",
                showTooltip: true,
                title: isTailing ? "Tailing Enabled" : ARIA_LABEL_ENABLE_TAILING
              }
            ),
            onClick: handleEnableTailing
          }
        )
      ] })
    ] }) })
  ] });
}
exports.ARIA_LABEL_ENABLE_TAILING = ARIA_LABEL_ENABLE_TAILING;
exports.ARIA_LABEL_SCROLL_TO_OLDEST = ARIA_LABEL_SCROLL_TO_OLDEST;
exports.FetchDirection = FetchDirection;
exports.FetchType = FetchType;
exports.default = LogViewer;
exports.formatLogEntry = formatLogEntry;
