/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { serialize } from "#util/serialize.js";
import { ImplementationError, InternalError, MatterError } from "../MatterError.js";
import { Bytes } from "../util/Bytes.js";
import { Lifecycle } from "../util/Lifecycle.js";
import { isObject } from "../util/Type.js";
import { Diagnostic } from "./Diagnostic.js";
import { LogLevel } from "./LogLevel.js";
const INDENT_SPACES = 2;
function LogFormat(format) {
  if (typeof format === "function") {
    return format;
  }
  const fn = LogFormat.formats[format];
  if (fn === void 0) {
    throw new ImplementationError(`Unsupported log format "${format}"`);
  }
  return fn;
}
((LogFormat2) => {
  LogFormat2.formats = {};
  LogFormat2.PLAIN = "plain";
  LogFormat2.ANSI = "ansi";
  LogFormat2.HTML = "html";
})(LogFormat || (LogFormat = {}));
const LifecycleIcons = {
  [Lifecycle.Status.Unknown]: "?",
  [Lifecycle.Status.Inactive]: "\u{1F4A4}",
  [Lifecycle.Status.Initializing]: "\u231B",
  [Lifecycle.Status.Active]: "\u2713",
  [Lifecycle.Status.Crashed]: "\u2717",
  [Lifecycle.Status.Destroying]: "\u2620\uFE0E",
  [Lifecycle.Status.Destroyed]: "\u2620\uFE0E"
};
function plaintextCreator(indents) {
  let broke = false;
  return {
    text(value) {
      if (broke) {
        broke = false;
        return `
${"".padStart(indents * INDENT_SPACES)}${value}`;
      }
      return value;
    },
    break() {
      broke = true;
      return "";
    },
    indent(producer) {
      indents++;
      const result = producer();
      indents--;
      return result;
    }
  };
}
function statusIcon(status) {
  return LifecycleIcons[status] ?? LifecycleIcons[Lifecycle.Status.Unknown];
}
LogFormat.formats.plain = function plain(diagnostic, indents = 0) {
  const creator = plaintextCreator(indents);
  const formatter = {
    ...creator,
    message: (message) => {
      const formattedValues = ensureIndented(renderDiagnostic(message.values, formatter));
      return `${formatTime(message.now)} ${LogLevel[message.level].toUpperCase()} ${message.facility} ${message.prefix}${formattedValues}`;
    },
    key: (text) => creator.text(`${text}: `),
    keylike: (text) => creator.text(`${text}`),
    value: (producer) => creator.text(producer()),
    strong: (producer) => creator.text(producer()),
    weak: (producer) => creator.text(producer()),
    error: (producer) => creator.text(producer()),
    status: (status, producer) => `${creator.text(statusIcon(status))}${producer()}`,
    via: (text) => creator.text(text),
    added: (producer) => creator.text(`+${producer()}`),
    deleted: (producer) => creator.text(`-${producer()}`)
  };
  return renderDiagnostic(diagnostic, formatter);
};
const ANSI_CODES = {
  reset: 0,
  bold: 1,
  dim: 2,
  red: 31,
  green: 32,
  yellow: 33,
  blue: 34,
  magenta: 35,
  cyan: 36,
  white: 37,
  default: 39,
  gray: 90
};
function ansiEscape(...codes) {
  const numbers = [];
  for (const code of codes) {
    if (code === "normal") {
      continue;
    }
    const number = ANSI_CODES[code];
    if (number === void 0) {
      throw new InternalError(`Invalid ANSI code ${code}`);
    }
    numbers.push(number);
  }
  if (!numbers.length) {
    return "";
  }
  return `\x1B[${numbers.join(";")}m`;
}
const Styles = {
  default: { color: "default" },
  prefix: { color: "default", dim: true },
  facility: { color: "gray", bold: true },
  debug: { color: "gray" },
  info: { color: "default" },
  notice: { color: "green" },
  warn: { color: "yellow" },
  error: { color: "red" },
  fatal: { color: "red", bold: true },
  key: { color: "blue" },
  value: { color: "default", dim: true },
  strong: { bold: true },
  weak: { dim: true },
  ballotCheck: { color: "green" },
  ballotCross: { color: "red" },
  unknown: { color: "gray" },
  inactive: { color: "gray" },
  initializing: { color: "yellow" },
  active: { color: "green" },
  crashed: { color: "red" },
  destroying: { color: "gray" },
  destroyed: { color: "gray" },
  via: { color: "magenta" },
  added: { color: "green" },
  deleted: { color: "red" }
};
LogFormat.formats.ansi = function ansi(diagnostic, indents = 0) {
  let baseStyleChanged = false;
  const creator = plaintextCreator(indents);
  const currentStyle = {
    color: "default",
    dim: false,
    bold: false
  };
  const styles = ["default"];
  function normal(text) {
    return style(styles[styles.length - 1] ?? "default", text);
  }
  const formatter = {
    message: ({ now, level, facility, prefix: nestPrefix, values }) => {
      baseStyleChanged = true;
      styles[0] = (LogLevel[level] ?? "default").toLowerCase();
      const prefix = style("prefix", `${formatTime(now)} ${LogLevel[level].toUpperCase().padEnd(6)}`);
      facility = style(
        "facility",
        facility.length > 20 ? `${facility.slice(0, 10)}~${facility.slice(facility.length - 9)}` : facility.padEnd(20)
      );
      if (nestPrefix) {
        nestPrefix = style("prefix", nestPrefix);
      }
      const formattedValues = ensureIndented(renderDiagnostic(values, formatter));
      return `${prefix} ${facility} ${nestPrefix}${formattedValues}`;
    },
    text: (text) => creator.text(normal(text)),
    indent: (producer) => creator.indent(producer),
    break: () => {
      if (baseStyleChanged) {
        baseStyleChanged = false;
        styles[0] = "default";
      }
      return creator.break();
    },
    key: (text) => creator.text(style("key", `${text}: `)),
    keylike: (text) => creator.text(style("key", `${text}`)),
    value: (producer) => {
      styles.push("value");
      const result = producer();
      styles.pop();
      return result;
    },
    strong: (producer) => {
      styles.push("strong");
      const result = producer();
      styles.pop();
      return result;
    },
    weak: (producer) => {
      styles.push("weak");
      const result = producer();
      styles.pop();
      return result;
    },
    error: (producer) => {
      styles.push("error");
      const result = producer();
      styles.pop();
      return result;
    },
    status: (status, producer) => {
      styles.push(status);
      const result = `${creator.text(style(status, statusIcon(status)))}${producer()}`;
      styles.pop();
      return result;
    },
    via: (text) => creator.text(style("via", text)),
    added: (producer) => {
      styles.push("added");
      const result = `${creator.text(style("added", "+"))}${producer()}`;
      styles.pop();
      return result;
    },
    deleted: (producer) => {
      styles.push("deleted");
      const result = `${creator.text(style("deleted", "-"))}${producer()}`;
      styles.pop();
      return result;
    }
  };
  return renderDiagnostic(diagnostic, formatter) + ansiEscape("reset");
  function escapes(styleName) {
    const style2 = Styles[styleName];
    if (style2 === void 0) {
      throw new InternalError(`Invalid ANSI style "${styleName}"`);
    }
    const targetDim = !!style2.dim;
    const targetBold = !!style2.bold;
    let targetColor = style2.color;
    if (!targetColor) {
      for (let i = styles.length; i > 0; i--) {
        const color = Styles[styles[i - 1] ?? "default"].color;
        if (color) {
          targetColor = color;
          break;
        }
      }
    }
    const codes = Array();
    if (!targetDim && currentStyle.dim || !targetBold && currentStyle.bold) {
      codes.push("reset");
      currentStyle.dim = false;
      currentStyle.bold = false;
      currentStyle.color = "default";
    }
    if (targetDim !== currentStyle.dim) {
      codes.push("dim");
      currentStyle.dim = true;
    }
    if (targetBold !== currentStyle.bold) {
      codes.push("bold");
      currentStyle.bold = true;
    }
    if (targetColor && targetColor !== currentStyle.color) {
      codes.push(targetColor);
      currentStyle.color = targetColor;
    }
    return ansiEscape(...codes);
  }
  function style(style2, text) {
    if (text === "") {
      return text;
    }
    const segments = text.match(/[^✓✔✗✘]+|[✓✔✗✘]/g);
    if (segments === null) {
      throw new InternalError("ANSI text processing regex failure");
    }
    return segments.map((segment) => {
      let esc;
      switch (segment) {
        case "\u2713":
        case "\u2714":
          esc = escapes("ballotCheck");
          break;
        case "\u2717":
        case "\u2718":
          esc = escapes("ballotCross");
          break;
        default:
          esc = escapes(style2);
          break;
      }
      return `${esc}${segment}`;
    }).join("");
  }
};
function htmlSpan(type, inner) {
  return `<span class="matter-log-${type}">${inner}</span>`;
}
LogFormat.formats.html = function html(diagnostic) {
  function escape(text) {
    return text.toString().replace(/</g, "&amp").replace(/</g, "&lt;").replace(/>/g, "&gt;");
  }
  const formatter = {
    message: ({ now, level, facility, prefix, values }) => {
      prefix = prefix.replace(/ /g, "&nbsp;");
      const formattedValues = renderDiagnostic(values, formatter);
      return htmlSpan(
        `line ${LogLevel[level].toLowerCase()}`,
        `${htmlSpan("time", formatTime(now))} ${htmlSpan("level", LogLevel[level].toUpperCase())} ${htmlSpan(
          "facility",
          facility
        )} ${prefix}${formattedValues}`
      );
    },
    text: escape,
    break: () => "<br/>",
    indent: (producer) => htmlSpan("indent", producer()),
    key: (text) => htmlSpan("key", `${escape(text)}:`) + " ",
    keylike: (text) => htmlSpan("key", `${escape(text)}`),
    value: (producer) => htmlSpan("value", producer()),
    strong: (producer) => `<em>${producer()}</em>`,
    weak: (producer) => htmlSpan("weak", producer()),
    error: (producer) => htmlSpan("error", producer()),
    status: (status, producer) => htmlSpan(`status-${status}`, producer()),
    via: (text) => htmlSpan("via", escape(text)),
    added: (producer) => htmlSpan("added", producer()),
    deleted: (producer) => htmlSpan("deleted", producer())
  };
  return renderDiagnostic(diagnostic, formatter);
};
function renderValue(value, formatter, squash) {
  if (value === void 0) {
    return formatter.text("undefined");
  }
  if (value === null) {
    return formatter.text("null");
  }
  if (Bytes.isBytes(value)) {
    return formatter.text(Bytes.toHex(value));
  }
  if (value instanceof Error) {
    return renderDiagnostic(Diagnostic.error(value), formatter);
  }
  if (typeof value === "object" && Symbol.iterator in value && !(value instanceof String)) {
    const list = sequenceToList(value);
    if (!list.length) {
      return "";
    }
    if (list.length > 1) {
      return renderList(list, formatter);
    }
    const first = valueFor(list[0]);
    return first.map((e) => {
      if (typeof e === "string" && !squash) {
        e = e.trim();
      }
      return renderDiagnostic(e, formatter);
    }).filter((e) => e.length > 0).join(squash ? "" : " ");
  }
  if (value instanceof Date) {
    return formatter.text(formatTime(value));
  }
  if (typeof value === "object") {
    if (value instanceof String) {
      return value.toString();
    }
    return formatter.text(serialize(value) ?? "undefined");
  }
  if (typeof value === "function") {
    return renderDiagnostic(value(), formatter);
  }
  const text = typeof value === "string" || value instanceof String ? value : value.toString().trim();
  if (!text.includes("\n")) {
    return formatter.text(text);
  }
  return renderList(text.split("\n"), formatter);
}
function renderList(value, formatter) {
  const parts = Array();
  for (const v of value) {
    parts.push(renderDiagnostic(v, formatter));
    formatter.break();
  }
  return parts.join("");
}
function renderIndentedList(value, formatter) {
  return formatter.indent(() => {
    return renderList(value, formatter);
  });
}
function renderDictionary(value, formatter) {
  const entries = value instanceof Map ? value.entries() : Object.entries(value);
  const parts = [];
  for (const [k, v] of entries) {
    if (parts.length) {
      parts.push(" ");
    }
    const suppressKey = isObject(v) && Diagnostic.presentationOf(v) === "flag";
    if (!suppressKey) {
      parts.push(formatter.key(k));
    }
    const formattedValue = formatter.value(() => renderDiagnostic(v, formatter));
    if (!suppressKey || formattedValue.length) {
      parts.push(formattedValue);
    } else {
      if (parts.length && parts[parts.length - 1] === " ") {
        parts.pop();
      }
    }
  }
  return parts.join("").trim();
}
function valueFor(value) {
  if (typeof value !== "object" || value === null) {
    return value;
  }
  if (Diagnostic.value in value) {
    const proxied = Diagnostic.valueOf(value);
    if (proxied === value) {
      throw new InternalError("Diagnostic value proxies to itself");
    }
    return proxied;
  }
  return value;
}
function presentationFor(value) {
  if (typeof value !== "object" || value === null) {
    return;
  }
  if (Diagnostic.presentation in value) {
    return Diagnostic.presentationOf(value);
  }
  const proxied = Diagnostic.valueOf(value);
  if (proxied && proxied !== value) {
    if (proxied === value) {
      throw new InternalError("Diagnostic value proxies to itself");
    }
    return presentationFor(proxied);
  }
}
function renderDiagnostic(value, formatter, ignorePresentation) {
  const presentation = ignorePresentation ? void 0 : presentationFor(value);
  const logValue = valueFor(value);
  if (logValue === value) {
    ignorePresentation = true;
  } else {
    ignorePresentation = void 0;
    value = logValue;
  }
  switch (presentation) {
    case void 0:
      return renderValue(value, formatter, false);
    case "message":
      if (value === void 0 || value === null) {
        throw new ImplementationError("Diagnostic message is not an object");
      }
      return formatter.message(Diagnostic.message(value));
    case "list":
      if (typeof value?.[Symbol.iterator] !== "function") {
        throw new ImplementationError("Diagnostic list is not iterable");
      }
      return renderIndentedList(value, formatter);
    case "squash":
      return renderValue(value, formatter, true);
    case "strong":
      return formatter.strong(() => renderDiagnostic(value, formatter, ignorePresentation));
    case "weak":
      return formatter.weak(() => renderDiagnostic(value, formatter, ignorePresentation));
    case "added":
      return formatter.added(() => renderDiagnostic(value, formatter, ignorePresentation));
    case "deleted":
      return formatter.deleted(() => renderDiagnostic(value, formatter, ignorePresentation));
    case "flag":
      return value.length ? formatter.keylike(value) : "";
    case "error":
      return formatter.error(() => renderDiagnostic(value, formatter, ignorePresentation));
    case "via":
      return formatter.via(`${value}`);
    case "dictionary":
      if (typeof value !== "object") {
        throw new ImplementationError("Diagnostic dictionary is not an object");
      }
      return renderDictionary(value, formatter);
    case Lifecycle.Status.Unknown:
    case Lifecycle.Status.Inactive:
    case Lifecycle.Status.Initializing:
    case Lifecycle.Status.Active:
    case Lifecycle.Status.Crashed:
    case Lifecycle.Status.Destroyed:
      return formatter.status(presentation, () => renderDiagnostic(value, formatter, ignorePresentation));
    default:
      throw new ImplementationError(`Unsupported diagnostic presentation "${presentation}"`);
  }
}
function sequenceToList(sequence) {
  let group;
  const list = Array();
  for (const value of sequence) {
    if (presentationFor(value) === "list") {
      group = void 0;
      list.push(value);
      continue;
    }
    if (!group) {
      list.push(group = [value]);
    } else {
      group.push(value);
    }
  }
  return list;
}
function formatTime(time) {
  return `${time.getFullYear()}-${(time.getMonth() + 1).toString().padStart(2, "0")}-${time.getDate().toString().padStart(2, "0")} ${time.getHours().toString().padStart(2, "0")}:${time.getMinutes().toString().padStart(2, "0")}:${time.getSeconds().toString().padStart(2, "0")}.${time.getMilliseconds().toString().padStart(3, "0")}`;
}
function ensureIndented(text) {
  if (text.match(/\n\S/)) {
    return text.replace(/\n/g, "\n  ");
  }
  return text;
}
if (MatterError.formatterFor === MatterError.defaultFormatterFactory) {
  MatterError.formatterFor = LogFormat;
}
if (typeof MatterHooks !== "undefined") {
  MatterHooks.messageAndStackFor = (error, parentStack) => {
    const { message, stack, stackLines } = Diagnostic.messageAndStackFor(error, parentStack);
    let stackStr;
    if (stack) {
      stackStr = stack.map((frame) => LogFormat.formats.ansi(frame).trim()).join("\n");
    }
    return { message, stack: stackStr, stackLines };
  };
}
export {
  LogFormat
};
//# sourceMappingURL=LogFormat.js.map
